diff --git a/.gitignore b/.gitignore index f13265ea4b..12748f8d4f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ node_modules/ /.events2 /dist/ /builds/*.gz +/test/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..031fe3a6af --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +dist: xenial +language: generic +install: npm install +script: make +branches: + only: + - master diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a89bcfb7e..3cc1884649 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,1346 @@ **Note**: Installing the script from one of the links below will disable automatic updates. If you want automatic updates, install the script from the links on the [main page](https://www.4chan-x.net/). +-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.23 + +**v1.14.23.1** *(2024-11-20)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.23.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.23.1/builds/4chan-X-noupdate.crx)] +- (saxamaphone69) Fix dead documentation link. +- (youcmd) Recognize Youtube stream links. +- (kpg-anon) Recognize Youtube shorts links. +- (TuxedoTako) Fix settings import from 4chan XT. +- (edde746) Fix issue with script on Orion browser. +- Possible fix for reported issue in v1.14.23.0 causing stylesheet selector not to work. + +**v1.14.23.0** *(2024-11-20)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.23.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.23.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.22.4. +- (4chenz) Support posting MP4 files on 4chan. +- 4channel.org domain is no more; fix issue with links not being bold in headers. +- Fix for unwanted sorting of catalog under certain settings. #3212 +- Turn JS Whitelist functionality off by default. At the moment it breaks more stuff than it fixes. +- (paradox460) Add ability to clear whole thread watcher. +- (paradox460) Add a button to quick reply to split long posts. + +### v1.14.22 + +**v1.14.22.5** *(2024-11-20)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.22.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.22.5/builds/4chan-X-noupdate.crx)] +- (4chenz) Support posting MP4 files on 4chan. + +**v1.14.22.4** *(2023-02-28)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.22.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.22.4/builds/4chan-X-noupdate.crx)] +- Recognize JPEG files with .jfif extensions as images for purposes of Image Hover etc.; also recognize .avif and .jxl files as images. +- Avoid breaking sauce settings of people with links to original Google Images and Google Lens, provided they didn't already update to v1.14.22.3. + +**v1.14.22.3** *(2023-02-27)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.22.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.22.3/builds/4chan-X-noupdate.crx)] +- Switch Google image search back to old version, thanks to https://boards.4channel.org/g/thread/91737566#p91789527 and others. + +**v1.14.22.2** *(2023-02-27)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.22.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.22.2/builds/4chan-X-noupdate.crx)] +- Fix quick reply not opening immediately after making post. #2905 +- Update Randomize Filename to match current 4chan format. https://boards.4channel.org/g/thread/91737566#p91784238 +- Remove empty space from ads if they don't load. https://kissu.moe/b/res/7155#11052 +- Make post from QR more like original form post to possibly reduce posting errors. #3330 +- Disable Javascript Whitelist on captcha iframe to fix issues with Cloudflare scripts. #3292 +- (4chenz) Add Google Lens image search url. +- Change Google image search to Google Lens due to old link not working. +- Change issues link from dead gitreports.com back to Github. + +**v1.14.22.1** *(2022-07-14)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.22.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.22.1/builds/4chan-X-noupdate.crx)] +- Remove old /nen/, add successor site. + +**v1.14.22.0** *(2022-05-23)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.22.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.22.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.21.7. +- (puckzxz) Added option in Thread Watcher to open dead threads +- (paradox460) Add a menu item to open unread in ThreadWatcher +- (WastedMeerkat) Fixed Twitch.tv embeds +- (PinkCatGoodActually) Fix vocaroo embeds +- Remove google.com permissions. +- Remove obsolete 'Captcha Fixes' and 'Captcha Solving Service' options and related code. + +### v1.14.21 + +**v1.14.21.7** *(2021-07-08)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.21.7/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.21.7/builds/4chan-X-noupdate.crx)] +- Don't warn on posting without captcha when "Verification not required." +- Fix error reported in #3124. + +**v1.14.21.6** *(2021-07-08)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.21.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.21.6/builds/4chan-X-noupdate.crx)] +- Captcha related bugfixes. + +**v1.14.21.5** *(2021-07-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.21.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.21.5/builds/4chan-X-noupdate.crx)] +- Fix bug causing captcha to sometimes not work when replying from index. + +**v1.14.21.4** *(2021-07-05)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.21.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.21.4/builds/4chan-X-noupdate.crx)] +- Preliminary support for new first-party captcha on 4chan. + +**v1.14.21.3** *(2021-05-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.21.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.21.3/builds/4chan-X-noupdate.crx)] +- Fix race condition causing unread posts tracking to malfunction. + +**v1.14.21.2** *(2021-05-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.21.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.21.2/builds/4chan-X-noupdate.crx)] +- Fix bug introduced in v1.14.21.1 causing error message when menu opened. + +**v1.14.21.1** *(2021-05-06)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.21.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.21.1/builds/4chan-X-noupdate.crx)] +- Fix some posts not being processed on 4chan /g/ and /sci/. +- Update for flag changes on 4chan /mlp/ and /pol/. + +**v1.14.21.0** *(2021-01-15)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.21.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.21.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.20.6. +- (ihavenoface) Fix bug causing unwanted menu in inlined/previewed quotes. +- (thth) Add Sauce links to Gallery. +- (jakem72360) Add keybind to download images in Gallery. +- (4chenz) Add new sorting mode to index / catalog: Posts per minute + +### v1.14.20 + +**v1.14.20.6** *(2021-01-15)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.20.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.2 +0.6/builds/4chan-X-noupdate.crx)] +- Userscript only: Add Youtube to Tampermonkey XHR domain whitelist to stop annoying permission popups. + +**v1.14.20.5** *(2021-01-14)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.20.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.20.5/builds/4chan-X-noupdate.crx)] +- (ihavenoface) Use native oembed for youtube link titles. Fixes "Forbidden or Private" on YouTube embeds. + +**v1.14.20.4** *(2020-10-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.20.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.20.4/builds/4chan-X-noupdate.crx)] +- Change default archives list to https://4chenz.github.io/archives.json/archives.json #2808 + +**v1.14.20.3** *(2020-09-13)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.20.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.20.3/builds/4chan-X-noupdate.crx)] +- (nonasol) Update vocaroo media link. This fixes the issue where the newer voca.ro links aren't loading properly. + +**v1.14.20.2** *(2020-08-24)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.20.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.20.2/builds/4chan-X-noupdate.crx)] +- Fix index not working on /vg/ when sort mode is last reply. https://github.com/ccd0/4chan-x/issues/2685#issuecomment-679311712 + +**v1.14.20.1** *(2020-08-24)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.20.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.20.1/builds/4chan-X-noupdate.crx)] +- Update for 4chan /vg/ change. + +**v1.14.20.0** *(2020-06-16)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.20.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.20.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.19.4. +- Make image features work with .webp images. +- Make embedding work with .webp links. #365 +- Make video features work with .ogv videos. +- Fix 'Reveal Spoiler Thumbnails' on .bmp files. + +### v1.14.19 + +**v1.14.19.4** *(2020-06-11)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.19.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.19.4/builds/4chan-X-noupdate.crx)] +- Update Twitch embedding. + +**v1.14.19.3** *(2020-05-24)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.19.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.19.3/builds/4chan-X-noupdate.crx)] +- Fix videos not being prefetched properly by prefetch button. #2601 +- Link former Samachan users to SushiChan with one-time notification. + +**v1.14.19.2** *(2020-05-21)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.19.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.19.2/builds/4chan-X-noupdate.crx)] +- Revert "Add link on recently-departed Samachan to proposed move to SushiChan." + +**v1.14.19.1** *(2020-05-19)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.19.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.19.1/builds/4chan-X-noupdate.crx)] +- Add link on recently-departed Samachan to proposed move to SushiChan. + +**v1.14.19.0** *(2020-05-12)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.19.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.19.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.18.1. +- Fix issue where shift-click on Quick Reply submit to bypass warning did not use captcha when posting. +- Fix Gallery 'Stretch to Fit' on sites with multifile posting. + +### v1.14.18 + +**v1.14.18.1** *(2020-04-30)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.18.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.18.1/builds/4chan-X-noupdate.crx)] +- Support smuglo.li fallback domains. + +**v1.14.18.0** *(2020-04-28)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.18.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.18.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.17.3. +- Switch to noembed.com for Youtube titles. This should fix most Youtube titles not loading. #2327 +- Use oEmbed for Clyp link titles so that they work in Chrome extension without additional permissions. +- Add AZcaptcha to solver service links. +- (saxamaphone69) CSS fixes for Quick Reply. + +### v1.14.17 + +**v1.14.17.3** *(2020-04-19)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.17.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.17.3/builds/4chan-X-noupdate.crx)] +- Fix captcha loading issue when captcha language option is used. #2531 + +**v1.14.17.2** *(2020-04-16)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.17.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.17.2/builds/4chan-X-noupdate.crx)] +- Add hcaptcha.com and subdomains to Javascript Whitelist so that captcha on Cloudflare security check operates properly. #2584 + +**v1.14.17.1** *(2020-04-03)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.17.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.17.1/builds/4chan-X-noupdate.crx)] +- Fix 'Mark Read' link for 'Unread Line in Index' being placed in wrong position on smuglo.li. + +**v1.14.17.0** *(2020-04-01)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.17.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.17.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.16.7. +- Preliminary support for new Kissu UI. + +### v1.14.16 + +**v1.14.16.7** *(2020-03-30)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.16.7/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.16.7/builds/4chan-X-noupdate.crx)] +- Fix bug causing some sections on advanced settings not to show up outside 4chan. +- Sauce: Update DeviantArt URL format (#2563) and ImgOps URL format. +- Embedding: Update Gfycat, LiveLeak, and Openings.moe embedding, and fix minor bugs. + +**v1.14.16.6** *(2020-03-24)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.16.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.16.6/builds/4chan-X-noupdate.crx)] +- Fix description exceeding maximum length. + +**v1.14.16.5** *(2020-03-06)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.16.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.16.5/builds/4chan-X-noupdate.crx)] +- Enable 4chan X by default on more sites. + +**v1.14.16.4** *(2020-02-05)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.16.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.16.4/builds/4chan-X-noupdate.crx)] +- Fix for unclosed link in https://sushigirl.us/ announcement. + +**v1.14.16.3** *(2020-01-21)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.16.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.16.3/builds/4chan-X-noupdate.crx)] +- Update Vocaroo embedding. #2528 + +**v1.14.16.2** *(2020-01-01)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.16.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.16.2/builds/4chan-X-noupdate.crx)] +- Make sure audio element with added controls is not collapsed. + +**v1.14.16.1** *(2019-12-30)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.16.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.16.1/builds/4chan-X-noupdate.crx)] +- Merge v1.14.15.3: Update location of archive list. #2520 + +**v1.14.16.0** *(2019-12-20)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.16.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.16.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.15.2. +- Fix parsing of spoilered images on kissu.moe. + +### v1.14.15 + +**v1.14.15.3** *(2019-12-30)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.15.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.15.3/builds/4chan-X-noupdate.crx)] +- Update location of archive list. #2520 + +**v1.14.15.2** *(2019-12-20)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.15.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.15.2/builds/4chan-X-noupdate.crx)] +- Display message when noscript captcha is disabled, and link to open list of alternate imageboards. #1539 #2500 + +**v1.14.15.1** *(2019-12-17)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.15.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.15.1/builds/4chan-X-noupdate.crx)] +- Enable 4chan X by default on more sites. +- Fix spurious linkification bug on infinity-based sites. #2356 +- Fix clash between 4chan X header bar and native header bars. #2171 +- Fix unwanted spaces in full board list on Tinyboard sites. + +**v1.14.15.0** *(2019-12-13)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.15.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.15.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.14.5. +- Support archived threads on kissu.moe. +- Fix display of board names containing special characters in custom board list. #2473 +- Increase link title cache size. #2327 +- Add option to use your own Youtube API key. #2327 +- %URL in Sauce uses full image when extension is .jpeg. + +### v1.14.14 + +**v1.14.14.5** *(2019-12-13)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.14.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.14.5/builds/4chan-X-noupdate.crx)] +- Never mark Youtube videos as 'Forbidden or Private' since 403 errors are most likely from API throttling. #2327 +- Update for changes in Tegaki (4chan drawing script). #2467 + +**v1.14.14.4** *(2019-09-16)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.14.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.14.4/builds/4chan-X-noupdate.crx)] +- Merge v1.14.13.4: Update for quotelink changes on kissu.moe. +- Don't try to insert header etc. on .rss URLs. + +**v1.14.14.3** *(2019-09-14)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.14.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.14.3/builds/4chan-X-noupdate.crx)] +- Fix bug in handling captcha errors when using captcha prerequest. + +**v1.14.14.2** *(2019-09-12)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.14.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.14.2/builds/4chan-X-noupdate.crx)] +- (saxamaphone69) CSS Fix for Post Jumper so it doesn't show up in catalog. +- Improve accuracy of determining that comment is only a quote and thus captcha should not be opened yet. #2421, #2431 +- Fix image hover activating when mousing over expanded images on ota-ch. + +**v1.14.14.1** *(2019-09-06)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.14.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.14.1/builds/4chan-X-noupdate.crx)] +- Merge v1.14.13.3: Move most of the pass message text to the wiki where we can let anyone edit it. + +**v1.14.14.0** *(2019-09-06)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.14.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.14.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.13.2. +- Don't open captcha when the only text so far is a quote link or quoted text. #2421 +- Add option to request captcha from captcha services when you start typing. +- Provide a visual indication that the captcha is loading from the captcha service. + +### v1.14.13 + +**v1.14.13.4** *(2019-09-16)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.13.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.13.4/builds/4chan-X-noupdate.crx)] +- Update for quotelink changes on kissu.moe. + +**v1.14.13.3** *(2019-09-06)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.13.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.13.3/builds/4chan-X-noupdate.crx)] +- Move most of the pass message text to the wiki where we can let anyone edit it. + +**v1.14.13.2** *(2019-09-05)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.13.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.13.2/builds/4chan-X-noupdate.crx)] +- Tweak message to Pass buyers and add button to dismiss it. + +**v1.14.13.1** *(2019-09-04)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.13.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.13.1/builds/4chan-X-noupdate.crx)] +- Add message to 4chan Pass buyers. + +**v1.14.13.0** *(2019-08-27)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.13.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.13.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.12.10. +- (saxamaphone69) Add option to open custom navigation links in a new tab. +- Move reply hiding button inside .intro on Tinyboard to reduce unwanted wrapping of .intro line. + +### v1.14.12 + +**v1.14.12.10** *(2019-08-27)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.10/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.10/builds/4chan-X-noupdate.crx)] +- Turn clicking to open in Gallery off on Tinyboard sites when JS enabled to avoid conflict with native image expansion. + +**v1.14.12.9** *(2019-08-22)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.9/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.9/builds/4chan-X-noupdate.crx)] +- Add option to enable/disable notification on quick filtering MD5s. #2408 +- Suggest disabling JSON Index if catalog.json is loading slowly. #2412 + +**v1.14.12.8** *(2019-08-18)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.8/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.8/builds/4chan-X-noupdate.crx)] +- Fix bug from v1.14.7.0 (commit af00c711ff) causing excerpt in thread watcher to not be set. #2404 +- Workaround for issue with new Chrome layout engine. #2397 +- Fix workaround for Chrome CORB bug breaking posting in old versions of Chrome. #2396 +- Maintain old default boardlist for people upgrading from previous versions. + +**v1.14.12.7** *(2019-08-18)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.7/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.7/builds/4chan-X-noupdate.crx)] +- Enable 4chan X by default on more sites. +- Add small announcement on /qa/. +- Remove 4chan boards from default custom board list. +- Workarounds for clashes between header and fixed elements on some sites. + +**v1.14.12.6** *(2019-08-16)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.6/builds/4chan-X-noupdate.crx)] +- Fix bug in Replace [filetype] features from v1.14.12.4. +- Enable by default on kissu.moe. + +**v1.14.12.5** *(2019-08-12)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.5/builds/4chan-X-noupdate.crx)] +- Make date parsing work on onesixtwo.club and avoid errors when we fail to parse the date. +- Minor bugfixes. + +**v1.14.12.4** *(2019-08-11)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.4/builds/4chan-X-noupdate.crx)] +- Move prefetch toggle from header menu to shortcut icons and make the option show up by default. + +**v1.14.12.3** *(2019-08-09)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.3/builds/4chan-X-noupdate.crx)] +- Merge v1.14.11.4: Assume Chrome is broken by default. #2378 + +**v1.14.12.2** *(2019-08-09)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.2/builds/4chan-X-noupdate.crx)] +- Merge v1.14.11.3: Reduce default thread updater interval from 30 seconds to 5 seconds. + +**v1.14.12.1** *(2019-08-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.1/builds/4chan-X-noupdate.crx)] +- Merge v1.14.11.2: - Fix error from Catalog Links feature in native catalog when /f/ is in the custom board list. #2390 +- Fix custom board links for boards without native catalog/archive being changed to nonexistent native catalog/archive links on catalog/archive pages. #2390 +- Fix spacing in /qa/ board title. #2369 + +**v1.14.12.0** *(2019-08-05)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.12.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.11.1. +- Shift+click on the Quick Reply submit button will now attempt to post regardless of cooldowns or other errors. +- Improved error reporting. #862 +- Detect conflicts with native extension and show appropriate error message. #1627 +- Only show MD5 quick filter notification when using keybind. #2385 +- Update Loopvid embedding. +- Update settings import from loadletter fork. +- (saxamaphone69) Make text areas in settings resize vertically only. +- Fix cooldown when the time is an exact number of minutes. #2301 +- Cache titles to reduce title requests. #2327 +- Other bugfixes. + +### v1.14.11 + +**v1.14.11.4** *(2019-08-09)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.11.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.11.4/builds/4chan-X-noupdate.crx)] +- Assume Chrome is broken by default. #2378 + +**v1.14.11.3** *(2019-08-09)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.11.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.11.3/builds/4chan-X-noupdate.crx)] +- Reduce default thread updater interval from 30 seconds to 5 seconds. + +**v1.14.11.2** *(2019-08-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.11.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.11.2/builds/4chan-X-noupdate.crx)] +- Fix error from Catalog Links feature in native catalog when /f/ is in the custom board list. #2390 + +**v1.14.11.1** *(2019-08-03)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.11.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.11.1/builds/4chan-X-noupdate.crx)] +- Security improvements. +- Fix 2captcha on Chrome extension. #2375 +- Minor bugfixes. + +**v1.14.11.0** *(2019-07-24)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.11.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.11.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.10.3. +- Make applicable keybinds work on Tinyboard/vichan. #2171 +- Make Index Navigation buttons work on Tinyboard/vichan. #2171 +- Make Catalog Links toggle work for links to Tinyboard/vichan sites in header. #2171 +- Display notification when MD5s are quick filtered with link to undo. #2221 +- Remove as many hardcoded board names as possible. #525 +- Make list of boards with external catalog user-editable. #570, #525 +- Use external catalog (catalog.neet.tv) on /f/ for catalog keybind and header links. +- Link from native catalog to external catalog now reads 'External Catalog'. +- Make list of banners configurable. +- Fix highlighting of left margin (of posts by/quoting you) when whole post is highlighted (by opening link to post or using keybinds). #585 + +### v1.14.10 + +**v1.14.10.3** *(2019-07-24)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.10.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.10.3/builds/4chan-X-noupdate.crx)] +- Fix bugs in gallery when rotating images with Fit Height enabled. + +**v1.14.10.2** *(2019-07-23)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.10.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.10.2/builds/4chan-X-noupdate.crx)] +- Limit number of autoretries on posting error to reduce likelihood of "excessive server request" bans. #1302 +- Fix bug from v1.14.10.0 causing full image and thumbnail to briefly appear simultaneously while contracting images and causing unwanted scrolling. +- Fix bug causing issues in inlined quotes such as "You" in post menu being unchecked and posts sometimes being wrongly marked as deleted. + +**v1.14.10.1** *(2019-07-19)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.10.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.10.1/builds/4chan-X-noupdate.crx)] +- Merge v1.14.9.2: Fix regression causing thread watcher to stop highlighting active thread. +- Add option `Filter in Native Catalog` to apply 4chan X filters on native catalog. Also works on vichan sites. Enabled by default for new installs only. #2351 +- (droM4X) Add keybinds to rotate images in Gallery. +- Other minor bugfixes. + +**v1.14.10.0** *(2019-07-17)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.10.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.10.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.9.1. +- 4chan X features such as Image Hover, Sauce, Gallery, etc. now work on multiple files per post on vichan sites. #2171 +- Make Thread Stats work on Tinyboard/vichan sites. #2171 +- Fix bug in highlighting of page number in Thread Stats when a sticky causes an 11th page. #753 +- Change `Last Reply` index/catalog sort mode to sort by last reply that's neither hidden or filtered. +- Support the same options for the `boards:` option in Sauce that we do for Filter. +- Add `Dismiss posts quoting you` item to Thread Watcher menu to unhighlight the icon and threads until there are new replies quoting you. +- Prevent Thread Watcher from showing unread posts in a thread if all unread posts are hidden/filtered. +- Fix excessive Thread Watcher checking on switching tabs when cookies are disabled. +- (saxamaphone69) HTML/CSS improvements for settings dialog. +- Other minor bugfixes. + +### v1.14.9 + +**v1.14.9.2** *(2019-07-19)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.9.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.9.2/builds/4chan-X-noupdate.crx)] +- Fix regression causing thread watcher to stop highlighting active thread. + +**v1.14.9.1** *(2019-07-11)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.9.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.9.1/builds/4chan-X-noupdate.crx)] +- "Change `Use Faster Image Host` setting to `Override 4chan Image Host` in Advanced menu, which can be set to whichever image host you want to use. #2046 +- Change announcement hiding link to FontAwesome minus button and make it work on Tinyboard/vichan sites. #2171 +- Fix Tinyboard/vichan post form redirecting before your posts can be recorded as yours. #2171 +- Fix posts made by you appearing to the side of the last post on Tinyboard/vichan. #2217 +- Make `Fappe Tyme`, `Werk Tyme`, `Volume in New Tab`, `Loop in New Tab`, and `Normalize URL` work on Tinyboard/vichan. #2171 +- Other small bugfixes. + +**v1.14.9.0** *(2019-07-09)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.9.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.9.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.8.0. +- (ebinBuddha) Implement `Unique ID and Capcode Navigation` feature to rapidly navigate all posts from an ID/capcode. +- (koma-cute) Update Sauce links for HTTPS support changes. +- Support captcha solving services. +- Implement new APIs: `LoadCaptcha`, `RequestCaptcha`, and `AbortCaptcha`. +- Add link to header menu to mark all posts in a thread as unread. #1299 +- Support searching index for threads by regular expressions on a given field using syntax field:/regexp/ +- Fix bugs in highlighting of own posts / posts quoting you / filter-highlighted posts on Tinyboard/vichan sites. #2169 +- Fix ID colors on Tinyboard/vichan sites. #2355 +- Fix thread stubs on Tinyboard/vichan sites. +- Update Yandex image search URL again. #2349 +- Various bugfixes, especially for Tinyboard/vichan sites. + +### v1.14.8 + +**v1.14.8.0** *(2019-06-24)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.8.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.8.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.7.4. +- Work around issues with blocked AJAX requests in Chrome extension. #2228 +- Update Yandex image search URL. #2330 +- Add contact links to mod and other capcode posts. +- (wlerin) Add Streamable embedding. +- Add BitChute embedding. #2038 +- Add PeerTube embedding. +- Handle failures of Greasemonkey API better. + +### v1.14.7 + +**v1.14.7.4** *(2019-05-19)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.7.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.7.4/builds/4chan-X-noupdate.crx)] +- Fix conflict with thread-stats element on some vichan boards. +- Update ad blocker workaround for 4channel domain. + +**v1.14.7.3** *(2019-04-22)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.7.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.7.3/builds/4chan-X-noupdate.crx)] +- Change default title on /qa/ to something more accurate. Users can, as always, edit it to whatever they want. +- Minor CSS fixes. + +**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.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)] +- Fix bugs related to additional permissions requests. #2230 +- Revert changes in thread watcher that caused performance decrease. +- Fix thread watcher highlighting when quoted on boards with unusual characters in name (e.g. some lainchan boards). + +**v1.14.5.12** *(2019-01-28)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.12/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.12/builds/4chan-X-noupdate.crx)] +- Recover as well as possible from data corruption caused by ad filter interaction with Tampermonkey. #2218 + +**v1.14.5.11** *(2019-01-26)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.11/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.11/builds/4chan-X-noupdate.crx)] +- Fix regression that broke favicon turning red on thread archival/404. #2190 +- Fix 'Auto Watch Reply' in corner cases when not all thread info is available. + +**v1.14.5.10** *(2019-01-25)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.10/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.10/builds/4chan-X-noupdate.crx)] +- Fix performance issues with video hover preview in long threads. #2214 + +**v1.14.5.9** *(2019-01-24)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.9/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.9/builds/4chan-X-noupdate.crx)] +- Don't re-insert unread line unless it needs to move. #2214 + +**v1.14.5.8** *(2019-01-24)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.8/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.8/builds/4chan-X-noupdate.crx)] +- Restore updating faster than 30 seconds after 4chan change. +- Fix false detection of posts added by updater on Tinyboard as own posts. +- Support recognizing quotelinks to pages with extensions other than .html. +- Add FLAC and M4A to embeddable audio types. #2202 +- Fix issue from v1.14.5.6 causing display of 'NaN seconds' before index is loaded. + +**v1.14.5.7** *(2019-01-06)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.7/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.7/builds/4chan-X-noupdate.crx)] +- Work when site uses extensions for pages other than .html + +**v1.14.5.6** *(2018-12-16)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.6/builds/4chan-X-noupdate.crx)] +- Fix bug causing Last Index Refresh time to not update with passing time until index/catalog reloaded. +- Treat .bmp files as images. +- Support current-catalog link in custom board list on Tinyboard/vichan. +- Quick fix for issues on lainchan due to not accounting for post container. + +**v1.14.5.5** *(2018-12-09)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.5/builds/4chan-X-noupdate.crx)] +- Fix bug causing errors on threads in overboards from boards with unusual characters in the name. + +**v1.14.5.4** *(2018-12-08)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.4/builds/4chan-X-noupdate.crx)] +- Tinyboard/vichan improvements: Process posts added by thread updating, thread expansion, and infinite scrolling scripts. +- Fire a `PostsRemoved` event when posts are removed. + +**v1.14.5.3** *(2018-12-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.3/builds/4chan-X-noupdate.crx)] +- Fix bugs in cross-site data access. + +**v1.14.5.2** *(2018-12-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.2/builds/4chan-X-noupdate.crx)] +- All Thread Watcher functionality is now supported on and across Tinyboard/vichan sites, including auto-updating, the unread count, and lighting up upon replies, with the exception that threads from sites without JSON APIs will not be updated when the thread watcher is refreshed. +- The `Unread Count`, `Unread Line`, `Scroll to Last Read Post`, and `Desktop Notifications` are now supported on Tinyboard/vichan sites. +- Replies made AJAX on Tinyboard/vichan sites are now marked as yours. + +**v1.14.5.1** *(2018-12-06)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.1/builds/4chan-X-noupdate.crx)] +- Support style switcher and non-default styles on Tinyboard. + +**v1.14.5.0** *(2018-12-06)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.5.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.4.7. +- Show threads from other sites in Thread Watcher. When threads from multiple sites are shown, a prefix is added before the board name to distinguish sites. The prefix can be disabled by unchecking `Show Site Prefix` preference in thread watcher menu. More work remains; refreshing is still only working on 4chan, and the unread count still only works for 4chan threads. +- Fix bug affecting boards with names containing characters escaped in URLs. + +### v1.14.4 + +**v1.14.4.7** *(2018-12-04)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.4.7/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.4.7/builds/4chan-X-noupdate.crx)] +- Display links to NSFW boards in bottom board list. #2158 +- Remove empty space from unloaded ads. + +**v1.14.4.6** *(2018-11-25)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.4.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.4.6/builds/4chan-X-noupdate.crx)] +- Use now working sys.4channel.org for posting to worksafe boards; should fix some issues with posting. #2140 #2149 +- Fix catalog/search link rewriting. #2151 +- Make URLs in thread watcher point to appropriate domain. #2143 +- Make cross-domain quotes of you light up thread watcher. + +**v1.14.4.5** *(2018-11-23)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.4.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.4.5/builds/4chan-X-noupdate.crx)] +- Fix bug in previous version causing 4channel.org to be seen as separate site in thread watcher etc. + +**v1.14.4.4** *(2018-11-23)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.4.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.4.4/builds/4chan-X-noupdate.crx)] +- Update for 4channel.org +- Don't remove code paste field if the captcha is refusing to serve a challenge. + +**v1.14.4.3** *(2018-11-08)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.4.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.4.3/builds/4chan-X-noupdate.crx)] +- Add extra collapse link at bottom of expanded threads. +- Add option `Expand thread only` in `Image Expansion` menu; makes expanding all images when in index only operate within current thread. +- Keep threads from moving off screen when contracted via keybind. +- Make `Mark Read` button extend across whole document. +- Add keybind to mark thread read from index (if `Unread Line in Index` enabled). Default is `Ctrl+0`. +- Make `Scroll to Last Read Post` operate in index if `Unread Line in Index` enabled. + +**v1.14.4.2** *(2018-10-31)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.4.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.4.2/builds/4chan-X-noupdate.crx)] +- Fix unread line becoming invisible on Halloween theme. + +**v1.14.4.1** *(2018-10-29)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.4.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.4.1/builds/4chan-X-noupdate.crx)] +- Move drawing of QR file onto Tegaki canvas into 4chan X so it is less likely to be affected by ad blocking. +- Add link to FAQ section in 'Could not open file.' error. +- Make metadata for files selected in Quick Reply available as data-type, data-height, data-width, and data-duration attributes on thumbnail. + +**v1.14.4.0** *(2018-10-22)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.4.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.4.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.3.2. +- (pentargency) Add field in Advanced settings allowing user to customize filename of images pasted into Quick Reply box +- (HushBugger) Embed images that have Twitter-style suffixes (e.g. .jpg:orig) +- The `PostsInserted` event is now fired on the common ancestor of the inserted posts. It bubbles, so listeners registered on the document will still work. +- Load Tegaki from rawgit.com if loading from s.4cdn.org fails or is blocked. +- Add `Unread Line in Index` option (default: off), which adds a line to threads in the index showing which posts are new, and adds a link to mark them read. +- Minor bugfixes. + +### v1.14.3 + +**v1.14.3.2** *(2018-10-22)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.3.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.3.2/builds/4chan-X-noupdate.crx)] +- whatanime.ga is now trace.moe #2106 + +**v1.14.3.1** *(2018-09-19)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.3.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.3.1/builds/4chan-X-noupdate.crx)] +- Fix ad blocking related issues with image downloading. #2066 +- Fix bug with hidden posts count on button in settings. +- Update reporting to archive to work with new report form. This still doesn't work with ad blocking enabled, but you can add `@@||$xmlhttprequest,domain=sys.4chan.org` to your filters to make it work. +- Uncheck 'Report to Archives' checkbox and disable details field by default. #1745 + +**v1.14.3.0** *(2018-09-18)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.3.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.3.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.2.1. +- Fix issues with archives, Link Title, and Github Gist embedding caused by ad blocking changes. + +### v1.14.2 + +**v1.14.2.1** *(2018-06-22)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.2.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.2.1/builds/4chan-X-noupdate.crx)] +- New fix for data loss issues. #1910 + +**v1.14.2.0** *(2018-06-05)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.2.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.2.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.1.2. +- Revert workaround for #1323 (7b8c2df5e4aae96b47771c0bb90989765d719d5c) which may be contributing for data corruption issues. #1910 + +### v1.14.1 + +**v1.14.1.2** *(2018-05-25)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.1.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.1.2/builds/4chan-X-noupdate.crx)] +- Disable JS whitelist if 4chan X loads too late for it to be effective. + +**v1.14.1.1** *(2018-05-18)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.1.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.1.1/builds/4chan-X-noupdate.crx)] +- Possible fix for data loss issues. #1875 + +**v1.14.1.0** *(2018-05-17)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.1.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.1.0/builds/4chan-X-noupdate.crx)] +- Based on v1.14.0.15. +- Allow the Quick Reply to be used on pages without an original post form such as archived threads. #242 +- Only autorefresh thread watcher from current tab. + +## v1.14.0 + +**v1.14.0.15** *(2018-05-05)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.15/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.15/builds/4chan-X-noupdate.crx)] +- Merge v1.13.15.12: Update workaround for ad breaking 4chan, take two. + +**v1.14.0.14** *(2018-05-05)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.14/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.14/builds/4chan-X-noupdate.crx)] +- Merge v1.13.15.11: Fix issue with HTTPS Redirect. #1876 +- Merge v1.13.15.11: Update workaround for ad breaking 4chan. +- (saxamaphone69) Support Vocaroo HTTPS embedding. + +**v1.14.0.13** *(2018-05-01)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.13/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.13/builds/4chan-X-noupdate.crx)] +- Fix some bugs in data storage. + +**v1.14.0.12** *(2018-04-01)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.12/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.12/builds/4chan-X-noupdate.crx)] +- Merge v1.13.15.10: Show 2018 Apr 01 team names in thread updates and other posts generated from JSON. + +**v1.14.0.11** *(2018-03-23)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.11/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.11/builds/4chan-X-noupdate.crx)] +- Fix anti-autoplay regression causing some videos not to show up. + +**v1.14.0.10** *(2018-02-20)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.10/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.10/builds/4chan-X-noupdate.crx)] +- Support VidLii embedding. + +**v1.14.0.9** *(2018-02-17)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.9/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.9/builds/4chan-X-noupdate.crx)] +- Revert "Possible workaround for Cloudflare 503 issue. #1746" + +**v1.14.0.8** *(2018-02-17)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.8/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.8/builds/4chan-X-noupdate.crx)] +- Possible workaround for Cloudflare 503 issue. #1746 +- Strawpoll embedding update. + +**v1.14.0.7** *(2018-02-09)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.7/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.7/builds/4chan-X-noupdate.crx)] +- Merge v1.13.15.9: Fix 404 redirection on error pages without doctype. #1811 + +**v1.14.0.6** *(2018-02-04)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.6/builds/4chan-X-noupdate.crx)] +- Fix bug from v1.14.0.0 that broke 404 redirect and other features on image URLs. #1789 + +**v1.14.0.5** *(2018-02-01)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.5/builds/4chan-X-noupdate.crx)] +- Merge branch v1.13.15.8: Captcha bypass cookie does not work for starting threads. Updating to treat this case correctly. + +**v1.14.0.4** *(2018-01-27)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.4/builds/4chan-X-noupdate.crx)] +- Merge v1.13.15.7: Show video contract button unconditionally for now due to changes in Firefox. +- Merge v1.13.15.7: Fix webm_audio undefined error shown on first install of script. #1778 +- Time Formatting whitespace fix for Tinyboard. + +**v1.14.0.3** *(2018-01-25)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.3/builds/4chan-X-noupdate.crx)] +- Merge v1.13.15.6: Bugfix to captcha opening logic. Don't ask user for new captchas when we have a bypass cookie or at least one captcha, even when many posts are queued. +- Merge v1.13.15.6: Do not save captchas to disk or share them between tabs. They are too short-lived to be worth it now. This should reduce associated I/O errors. + +**v1.14.0.2** *(2018-01-24)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.2/builds/4chan-X-noupdate.crx)] +- Small bugfixes for Tinyboard. + +**v1.14.0.1** *(2018-01-24)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.1/builds/4chan-X-noupdate.crx)] +- Fix custom navigation bug from v1.14.0.0 #1774 and older issues #384, #642. + +**v1.14.0.0** *(2018-01-24)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.14.0.0/builds/4chan-X-noupdate.crx)] +- Based on v1.13.15.5. +- Preliminary support for Tinyboard / vichan based imageboards. Only a subset of features are working. To use 4chan X on a site, use the userscript version of 4chan X and add the site the user `@match` rules. In the instructions below, replace "examplechan.com" with the site you want to add: + - Greasemonkey 4: [Not implemented yet](https://github.com/greasemonkey/greasemonkey/issues/2728). Use Violentmonkey or Tampermonkey for now, or edit the script if you are not auto-updating. + - Greasemonkey 3: Go to the "User Scripts" tab of about:addons, find 4chan X, and click "Options". On the "User Settings" tab, click the "Add" button next to "Matched Pages". Enter `https://examplechan.com/*`. + - Violentmonkey: Open the Violentmonkey settings page and find 4chan X. Click the edit button (looks like ``). Go to the "Settings" tab and enter `https://examplechan.com/*` in the "@match rules" field. Click save. + - Tampermonkey: Open the Tampermonkey settings page, go to the "Installed userscripts" tab, and find 4chan X. Click the edit button (pencil on paper). Go to the "Settings" tab and click the "Add" button below "User matches". Enter `https://examplechan.com/*`. + - If you are not auto-updating 4chan X, you can also edit the script, adding `// @match https://examplechan.com/*` (recommended) or `// @include https://examplechan.com/*` (some browsers / script engines may not support `@match`) after the existing `// @include` lines. + +### v1.13.15 + +**v1.13.15.12** *(2018-05-05)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.12/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.12/builds/4chan-X-noupdate.crx)] +- Update workaround for ad breaking 4chan, take two. + +**v1.13.15.11** *(2018-05-05)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.11/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.11/builds/4chan-X-noupdate.crx)] +- Fix issue with HTTPS Redirect. #1876 +- Update workaround for ad breaking 4chan. + +**v1.13.15.10** *(2018-04-01)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.10/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.10/builds/4chan-X-noupdate.crx)] +- Fix anti-autoplay regression causing some videos not to show up. +- Show 2018 Apr 01 team names in thread updates and other posts generated from JSON. + +**v1.13.15.9** *(2018-02-09)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.9/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.9/builds/4chan-X-noupdate.crx)] +- Fix 404 redirection on error pages without doctype. #1811 + +**v1.13.15.8** *(2018-02-01)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.8/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.8/builds/4chan-X-noupdate.crx)] +- Captcha bypass cookie does not work for starting threads. Updating to treat this case correctly. + +**v1.13.15.7** *(2018-01-27)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.7/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.7/builds/4chan-X-noupdate.crx)] +- Show video contract button unconditionally for now due to changes in Firefox. +- Fix webm_audio undefined error shown on first install of script. #1778 + +**v1.13.15.6** *(2018-01-25)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.6/builds/4chan-X-noupdate.crx)] +- Bugfix to captcha opening logic. Don't ask user for new captchas when we have a bypass cookie or at least one captcha, even when many posts are queued. +- Don't save captchas to disk or share them between tabs. They are too short-lived to be worth it now. This should reduce associated I/O errors. + +**v1.13.15.5** *(2018-01-23)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.5/builds/4chan-X-noupdate.crx)] +- Add link in settings to captcha FAQ. + +**v1.13.15.4** *(2018-01-23)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.4/builds/4chan-X-noupdate.crx)] +- Remove Recaptcha v1 options. +- Don't require captcha if cookie is set indicating captcha not needed yet. #1767 +- Revert race condition bugfixes from v1.13.15.0 until I'm sure they're not making things worse. + +**v1.13.15.3** *(2018-01-02)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.3/builds/4chan-X-noupdate.crx)] +- Fix removal of stale cached thread data on index refresh which was broken by updates for GM4. + +**v1.13.15.2** *(2017-12-28)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.2/builds/4chan-X-noupdate.crx)] +- Fix scroll wheel volume adjustment on /r/ and /wsr/. Also read list of boards allowing audio from 4chan's boards.json. +- Minor Fixes to `Disable Autoplaying Sounds`. +- Use HTTPS for catalog.neet.tv if on HTTPS 4chan page. +- (pentargency) Prevent filtering of own posts. + +**v1.13.15.1** *(2017-12-18)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.1/builds/4chan-X-noupdate.crx)] +- Merge v1.13.14.13: Quick workaround for new ad breaking 4chan. + +**v1.13.15.0** *(2017-12-11)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.15.0/builds/4chan-X-noupdate.crx)] +- Based on v1.13.14.12. +- Better protection against race conditions that can lead to data loss. + +### v1.13.14 + +**v1.13.14.13** *(2017-12-18)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.13/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.13/builds/4chan-X-noupdate.crx)] +- Quick workaround for new ad breaking 4chan. + +**v1.13.14.12** *(2017-12-10)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.12/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.12/builds/4chan-X-noupdate.crx)] +- Feedback request. + +**v1.13.14.11** *(2017-12-10)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.11/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.11/builds/4chan-X-noupdate.crx)] +- Fix bug causing Quick Reply errors. #1652 + +**v1.13.14.10** *(2017-11-20)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.10/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.10/builds/4chan-X-noupdate.crx)] +- Attempt to fix navigation keybind issue in Violentmonkey. #1656 + +**v1.13.14.9** *(2017-11-17)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.9/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.9/builds/4chan-X-noupdate.crx)] +- Hard disable 'Force Noscript Captcha for v1' in GM4 for now. +- Work around double loading issue in Greasemonkey 4. #1629 +- Fix 'Open front page' keybind in Tampermonkey. + +**v1.13.14.8** *(2017-11-16)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.8/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.8/builds/4chan-X-noupdate.crx)] +- Yet more Greasemonkey 4 related fixes. + +**v1.13.14.7** *(2017-11-15)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.7/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.7/builds/4chan-X-noupdate.crx)] +- More Greasemonkey 4 related fixes. + +**v1.13.14.6** *(2017-11-05)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.6/builds/4chan-X-noupdate.crx)] +- Turn 'Force Noscript Captcha for v1' off by default in GM4 due to missing frame support. +- Fix bugs related to 4chan's ads. + +**v1.13.14.5** *(2017-10-30)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.5/builds/4chan-X-noupdate.crx)] +- Merge v1.13.13.3: Update for Halloween theme compatibility. + +**v1.13.14.4** *(2017-10-27)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.4/builds/4chan-X-noupdate.crx)] +- Merge v1.13.13.2: Drop now redundant /qa/ message. +- Read /pol/ flags from 4chan API instead of hardcoding them. + +**v1.13.14.3** *(2017-10-23)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.3/builds/4chan-X-noupdate.crx)] +- CSS tweaks to bottom backlinks. + +**v1.13.14.2** *(2017-10-23)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.2/builds/4chan-X-noupdate.crx)] +- Fix bottom backlinks related error. + +**v1.13.14.1** *(2017-10-23)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.1/builds/4chan-X-noupdate.crx)] +- Bugfix: hide OP bottom backlinks in catalog mode. + +**v1.13.14.0** *(2017-10-23)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.14.0/builds/4chan-X-noupdate.crx)] +- Based on v1.13.13.1. +- (saxamaphone69) Implement `Bottom Backlinks` option to place backlinks below the post content rather than above it. #101 + +### v1.13.13 + +**v1.13.13.3** *(2017-10-30)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.13.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.13.3/builds/4chan-X-noupdate.crx)] +- Update for Halloween theme compatibility. + +**v1.13.13.2** *(2017-10-27)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.13.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.13.2/builds/4chan-X-noupdate.crx)] +- Drop now redundant /qa/ message. +- Add Catalonia to /pol/ flags. + +**v1.13.13.1** *(2017-10-18)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.13.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.13.1/builds/4chan-X-noupdate.crx)] +- Fix setting clearing/importing in GM4. #1531 +- Fix issue with new Tampermonkey version on Edge. #1534 + +**v1.13.13.0** *(2017-10-09)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.13.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.13.0/builds/4chan-X-noupdate.crx)] +- Based on v1.13.12.3. +- Experimental support for installing the Chrome extension version in Firefox. +- Minor bugfixes. + +### v1.13.12 + +**v1.13.12.3** *(2017-10-02)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.12.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.12.3/builds/4chan-X-noupdate.crx)] +- Fix QR resizing bug in Chrome. #1516 + +**v1.13.12.2** *(2017-10-02)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.12.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.12.2/builds/4chan-X-noupdate.crx)] +- Workaround for Twitter embed height issues. #1517 + +**v1.13.12.1** *(2017-09-29)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.12.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.12.1/builds/4chan-X-noupdate.crx)] +- Merge v1.13.11.5: Fix lag after settings changes. + +**v1.13.12.0** *(2017-09-28)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.12.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.12.0/builds/4chan-X-noupdate.crx)] +- Based on v1.13.11.4. +- Preliminary support for Greasemonkey 4. +- Minor custom cooldown bugfix. +- (BeltranBot) Fix 'open thread in new tab' keybind for VM/TM + +### v1.13.11 + +**v1.13.11.5** *(2017-09-29)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.5/builds/4chan-X-noupdate.crx)] +- Fix lag after settings changes. + +**v1.13.11.4** *(2017-08-24)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.4/builds/4chan-X-noupdate.crx)] +- Merge v1.13.10.7: Fix quote preview bug when reply is in index data but no thread object exists. #1478 + +**v1.13.11.3** *(2017-08-13)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.3/builds/4chan-X-noupdate.crx)] +- Add language setting for time formatting. + +**v1.13.11.2** *(2017-08-12)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.2/builds/4chan-X-noupdate.crx)] +- Last Long Reply order will now ignore hidden and filtered replies. + +**v1.13.11.1** *(2017-08-10)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.1/builds/4chan-X-noupdate.crx)] +- Merge v1.13.10.6: Disable 'Redirect to HTTPS' on platforms where we use localStorage for saving settings. + +**v1.13.11.0** *(2017-08-08)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.0/builds/4chan-X-noupdate.crx)] +- Based on v1.13.10.5. +- Support [spoiler] and [code] tags in 'Copy Text' menu item. +- Trim quoted text to text fully inside post. #1108 + +### v1.13.10 + +**v1.13.10.7** *(2017-08-24)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.7/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.7/builds/4chan-X-noupdate.crx)] +- Fix quote preview bug when reply is in index data but no thread object exists. #1478 + +**v1.13.10.6** *(2017-08-10)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.6/builds/4chan-X-noupdate.crx)] +- Disable 'Redirect to HTTPS' on platforms where we use localStorage for saving settings. + +**v1.13.10.5** *(2017-08-04)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.5/builds/4chan-X-noupdate.crx)] +- Better parsing of archive links for Quote Inlining / Hover. +- Add Board Tips. + +**v1.13.10.4** *(2017-07-29)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.4/builds/4chan-X-noupdate.crx)] +- Reduce disk reads preformed by QR Cooldown. +- Change the MD5 Quick Filter button from a trash can to an X. + +**v1.13.10.3** *(2017-07-26)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.3/builds/4chan-X-noupdate.crx)] +- Fix double sticky icon bug on /f/. + +**v1.13.10.2** *(2017-07-20)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.2/builds/4chan-X-noupdate.crx)] +- Add an API for adding captchas to 4chan X's cache (`SaveCaptcha` event). + +**v1.13.10.1** *(2017-07-16)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.1/builds/4chan-X-noupdate.crx)] +- Add `Redirect to HTTPS` setting and turn it on by default. #885 +- Turn `Force Noscript Captcha for v1` on by default. +- Add "General" filter category for filters that apply to multiple fields given by `type` option. #1124 +- Various embedding updates and bugfixes, including Link Title support for Clyp. +- (friendlyanon) Add menu item to copy a post's clean text. + +**v1.13.10.0** *(2017-07-11)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.0/builds/4chan-X-noupdate.crx)] +- Based on v1.13.9.6. +- Support adjusting the length thresholds for 'Last Long Reply' order. Thresholds can be set separately for replies with and without images. +- In Last Long Reply order, if no visible reply meets threshold and there are omitted replies, sort by first visible reply. +- When multiple filters hide a post, non-stub filters should override with-stub filters. #1414 +- (rivertam) Add more customizable keybindings for gallery image navigation. + +### v1.13.9 + +**v1.13.9.6** *(2017-06-28)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.9.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.9.6/builds/4chan-X-noupdate.crx)] +- Include link to FAQ entry about [blob: blocking issue](https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions#error-reading-metadata) in warning message when can't read file metadata. #1417 + +**v1.13.9.5** *(2017-06-27)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.9.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.9.5/builds/4chan-X-noupdate.crx)] +- Support /pol/ custom flags in archive-related features. #1403 + +**v1.13.9.4** *(2017-06-13)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.9.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.9.4/builds/4chan-X-noupdate.crx)] +- Update for restoration of custom flags on /pol/. #1403 + +**v1.13.9.3** *(2017-05-14)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.9.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.9.3/builds/4chan-X-noupdate.crx)] +- Merge v1.13.8.7: Fix MathJax on /sci/. #1356 +- Merge v1.13.8.7: Minor fixes for new board /bant/. +- Restore 'Use Recaptcha v1 in Reports' functionality when reporting from native catalog. #1346 +- Fix noscript captcha in original post form on /f/. +- (mahkoh) Hide related videos when a youtube video is paused. + +**v1.13.9.2** *(2017-04-26)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.9.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.9.2/builds/4chan-X-noupdate.crx)] +- Workaround for issues with cooldown timer and other things starting in Firefox 53. #1323 + +**v1.13.9.1** *(2017-04-23)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.9.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.9.1/builds/4chan-X-noupdate.crx)] +- Fix captcha cleanup. #1341 + +**v1.13.9.0** *(2017-04-16)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.9.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.9.0/builds/4chan-X-noupdate.crx)] +- Based on v1.13.8.5. +- Restore support for noscript fallback version of Recaptcha v1. Can be activated through new `Force Noscript Captcha for v1` option. Only working on HTTPS currently. If used, this will validate captchas before posting. +- `Use Recaptcha v1` will no longer replace the captcha in the original post form. +- Make possible filtering threads without subject. #1328 +- (saxamaphone69) Small CSS fixes. #1326 + +### v1.13.8 + +**v1.13.8.8** *(2017-06-13)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.8.8/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.8.8/builds/4chan-X-noupdate.crx)] +- Update for restoration of custom flags on /pol/. #1403 + +**v1.13.8.7** *(2017-05-13)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.8.7/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.8.7/builds/4chan-X-noupdate.crx)] +- Fix MathJax on /sci/. #1356 +- Minor fixes for new board /bant/. + +**v1.13.8.6** *(2017-04-26)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.8.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.8.6/builds/4chan-X-noupdate.crx)] +- Workaround for issues with cooldown timer and other things starting in Firefox 53. #1323 +- Fix captcha cleanup. #1341 + +**v1.13.8.5** *(2017-03-13)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.8.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.8.5/builds/4chan-X-noupdate.crx)] +- WebM with audio is now allowed on /wsr/ and /r/. #1319 +- Minor bugfixes. + +**v1.13.8.4** *(2017-02-21)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.8.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.8.4/builds/4chan-X-noupdate.crx)] +- As a workaround for 4chan's recent removal of the ability to start new threads using the v1 (text) Recaptcha, the `Use Recaptcha v1` option now only applies within threads. You can enable the new option `Use Recaptcha v1 on Index` to get Recaptcha v1 in the index and catalog, but unless 4chan's change is reverted, this will interfere with starting threads. + +**v1.13.8.3** *(2017-02-11)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.8.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.8.3/builds/4chan-X-noupdate.crx)] +- Make posts from archives with files deleted (by archive) show as "File Deleted". #1287 + +**v1.13.8.2** *(2017-02-11)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.8.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.8.2/builds/4chan-X-noupdate.crx)] +- Update for recent site changes. Fixes quote preview on archive page. + +**v1.13.8.1** *(2017-02-10)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.8.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.8.1/builds/4chan-X-noupdate.crx)] +- Fix bug from v1.13.8.0 causing v1 captcha sometimes not to reload when needed. + +**v1.13.8.0** *(2017-02-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.8.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.8.0/builds/4chan-X-noupdate.crx)] +- Based on v1.13.7.2. +- Retry posting on connection errors using the same captcha. +- Don't autohide QR while uploading is in progress. (#222) Also, `Auto Hide QR` is now a suboption of `Persistent QR`. +- Various minor captcha-related bugfixes and improvements. +- Sauce link optimizations and bugfixes. +- Move You checkbox down in menu (beneath Archive). #1277 +- Turn `Download Link` off by default in new installs. #1222 + +### v1.13.7 + +**v1.13.7.2** *(2017-02-06)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.7.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.7.2/builds/4chan-X-noupdate.crx)] +- Add `Require OP Quote Link` option (off by default in new installs) to Thread Watcher menu: For purposes of thread watcher highlighting, only consider posts with a quote link to the OP as replies to the OP. +- Turn on `Require OP Quote Link` for upgrading users as it is the old behavior. + +**v1.13.7.1** *(2017-02-02)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.7.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.7.1/builds/4chan-X-noupdate.crx)] +- Merge v1.13.5.3: Update for Recaptcha changes. + +**v1.13.7.0** *(2017-02-01)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.7.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.7.0/builds/4chan-X-noupdate.crx)] +- Based on v1.13.6.1. +- Fix scroll bars sometimes appearing on noscript captcha in QR. + +### v1.13.6 + +**v1.13.6.1** *(2017-01-31)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.6.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.6.1/builds/4chan-X-noupdate.crx)] +- Add CSS class `toggle-you` to menu entry for marking posts as yours. + +**v1.13.6.0** *(2017-01-30)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.6.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.6.0/builds/4chan-X-noupdate.crx)] +- Based on v1.13.5.2. +- Add item in the post menu to mark/unmark posts as yours. #195 +- When you are the OP of a thread, any unread reply to the thread will now light up the `Thread Watcher` icon, not just replies with quote links to you. #913 +- Show `##Manager`, `##Founder`, and `##Verified` capcodes in posts loaded from the archives. Also support searching for them from the post menu. +- Make `Anonymize` more efficient, and extend it to the /f/ index and native catalog. #1111 +- If `Quote Preview` is enabled, links to threads in the internal archive will show previews of the OP on hover, as in the native extension. #1256 +- If we detect the QR paste icon isn't needed, hide it instead of disabling it so it can be brought back with CSS if necessary. +- Don't show archive report form on the "Report submitted!" page. + +### v1.13.5 + +**v1.13.5.3** *(2017-02-02)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.5.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.5.3/builds/4chan-X-noupdate.crx)] +- Update for Recaptcha changes. + +**v1.13.5.2** *(2017-01-27)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.5.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.5.2/builds/4chan-X-noupdate.crx)] +- Improvements to Sauce settings panel. +- Minor bugfixes. + +**v1.13.5.1** *(2017-01-25)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.5.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.5.1/builds/4chan-X-noupdate.crx)] +- Fix appearance of the new ##Verified capcode in posts added by updater and cross-thread quote previews. + +**v1.13.5.0** *(2017-01-24)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.5.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.5.0/builds/4chan-X-noupdate.crx)] +- Based on v1.13.4.1. +- Bring back `Archive Report` feature, now with reporting to multiple archives through the offsite reports API of https://github.com/pleebe/foolfuuka-plugin-popup-report. #1260 +- Add filename regular expression matching to Sauce. Sauce will now recognize Pixiv, DeviantArt, Imgur, Flickr, and Facebook filenames and link to the page the image came from. #1183 +- Update regex.info -> exif.regex.info in Sauce links. +- Parameters in Sauce links will only be expanded in the URL and displayed text. +- Posts fetched from an archive now have their file links point to the archive chosen by the user for file redirection, rather than the URL given by the archive. #1255 +- Minor bugfixes. + +### v1.13.4 + +**v1.13.4.1** *(2017-01-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.4.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.4.1/builds/4chan-X-noupdate.crx)] +- Revert Data Saver workarounds. They didn't work. This release is the same as v1.13.3.0 except for the version number. #1241 + +**v1.13.4.0** *(2017-01-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.4.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.4.0/builds/4chan-X-noupdate.crx)] +- Based on v1.13.3.0. +- Tell Chrome Data Saver not to convert images to WebP when posting images from URLs. #1241 + +### v1.13.3 + +**v1.13.3.0** *(2017-01-06)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.3.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.3.0/builds/4chan-X-noupdate.crx)] +- Based on v1.13.2.4. +- Full support for new is2.4chan.org host. +- Use is.4chan.org for building posts if `Use Faster Image Host` is off. + +### v1.13.2 + +**v1.13.2.4** *(2017-01-05)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.2.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.2.4/builds/4chan-X-noupdate.crx)] +- Change is2.4chan.org image links to i.4cdn.org also. + +**v1.13.2.3** *(2016-12-21)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.2.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.2.3/builds/4chan-X-noupdate.crx)] +- Update for new Recaptcha URL. Restores image selection by keyboard. #1234 + +**v1.13.2.2** *(2016-11-29)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.2.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.2.2/builds/4chan-X-noupdate.crx)] +- Drop support for is.4chan.org in posts generated from JSON for now. (This only affects users with `Use Faster Image Host` off.) + +**v1.13.2.1** *(2016-11-27)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.2.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.2.1/builds/4chan-X-noupdate.crx)] +- Fix bug from v1.13.2.0 causing errors when non-embeddable link is in inlined/previewed quote. + +**v1.13.2.0** *(2016-11-27)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.2.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.2.0/builds/4chan-X-noupdate.crx)] +- Based on v1.13.1.12. +- (ihavenoface) Add `Cover Preview` function: Show preview of supported links on hover. + - Currently supported: Youtube and Dailymotion. +- (ihavenoface) Keep floating embeds visible while moving the window. +- Various embedding-related bugfixes, including performance issue from v1.13.0.0 when switching to catalog. +- Make floating updater draggable by any edge so it doesn't get stuck at the top. #1031 + +### v1.13.1 + +**v1.13.1.12** *(2016-11-27)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.12/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.12/builds/4chan-X-noupdate.crx)] +- Fix race condition bug from v1.13.0.0 causing 'Watch thread' item to sometimes not appear in header menu. +- Turn `Auto Prune` in Thread Watcher options back off by default. + +**v1.13.1.11** *(2016-11-26)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.11/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.11/builds/4chan-X-noupdate.crx)] +- Revert performance issue fix from v1.13.1.10 as it may cause more issues than it solves. + +**v1.13.1.10** *(2016-11-26)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.10/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.10/builds/4chan-X-noupdate.crx)] +- Fix bug from v1.13.0.0 causing `Auto-embed` to sometimes not work. +- Fix performance issue from v1.13.0.0 due to all link embeds in OPs being reloaded upon switching to catalog mode. +- Include Yandex in default sauce links. + +**v1.13.1.9** *(2016-11-20)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.9/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.9/builds/4chan-X-noupdate.crx)] +- Change replies-quoting-you exclamation mark from red to green on dead-thread icon in 4chanJS set. + +**v1.13.1.8** *(2016-11-19)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.8/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.8/builds/4chan-X-noupdate.crx)] +- Rearrange some options. + +**v1.13.1.7** *(2016-11-18)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.7/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.7/builds/4chan-X-noupdate.crx)] +- Fix in-comment links to is.4chan.org. + +**v1.13.1.6** *(2016-11-14)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.6/builds/4chan-X-noupdate.crx)] +- (desaku) Update 4chanJS favicons to reflect the native extension. #1038 +- Fix reply-to-you favicons in `xat-` set looking blurry. +- Show all icons in Favicon settings. #1191 + +**v1.13.1.5** *(2016-11-13)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.5/builds/4chan-X-noupdate.crx)] +- Merge v1.13.0.25: Fix bug from v1.13.0.0 causing errors on index refresh in certain cases when creating threads with cookies disabled. #1184 +- Merge v1.13.0.25: Better link text in file error message: 'delete' -> 'delete post'. #1186 +- Merge v1.13.0.25: Fix bug causing auto-pruning if you refreshed the index too soon after creating a thread. +- Add `Catalog Hover Toggle` setting, which sets whether clicking in the catalog toggles `Catalog Hover Expand`. +- Improved support for those who want to unblock the top banner ads. See the [FAQ](https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions#ads) for how to do so. + +**v1.13.1.4** *(2016-11-09)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.4/builds/4chan-X-noupdate.crx)] +- Fix deletion cooldown bug from v1.13.1.0. + +**v1.13.1.3** *(2016-11-09)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.3/builds/4chan-X-noupdate.crx)] +- Merge v1.13.0.24: Fix bug from v1.13.0.0 causing lack of scroll bar when `Fit width` is disabled and images overflow screen. + +**v1.13.1.2** *(2016-11-08)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.2/builds/4chan-X-noupdate.crx)] +- Merge v1.13.0.23: Fix bug from v1.13.0.0 affecting the catalog sorting order of recently hidden/shown threads. +- Merge v1.13.0.23: Cosmetic fixes for bottom ad changes. +- Reply Pruning is no longer activated by default except in stickies. Added `Prune All Threads` option (default: false) to activate Reply Pruning by default in all threads. +- Reply Pruning will no longer be deactivated by `Scroll to Last Read Post` in order to unhide the last read post, or by following links to the OP. +- Add option for quick MD5-filtering button (`%f`) to File Info Formatting (Advanced settings tab). +- Add keybind for filtering image MD5s (default: `5`). +- (ihavenoface) Add Bing reverse image search to Sauce examples. +- Various minor bugfixes. + +**v1.13.1.1** *(2016-11-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.1/builds/4chan-X-noupdate.crx)] +- Fix bug causing replies to not immediately show when catalog thread is clicked. + +**v1.13.1.0** *(2016-11-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.1.0/builds/4chan-X-noupdate.crx)] +- Based on v1.13.0.22. +- Only activate `Catalog Hover Expand` when catalog is clicked. Deactivate on second click. Turn `Catalog Hover Expand` back on by default. +- (Koushien) Add checkbox after Index dropdowns to reverse the sort order of the index. Also: + - Let custom board navigation accept "rev" option as part of the sort option, e.g. `g-sort:"creation date rev"`. + - Support hash commands of the form `#bump-order-rev` to open the index with reverse sorting on. +- Prevent auto-posting when editing any part of the first post in the last 5 seconds of the cooldown, not just when editing the comment. +- Make rewriting of is.4chan.org links to i.4cdn.org optional (`Use Faster Image Host`, default: true). +- Include rolls and fortunes in filterable text but continue removing them from notifications and thread excerpts. +- Various minor bugfixes. + +## v1.13.0 + +**v1.13.0.25** *(2016-11-13)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.25/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.25/builds/4chan-X-noupdate.crx)] +- Fix bug from v1.13.0.0 causing errors on index refresh in certain cases when creating threads with cookies disabled. #1184 +- Better link text in file error message: 'delete' -> 'delete post'. #1186 +- Fix bug causing auto-pruning if you refreshed the index too soon after creating a thread. + +**v1.13.0.24** *(2016-11-09)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.24/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.24/builds/4chan-X-noupdate.crx)] +- Fix bug from v1.13.0.0 causing lack of scroll bar when `Fit width` is disabled and images overflow screen. + +**v1.13.0.23** *(2016-11-08)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.23/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.23/builds/4chan-X-noupdate.crx)] +- Fix bug from v1.13.0.0 affecting the catalog sorting order of recently hidden/shown threads. +- Cosmetic fixes for bottom ad changes. + +**v1.13.0.22** *(2016-11-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.22/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.22/builds/4chan-X-noupdate.crx)] +- Turn `Catalog Hover Expand` off by default for now. +- Adjust catalog CSS; opt for more entries displayed as it was previously. + +**v1.13.0.21** *(2016-11-06)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.21/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.21/builds/4chan-X-noupdate.crx)] +- Prevent hovered catalog threads from going offscreen if the extra padding on `.board` is removed. + +**v1.13.0.20** *(2016-11-05)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.20/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.20/builds/4chan-X-noupdate.crx)] +- Bugfix: Don't add embedding window to error pages. +- Hide EXIF data in /p/ catalog except on hover. + +**v1.13.0.19** *(2016-10-31)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.19/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.19/builds/4chan-X-noupdate.crx)] +- Bring back CSS tweaks for Halloween theme. + +**v1.13.0.18** *(2016-10-31)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.18/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.18/builds/4chan-X-noupdate.crx)] +- Improve robustness against invalid settings data, including thread watcher timestamps from future. + +**v1.13.0.17** *(2016-10-30)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.17/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.17/builds/4chan-X-noupdate.crx)] +- Various regression and bug fixes. + +**v1.13.0.16** *(2016-10-27)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.16/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.16/builds/4chan-X-noupdate.crx)] +- Merge v1.12.3.11: Update due to 4chan's ad changes, part two. + +**v1.13.0.15** *(2016-10-27)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.15/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.15/builds/4chan-X-noupdate.crx)] +- Merge v1.12.3.10: Update due to 4chan's ad changes. + +**v1.13.0.14** *(2016-10-23)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.14/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.14/builds/4chan-X-noupdate.crx)] +- Fix bug from v1.13.0.3 causing index/catalog search to not search comment. + +**v1.13.0.13** *(2016-10-15)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.13/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.13/builds/4chan-X-noupdate.crx)] +- Add styling guide link to custom CSS section. +- Add basic support for is.4chan.org domain. + +**v1.13.0.12** *(2016-10-14)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.12/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.12/builds/4chan-X-noupdate.crx)] +- Switch back to using `border` rather than `outline` for highlighting watched threads in catalog. +- Restore hiding of extra linebreaks in catalog. Show them at reduced height on hover. + +**v1.13.0.11** *(2016-10-13)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.11/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.11/builds/4chan-X-noupdate.crx)] +- Merge v1.12.3.8: Don't run 4chan X on the donation or advertisement purchase pages (it already didn't run on the pass purchase page). + +**v1.13.0.10** *(2016-10-09)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.10/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.10/builds/4chan-X-noupdate.crx)] +- Merge v1.12.3.7: Workaround for problem on 4chan's end with images not loading. + +**v1.13.0.9** *(2016-10-09)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.9/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.9/builds/4chan-X-noupdate.crx)] +- Fix bug from v1.13.0.0 that broke Quote Inlining / Previewing of OPs on /f/. + +**v1.13.0.8** *(2016-10-08)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.8/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.8/builds/4chan-X-noupdate.crx)] +- (AchtBit) Add keybind for toggling custom cooldown (default: `Alt+Comma`). +- Add keybind to post file from URL (default: `Alt+l`). +- Add keybind for adding new post to QR dump list (default: `Alt+n`). + +**v1.13.0.7** *(2016-10-05)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.7/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.7/builds/4chan-X-noupdate.crx)] +- Fix filenames not being properly unescaped in catalog replies. + +**v1.13.0.6** *(2016-10-05)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.6/builds/4chan-X-noupdate.crx)] +- Fix bug from v1.13.0.3 causing threads to be incorrectly pruned / marked dead in Thread Watcher. + +**v1.13.0.5** *(2016-10-04)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.5/builds/4chan-X-noupdate.crx)] +- (dzamie) `sfw` and `nsfw` can be used in the `boards` options of filters. +- Mirror most of `Index Navigation` menu settings in main settings panel. +- Fix a hidden threads related bug from v1.13.0.4. + +**v1.13.0.4** *(2016-10-03)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.4/builds/4chan-X-noupdate.crx)] +- Performance work and bug fixes related to recent catalog changes. +- Don't show excerpts of hidden or filtered replies in the catalog. + +**v1.13.0.3** *(2016-10-02)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.3/builds/4chan-X-noupdate.crx)] +- Add `Pass Date` to filterable items. +- Show Pass flair in posts constructed from JSON. +- Anonymize will now hide Pass flair. +- Change reply cooldowns to their reduced values for Pass users. +- Make the JSON index load quicker. + +**v1.13.0.2** *(2016-10-01)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.2/builds/4chan-X-noupdate.crx)] +- Party hat CSS tweaks. + +**v1.13.0.1** *(2016-10-01)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.1/builds/4chan-X-noupdate.crx)] +- Merge v1.12.3.5: Fix cooldowns. +- Merge v1.12.3.5: Fix party hat alignment when Thread Hiding Buttons are enabled. + +**v1.13.0.0** *(2016-09-29)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.0.0/builds/4chan-X-noupdate.crx)] +- Based on v1.12.3.4. +- Major rework of the 4chan X catalog: + - Hovering over a thread in the catalog now shows the full comment, as well as the poster name, flag (if available), post date, file info, and if `Show replies` is checked in the `Index Navigation` submenu in the header, excerpts of the last few replies along with how long ago they were made. The full reply can be shown by hovering over the "..." at the end of the excerpt. This can all be disabled by unchecking `Catalog hover expand`, also under `Index Navigation` in the header menu. + - The `Quote Preview`, `Resurrect Quotes` (from the archives), `Mark Quotes of You`, `Embedding` (via floating embeds), and `Gallery` features now work in the catalog, and the `File Info Formatting`, `Time Formatting`, `Relative Post Dates`, and `Relative Date Title` settings apply to the extra details shown on hover. + - The appearance of the catalog when `Werk Tyme` (thumbnail hiding) is enabled has been redesigned. + - HTML-wise, the posts shown in the catalog are now normal posts that have been restyled, and to which `.catalog-link`, `.catalog-stats`, and `.catalog-replies` elements have been added. The class names on the post and its immediate ancestors are `post catalog-post`, `postContainer catalog-container`, and `thread catalog-thread` (only the last of which is a newly created element for the catalog). You may need to update your custom CSS due to the changes. +- The default `File Info Formatting` setting has been changed to "%L %d (%p%s, %r%g)". It now includes a download button by default (the "%d"). +- The `(You)`, `(OP)`, `(Cross-thread)` and `(Dead)` quote markers are now inside `` elements with the CSS classes `qmark-you`, `qmark-op`, `qmark-ct`, and `qmark-dead`, respectively, making it possible to style them. +- 4chan's default strikethrough is only removed from dead quotelinks if `Resurrect Quotes` is enabled. +- The thread watcher header icon, which lights up when you are replied to, will no longer go out when the thread watcher is opened. +- The index is no longer immedately resorted when `Pin watched threads` is enabled and you watch/unwatch a thread, and is only resorted when you refresh. The catalog is still immediately resorted. +- The `Persistent Thread Watcher` loads a little quicker. +- Very long filenames in posts now wrap properly, at least in Webkit-based browsers, and possibly at a later date in others. +- The error messages displayed when the Chrome extension updates but the tab has not been reloaded have been replaced with a single warning message asking you to reload the tab. +- API change: The conditions under which the `IndexRefresh` event is fired have changed to better serve the needs of clients (particularly Name Sync, which is the only extension I know of using this). It now fires whenever new posts are added to the index (including the initial ones, and including posts in the non-JSON index), and always fires after `4chanXInitFinished`. + +### v1.12.3 + +**v1.12.3.11** *(2016-10-27)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.11/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.11/builds/4chan-X-noupdate.crx)] +- Update due to 4chan's ad changes, part two. + +**v1.12.3.10** *(2016-10-27)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.10/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.10/builds/4chan-X-noupdate.crx)] +- Update due to 4chan's ad changes. + +**v1.12.3.9** *(2016-10-18)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.9/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.9/builds/4chan-X-noupdate.crx)] +- Add basic support for is.4chan.org domain. + +**v1.12.3.8** *(2016-10-13)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.8/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.8/builds/4chan-X-noupdate.crx)] +- Don't run 4chan X on the donation or advertisement purchase pages (it already didn't run on the pass purchase page). + +**v1.12.3.7** *(2016-10-09)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.7/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.7/builds/4chan-X-noupdate.crx)] +- Workaround for problem on 4chan's end with images not loading. +- The error messages displayed when the Chrome extension updates but the tab has not been reloaded have been replaced with a single warning message asking you to reload the tab. + +**v1.12.3.6** *(2016-10-03)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.6/builds/4chan-X-noupdate.crx)] +- Add `Pass Date` to filterable items. +- Show Pass flair in posts constructed from JSON. +- Anonymize will now hide Pass flair. +- Change reply cooldowns to their reduced values for Pass users. + +**v1.12.3.5** *(2016-10-01)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.5/builds/4chan-X-noupdate.crx)] +- Fix cooldowns. +- Fix party hat alignment when Thread Hiding Buttons are enabled. + +**v1.12.3.4** *(2016-09-28)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.4/builds/4chan-X-noupdate.crx)] +- Let board banners show. You may need to add the an exception to your ad blocker's cosmetic filters: `boards.4chan.org#@#.middlead` If you do want to hide them, you can add `.middlead {display: none;}` to custom CSS. + +**v1.12.3.3** *(2016-09-25)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.3/builds/4chan-X-noupdate.crx)] +- Fix `Loop in New Tab` and `Volume in New Tab` which were broken by a regression in v1.12.1.3. + +**v1.12.3.2** *(2016-09-18)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.2/builds/4chan-X-noupdate.crx)] +- Fix Thread Watcher icon not lighting up when `Persistent Thread Watcher` is enabled. +- Fix TeX sometimes not working in /sci/ catalog. + +**v1.12.3.1** *(2016-09-16)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.1/builds/4chan-X-noupdate.crx)] +- Fix scrollbar in captcha bug. + +**v1.12.3.0** *(2016-09-12)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.3.0/builds/4chan-X-noupdate.crx)] +- Based on v1.12.2.1. +- Support comments in `Javascript Whitelist`. +- `Image Hover in Catalog`, `Auto Watch`, `Auto Watch Reply`, and `Auto Prune` are now on by default in new installs. +- Various bug fixes. + +### v1.12.2 + +**v1.12.2.1** *(2016-07-11)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.2.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.2.1/builds/4chan-X-noupdate.crx)] +- Fix frequent connection errors from timeout fetching board configuration. + +**v1.12.2.0** *(2016-07-10)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.2.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.2.0/builds/4chan-X-noupdate.crx)] +- Based on v1.12.1.5. +- Support image pasting in Firefox 50+ without selecting paste icon in Quick Reply. +- Improve 4chan X's ability to deal with 4chan changes without a script update. + +### v1.12.1 + +**v1.12.1.5** *(2016-07-10)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.1.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.1.5/builds/4chan-X-noupdate.crx)] +- Restore `Remove Thread Excerpt` option. + +**v1.12.1.4** *(2016-07-09)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.1.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.1.4/builds/4chan-X-noupdate.crx)] +- Re-fix scrolling on space in captcha in Chromium-based browsers. + +**v1.12.1.3** *(2016-07-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.1.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.1.3/builds/4chan-X-noupdate.crx)] +- Fix error message in MathJax popups in Firefox. + +**v1.12.1.2** *(2016-07-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.1.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.1.2/builds/4chan-X-noupdate.crx)] +- Merge v1.12.0.9: Partially revert removal of workarounds for old Chromium versions. + +**v1.12.1.1** *(2016-07-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.1.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.1.1/builds/4chan-X-noupdate.crx)] +- Merge v1.12.0.8: Restore `Restart when Opened` option. + +**v1.12.1.0** *(2016-07-06)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.1.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.1.0/builds/4chan-X-noupdate.crx)] +- Based on v1.12.0.7. +- (dzamie) Replace `Toggleable Thread Watcher` setting with `Persistent Thread Watcher` setting (off by default). With `Persistent Thread Watcher` on, the thread watcher is shown by default, but can still be hidden. +- (dzamie) Add `Toggle thread watcher` keybind (default: "t"). +- Make 'all websites' permission optional for Chrome extension. + ## v1.12.0 +**v1.12.0.9** *(2016-07-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.9/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.9/builds/4chan-X-noupdate.crx)] +- Partially revert removal of workarounds for old Chromium versions. + +**v1.12.0.8** *(2016-07-07)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.8/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.8/builds/4chan-X-noupdate.crx)] +- Restore `Restart when Opened` option. + +**v1.12.0.7** *(2016-07-06)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.7/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.7/builds/4chan-X-noupdate.crx)] +- Restore `Open Post in New Tab` option. + +**v1.12.0.6** *(2016-06-28)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.6/builds/4chan-X-noupdate.crx)] +- Merge v1.11.35.9: Update archive list, and update cuckchan.org -> desuarchive.org in sauce examples. + +**v1.12.0.5** *(2016-06-25)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.5/builds/4chan-X-noupdate.crx)] +- Merge v1.11.35.8: Update desustorage.org -> cuckchan.org in sauce examples. + +**v1.12.0.4** *(2016-06-25)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.4/builds/4chan-X-noupdate.crx)] +- Update default archive list. + +**v1.12.0.3** *(2016-06-23)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.3/builds/4chan-X-noupdate.crx)] +- Merge v1.11.35.7: Fix banner contest form not showing up with JS disabled. + +**v1.12.0.2** *(2016-06-20)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.2/builds/4chan-X-noupdate.crx)] +- Fix issue with disabling native extension on Pale Moon with cookies disabled on 4chan. + +**v1.12.0.1** *(2016-06-19)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.1/builds/4chan-X-noupdate.crx)] +- Implement `Count Posts by ID`. + **v1.12.0.0** *(2016-06-19)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.12.0.0/builds/4chan-X-noupdate.crx)] - Based on v1.11.35.6. - Some workarounds for old browsers have been dropped. Those using versions older than Chromium/Chrome 38, Maxthon 4.9, or SeaMonkey 2.35 may need to upgrade for 4chan X to work. -- Some obsolete or not-often-used options have been removed: +- Some obsolete or not-often-used options have been removed. Please speak up if you want any of these options restored by [opening an issue](https://github.com/ccd0/4chan-x/issues). - `Archive Report`, `Restart when Opened`, `Show Name and Subject`, `Thread Excerpt`, and `Remove Thread Excerpt` have been removed. - The options to disable `QR Shortcut`, `Bottom QR Link`, and `Open Post in New Tab` have been removed. - The `disabled` option in the archive choices section of the Advanced settings have been removed. A better way to disable access to a particular archive is to add (using 4plebs as an example) `{"uid": 3, "boards": []}` to the `Archive Lists` setting. @@ -24,10 +1359,19 @@ - The captcha complaint links have been removed. - The options `Exempt Archives from Encryption` and `Show New Thread Option in Threads` will be enabled by default in new installs. -**Note**: 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 (currently ccd0). (This practice was abandoned starting in v1.12.0; in general it's better to check the git logs.) - ### v1.11.35 +**v1.11.35.9** *(2016-06-28)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.35.9/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.35.9/builds/4chan-X-noupdate.crx)] +- Update default archive list. +- Update cuckchan.org -> desuarchive.org in sauce examples. + +**v1.11.35.8** *(2016-06-25)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.35.8/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.35.8/builds/4chan-X-noupdate.crx)] +- Update default archive list. +- Update desustorage.org -> cuckchan.org in sauce examples. + +**v1.11.35.7** *(2016-06-23)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.35.7/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.35.7/builds/4chan-X-noupdate.crx)] +- Fix banner contest form not showing up with JS disabled. + **v1.11.35.6** *(2016-06-18)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.35.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.35.6/builds/4chan-X-noupdate.crx)] - Use backslashes instead of concatenation for multiline strings. Fixes issue causing script to stop working for some users in Chrome 53. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2ea99dac10..eab6e8e915 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,24 +2,26 @@ Bug reports and feature requests for 4chan X are tracked at **https://github.com/ccd0/4chan-x/issues?q=is%3Aopen+sort%3Aupdated-desc**. -You can submit a bug report / feature request either via your Github account or the [anonymous report form](https://gitreports.com/issue/ccd0/4chan-x). +You can submit a bug report / feature request via your Github account. If you're reporting a bug, the more detail you can give, the better. If I can't reproduce your bug, I probably won't be able to fix it. You can help by doing the following: 1. Include precise steps to reproduce the problem, with the expected and actual results. -2. **Please mention any other extensions / scripts you are using.** To check if a bug is due to a conflict with another extension, temporarily disable any other extensions and userscripts. If the bug goes away, turn them back on one by one until you find the one causing the problem. -3. Make sure your **browser**, **4chan X**, and (if applicable) **Greasemonkey** are up to date. Include the versions you're using in bug reports. -4. Test if the bug occurs with 4chan X disabled and using the native extension. If it does, it's likely a problem with 4chan or your browser rather than with 4chan X. -5. Open your console with Shift+Control+J (⇧⌘J on OS X Firefox, ⌘⌥J on OS X Chromium), and look for any error messages, especially ones that occur at the same time as the bug. Include these in your bug report. If you're using Firefox, be sure to check the browser console (Shift+Control+J), not the web console (Shift+Control+K) as errors may not show up in the latter. +2. Make sure your browser, 4chan X, and userscript manager (e.g. Greasemonkey, ViolentMoney, or Tampermonkey) are up to date. **Include the versions you're using in bug reports.** +3. Open your console with Shift+Control+J (⇧⌘J on OS X Firefox, ⌘⌥J on OS X Chromium), and **look for error messages**, especially ones that occur at the same time as the bug. Include these in your bug report. If you're using Firefox, be sure to check the browser console (Shift+Control+J), not just the web console (Shift+Control+K) as errors may not show up in the latter. Messages about "Content Security Policy" are expected and can be ignored. +4. If other people (including me) aren't having your problem, **test whether it happens in a fresh profile**. Here are instructions for [Firefox](https://support.mozilla.org/en-US/kb/profile-manager-create-and-remove-firefox-profiles) and [Chromium](https://developer.chrome.com/devtools/docs/clean-testing-environment). +5. **Please mention any other extensions / scripts you are using.** To check if a bug is due to a conflict with another extension, temporarily disable any other extensions and userscripts. If the bug goes away, turn them back on one by one until you find the one causing the problem. 6. To test if the bug occurs under the default settings or only with specific settings, back up your settings and reset them using the **Export** and **Reset Settings** links in the settings panel. If the bug only occurs under specific settings, upload your exported settings to a site like https://paste.installgentoo.com/, and link to it in your bug report. If your settings contains sensitive information (e.g. personas), edit the text file manually. +7. Test if the bug occurs using the **native extension** with 4chan X disabled. If it does, it's likely a problem with 4chan or your browser rather than with 4chan X. ## Development & Contribution ### Get started -- Install [git](https://git-scm.com/), [node.js](https://nodejs.org/), and [npm](https://www.npmjs.com/) (usually distributed with node), and GNU Make (on Windows, the [MinGW](http://www.mingw.org/) port will work). +- Install [git](https://git-scm.com/), [node.js](https://nodejs.org/), [npm](https://www.npmjs.com/) (usually distributed with node), and GNU Make (on Windows, the [MinGW](http://www.mingw.org/) port will work, and the [GnuWin](http://gnuwin32.sourceforge.net/) port has been reported to work as well). - Clone 4chan X: `git clone https://github.com/ccd0/4chan-x.git`
(If this is taking too long, you can add `--depth 100` to fetch only recent history.) - Open the directory: `cd 4chan-x` +- Fetch needed dependencies with: `npm install` ### Build @@ -29,16 +31,18 @@ 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. - Edit the sources in the src/ directory (not the compiled scripts in builds/). -- Compile the script with: `grunt` +- Fetch needed dependencies with: `npm install` +- Compile the script with: `make` - 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:
`git config user.name yourname`
`git config user.email youremail` - Commit your changes: `git commit -a` - Open a pull request by doing any of the following: - Fork this repository on Github, push your changes to your fork, and make a pull request through the Github website. - - Push your changes to any online Git repository, and [open an issue](https://gitreports.com/issue/ccd0/4chan-x) with an explanation of your changes and the URL, branch, and commit you want me to pull from. - - Export your changes via `git bundle` (e.g. `git bundle create file.bundle master..your-branch`), and upload them to a file host like https://jii.moe/. Then [open an issue](https://gitreports.com/issue/ccd0/4chan-x) with an explanation of your changes and the URL of the file. + - Push your changes to any online Git repository, and send an email with an explanation of your changes and the URL, branch, and commit you want me to pull from. + - Export your changes via `git bundle` (e.g. `git bundle create file.bundle master..your-branch`), and upload them to a file host. Then send an email with an explanation of your changes and the URL of the file. -Archive list updates should go to https://github.com/MayhemYDG/archives.json. +Pull requests to archive.json should be sent upstream: https://github.com/4chenz/archives.json +4chan X updates from there automatically. ### More info diff --git a/Makefile b/Makefile index bd4bce0eba..01f95ce95b 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ ifdef ComSpec BIN := $(subst /,\,node_modules/.bin/) RMDIR := -rmdir /s /q RM := -del - CAT = type $(subst /,\,$1) > $(subst /,\,$2) + CAT = type $(subst /,\,$1) > $(subst /,\,$2) 2>NUL MKDIR = -mkdir $(subst /,\,$@) QUOTE = $(patsubst %,"%",$1) else @@ -15,24 +15,23 @@ else endif CP = $(call CAT,$<,$@) -npgoals := clean cleanrel cleanweb cleanfull withtests wrapped $(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))" "" .NOTPARALLEL : endif coffee := $(BIN)coffee -c --no-header -coffee_deps := node_modules/coffee-script/package.json 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)) # must be read in when needed to prevent out-of-date version version = $(shell node -p "JSON.parse(require('fs').readFileSync('version.json')).version") source_directories := \ - globals config css platform classes \ + globals config css platform classes site \ Archive Filtering General Images Linkification \ Menu Miscellaneous Monitoring Posting Quotelinks \ main @@ -46,16 +45,14 @@ sources := $(foreach d,$(source_directories),$(call sort_directory,$(d))) uses_tests_enabled := \ src/classes/Post.coffee \ - src/General/Build.Test.coffee \ + src/General/Test.coffee \ src/Linkification/Linkify.coffee \ - src/Miscellaneous/Keybinds.coffee \ - src/Monitoring/Unread.coffee \ src/main/Main.coffee imports_src/globals/globals.js := \ version.json imports_src/css/CSS.js := \ - node_modules/font-awesome/package.json + node_modules/font-awesome/fonts/fontawesome-webfont.woff imports_src/Monitoring/Favicon.coffee := \ src/meta/icon128.png @@ -87,7 +84,7 @@ crx_contents := script.js eventPage.js icon16.png icon48.png icon128.png manifes release := \ $(foreach f, \ - $(foreach c,. -beta.,$(name)$(c)crx updates$(c)xml $(name)$(c)user.js $(name)$(c)meta.js) \ + $(foreach c,. -beta.,$(name)$(c)crx updates$(c)xml updates$(c)json $(name)$(c)user.js $(name)$(c)meta.js) \ $(name)-noupdate.crx \ $(name)-noupdate.user.js \ $(name).zip \ @@ -104,22 +101,6 @@ all : default release .events .events2 tmp testbuilds builds : $(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 : - npm install $* - -endif - .tests_enabled : echo false> .tests_enabled @@ -137,7 +118,7 @@ endef $(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, \ $(subst .events/,tmp/, \ $(if $(filter-out $(updates),$?), \ @@ -150,11 +131,11 @@ $(foreach s,$(sources),$(eval $(call check_source,$(subst $$,$$$$,$(s))))) $(dests) : .events/compile $(if $(wildcard $@),, \ - node tools/chain.js $(filter-out $(wildcard $(dests)),$(dests)) \ + node tools/chain.js $(call QUOTE, $(filter-out $(wildcard $(dests)),$(dests))) \ && 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 tmp/LICENSE : LICENSE tools/newlinefix.js | tmp @@ -169,7 +150,8 @@ testbuilds/crx$1 : $$(MKDIR) testbuilds/crx$1/script.js : $$(call pieces,crx) | testbuilds/crx$1 .events/compile - $$(call CAT,$$(call QUOTE,$$(call pieces,crx)),$$@) + @echo Concatenating: $$@ + @$$(call CAT,$$(call QUOTE,$$(call pieces,crx)),$$@) testbuilds/crx$1/eventPage.js : tmp/eventPage.js | testbuilds/crx$1 $$(CP) @@ -183,19 +165,23 @@ testbuilds/crx$1/manifest.json : src/meta/manifest.json version.json $(template_ testbuilds/updates$1.xml : src/meta/updates.xml version.json $(template_deps) | testbuilds/crx$1 $(template) $$< $$@ type=crx channel=$1 +testbuilds/updates$1.json : src/meta/updates.json version.json $(template_deps) | testbuilds/crx$1 + $(template) $$< $$@ type=crx channel=$1 + testbuilds/$(name)$1.crx.zip : \ $(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 -testbuilds/$(name)$1.crx : testbuilds/$(name)$1.crx.zip package.json tools/sign.js node_modules/crx/package.json - node tools/sign.js $1 +testbuilds/$(name)$1.crx : $(foreach f,$(crx_contents),testbuilds/crx$1/$(f)) version.json tools/sign.sh | tmp + tools/sign.sh $1 -testbuilds/$(name)$1.meta.js : src/meta/metadata.js src/meta/icon48.png version.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 testbuilds/$(name)$1.user.js : testbuilds/$(name)$1.meta.js tmp/meta-newline.js $$(call pieces,userscript) | .events/compile - $$(call CAT,testbuilds/$(name)$1.meta.js tmp/meta-newline.js $$(call QUOTE,$$(call pieces,userscript)),$$@) + @echo Concatenating: $$@ + @$$(call CAT,testbuilds/$(name)$1.meta.js tmp/meta-newline.js $$(call QUOTE,$$(call pieces,userscript)),$$@) endef @@ -209,7 +195,7 @@ testbuilds/$(name).zip : testbuilds/$(name)-noupdate.crx.zip builds/% : testbuilds/% | builds $(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 index.html : test.html @@ -218,7 +204,7 @@ index.html : test.html tmp/.jshintrc : src/meta/jshint.json tmp/declaration.js src/globals/globals.js $(template_deps) | tmp $(template) $< $@ -.events/jshint : $(dests) tmp/.jshintrc node_modules/jshint/package.json +.events/jshint : $(dests) tmp/.jshintrc $(BIN)jshint $(call QUOTE, \ $(if $(filter-out $(dests),$?), \ $(dests), \ @@ -234,7 +220,7 @@ install.json : node tools/install.js echo -> $@ -.events/CHANGELOG : version.json | .events node_modules/dateformat/package.json +.events/CHANGELOG : version.json | .events node tools/updcl.js echo -> $@ @@ -258,13 +244,13 @@ distready : dist $(wildcard dist/* dist/*/*) git push web $(meta_distBranch) echo -> $@ -.events2/push-store : .git/refs/tags/stable | .events2 distready node_modules/webstore-upload/package.json +.events2/push-store : .git/refs/tags/stable | .events2 distready node tools/webstore.js echo -> $@ .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) @@ -278,23 +264,18 @@ install : .events/install 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 : - $(RMDIR) tmp testbuilds .events + $(RMDIR) tmp tmp-crx testbuilds .events $(RM) .tests_enabled cleanrel : clean $(RMDIR) builds cleanweb : - $(RM) test.html captchas.html + $(RM) test.html cleanfull : clean cleanweb $(RMDIR) .events2 dist node_modules - $(RM) npm-shrinkwrap.json git worktree prune withtests : @@ -302,11 +283,14 @@ withtests : -$(MAKE) echo false> .tests_enabled -wrapped : src/meta/npm-shrinkwrap.json - $(call CAT,$<,npm-shrinkwrap.json) - npm install +archives : + git fetch -n archives + git merge --no-commit -s ours archives/gh-pages + git show archives/gh-pages:archives.json > src/Archive/archives.json + -git commit -am 'Update archive list.' $(foreach i,1 2 3 4,bump$(i)) : + $(MAKE) archives node tools/bump.js $(subst bump,,$@) $(MAKE) .events/CHANGELOG $(MAKE) all @@ -314,7 +298,6 @@ $(foreach i,1 2 3 4,bump$(i)) : tag : git add builds $(MAKE) cleanrel - $(MAKE) wrapped $(MAKE) all git diff --quiet -- builds $(MAKE) tagcommit @@ -333,7 +316,7 @@ stable : distready git push . HEAD:bstable git tag -af stable -m "$(meta_name) v$(version)." cd dist && git merge --no-commit -s ours stable - cd dist && git checkout stable "builds/$(name).*" builds/updates.xml + cd dist && git checkout stable "builds/$(name).*" builds/updates.xml builds/updates.json cd dist && git commit -am "Move $(meta_name) v$(version) to stable channel." web : index.html distready @@ -343,15 +326,11 @@ web : index.html distready cd dist && git commit -am "Update web page." update : - $(RM) npm-shrinkwrap.json + $(RM) package-lock.json npm install --save-dev $(shell node tools/unpinned.js) npm install - npm shrinkwrap --dev - $(call CAT,npm-shrinkwrap.json,src/meta/npm-shrinkwrap.json) updatehard : - $(RM) npm-shrinkwrap.json + $(RM) package-lock.json npm install --save-dev $(shell node tools/unpinned.js latest) npm install - npm shrinkwrap --dev - $(call CAT,npm-shrinkwrap.json,src/meta/npm-shrinkwrap.json) diff --git a/README.md b/README.md index 4113f37863..00b9acceac 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,28 @@ ![screenshot](https://ccd0.github.io/4chan-x/img/screenshot.png) # 4chan X -Adds various features to 4chan. -Previously developed by [aeosynth](https://github.com/aeosynth/4chan-x), [Mayhem](https://github.com/MayhemYDG/4chan-x), [ihavenoface](https://github.com/ihavenoface/4chan-x), [Zixaphir](https://github.com/zixaphir/appchan-x), [Seaweed](https://github.com/seaweedchan/4chan-x), and [Spittie](https://github.com/Spittie/4chan-x), with contributions from many others. +4chan X is a script that adds various features to anonymous imageboards. It was originally developed for 4chan but has no affiliation with it. + +It was previously developed by [aeosynth](https://github.com/aeosynth/4chan-x), [Mayhem](https://github.com/MayhemYDG/4chan-x), [ihavenoface](https://github.com/ihavenoface/4chan-x), [Zixaphir](https://github.com/zixaphir/appchan-x), [Seaweed](https://github.com/seaweedchan/4chan-x), and [Spittie](https://github.com/Spittie/4chan-x), with contributions from many others. If you're looking for a maintained fork of OneeChan (a style script used in addition to 4chan X), try -https://github.com/Nebukazar/OneeChan. +https://github.com/KevinParnell/OneeChan. ## Please note -**Uninstalling**: 4chan X disables the native extension, so if you uninstall 4chan X, you'll need to re-enable it. To do this, click the `[Settings]` link in the top right corner, uncheck "`Disable the native extension`" in the panel that appears, and click the "`Save Settings`" button. +**Uninstalling**: 4chan X disables the native extension, so if you uninstall 4chan X, you'll need to re-enable it. To do this, click the `[Settings]` link in the top right corner, uncheck "`Disable the native extension`" in the panel that appears, and click the "`Save Settings`" button. If you don't see a "`Save Settings`" button, it may be being hidden by your ad blocker. -**Private browsing**: 4chan X does not yet support private browsing / incognito mode. Although it may work in this mode, browsing data recorded by 4chan X, such as your last read post in a thread and which posts are yours, will still need to be cleared manually by resetting your settings. To control what browsing data 4chan X records, use the `Remember Last Read Post` and `Mark Quotes of You` options in the settings panel. +**Private browsing**: By default, 4chan X remembers your last read post in a thread and which posts were made by you, even if you are in private browsing / incognito mode. If you want to turn this off, uncheck the `Remember Last Read Post` and `Remember Your Posts` options in the settings panel. You can clear all 4chan browsing history saved by 4chan X by resetting your settings. -**HTTPS**: 4chan X currently shares your settings and post history between the HTTP and HTTPS versions of 4chan. If you are concerned about protecting your privacy against a man-in-the-middle attack, you should disable 4chan X on the HTTP version of 4chan and/or install [HTTPS Everywhere](https://www.eff.org/https-everywhere). +Use of the "Link Title" feature to fetch titles of Youtube links is subject to Youtube's [Terms of Service](https://www.youtube.com/t/terms) and [Privacy Policy](http://www.google.com/policies/privacy). For more details on what information is sent to Youtube and other sites, and how to turn it off if you don't want the feature, see 4chan X's [privacy documentation](https://github.com/ccd0/4chan-x/wiki/Privacy). ## Install ### Firefox -Install [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/), then **[click here to install 4chan X](https://www.4chan-x.net/builds/4chan-X.user.js)**. +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). ### Chromium -**Userscript**: Install Violentmonkey ([Opera store](https://addons.opera.com/en/extensions/details/violent-monkey/) / [Chrome store](https://chrome.google.com/webstore/detail/violent-monkey/jinjaccalgkegednnccohejagnlnfdag)) or [Tampermonkey](https://tampermonkey.net/), then **[click here to install 4chan X](https://www.4chan-x.net/builds/4chan-X.user.js)**. +**Userscript**: Install [Violentmonkey](https://chrome.google.com/webstore/detail/violent-monkey/jinjaccalgkegednnccohejagnlnfdag) or [Tampermonkey](https://tampermonkey.net/), then **[click here to install 4chan X](https://www.4chan-x.net/builds/4chan-X.user.js)**. **Chrome extension**: 4chan X is also available as a standalone Chrome extension. The Chrome extension has the additional feature of being able to sync your settings and data with other devices via Chrome Sync. But there is an issue when the script updates: Whenever the Chrome extension is updated, until you hard refresh (F5) the tab, 4chan X is unable to save any data (such as posts marked as yours and settings changes). The userscript version above does not have this problem when 4chan X updates, only when Violentmonkey / Tampermonkey is updated. To install as a Chrome extension: @@ -32,30 +33,43 @@ Ports of Greasemonkey are available for [SeaMonkey](https://sourceforge.net/proj Note: This version of 4chan X does not work with Opera 12. If you need Opera 12 support, try [loadletter's fork](https://github.com/loadletter/4chan-x) instead. ### Safari -Install [JS Blocker](http://jsblocker.toggleable.com/) or [Tampermonkey](http://tampermonkey.net/?browser=safari), then **[click here to install 4chan X](https://www.4chan-x.net/builds/4chan-X.user.js)**. +Install the [Userscripts](https://itunes.apple.com/us/app/userscripts/id1463298887) extension. Enable it by pressing `⌘,`, navigating to the extensions pane and checking `Userscripts` checkbox. Now open the Userscripts editor by clicking on the `` button in the taskbar. Then click on the `+` button and select the `New Javascript` option. Replace the default text with the contents of the 4chan X **[script](https://www.4chan-x.net/builds/4chan-X.user.js)**. Finally save it by pressing `⌘s`. -### WebKitGTK+ -Several WebKitGTK+ based browsers have support for userscripts and can run 4chan X. Due to the lack of the cross-site GM_* API, and lack of support for userscripts in iframes, not all features will work. You may experience crashes when repeatedly solving the default image-based captchas. You can avoid this problem by enabling `Use Recaptcha v1` in your settings. +### WebKitGTK+ / QtWebKit / QtWebEngine +Several minimal browsers have support for userscripts and can run 4chan X. Due to the lack of the cross-site GM_* API, and lack of support for userscripts in iframes, not all features will work. You may experience crashes when repeatedly solving the default image-based captchas. You can avoid this problem by enabling `Use Recaptcha v1` in your settings. - **dwb**: Install the userscripts extension, then save the [script](https://www.4chan-x.net/builds/4chan-X.user.js) to the `$XDG_CONFIG_HOME/dwb/greasemonkey` or `$HOME/.config/dwb/greasemonkey` directory (creating it if necessary): - dwbem -N -i userscripts - wget -P ${XDG_CONFIG_HOME:-$HOME/.config}/dwb/greasemonkey https://www.4chan-x.net/builds/4chan-X.user.js + ``` + dwbem -N -i userscripts + wget -P ${XDG_CONFIG_HOME:-$HOME/.config}/dwb/greasemonkey https://www.4chan-x.net/builds/4chan-X.user.js + ``` - **Midori**: Enable `User addons` in your preferences, under the Extensions tab. In the Privacy tab, check `Enable HTML5 local storage support`. Optionally, if you want 4chan X to be able to open new tabs when you start or reply to a thread, you will need to check `Allow scripts to open popups` under the Behavior tab. Then click the link to the [script](https://www.4chan-x.net/builds/4chan-X.user.js) to install it. - **Luakit**: Navigate to the [script](https://www.4chan-x.net/builds/4chan-X.user.js), then type the command `:usi` to install it. -- **uzbl**: Install the script from https://github.com/singpolyma/singpolyma/blob/master/uzbl/data/scripts/userscript.sh, enable it in your config file, and then save [4chan X](https://www.4chan-x.net/builds/4chan-X.user.js) to `$XDG_DATA_HOME/uzbl/userscripts` (or `$HOME/.local/share/uzbl/userscripts`). +- **uzbl**: Install the script from https://github.com/singpolyma/singpolyma/blob/master/uzbl/data/scripts/userscript.sh, enable it in your config file, and then save [4chan X](https://www.4chan-x.net/builds/4chan-X.user.js) to `$XDG_DATA_HOME/uzbl/userscripts` (or `$HOME/.local/share/uzbl/userscripts`). The commands below assume you have run uzbl at least once to create its config file. + + ``` + wget -P "${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/scripts" https://raw.githubusercontent.com/singpolyma/singpolyma/master/uzbl/data/scripts/userscript.sh + chmod +x "${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/scripts/userscript.sh" + echo '@on_event LOAD_COMMIT spawn @scripts_dir/userscript.sh document-start' >> "${XDG_CONFIG_HOME:-$HOME/.config}/uzbl/config" + echo '@on_event LOAD_FINISH spawn @scripts_dir/userscript.sh document-end' >> "${XDG_CONFIG_HOME:-$HOME/.config}/uzbl/config" + wget -P "${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/userscripts" https://www.4chan-x.net/builds/4chan-X.user.js + ``` + +- **qutebrowser**: Save the [script](https://www.4chan-x.net/builds/4chan-X.user.js) to the `$XDG_DATA_HOME/qutebrowser/greasemonkey` or `$HOME/.local/share/qutebrowser/greasemonkey` directory: + + ``` + wget -P ${XDG_DATA_HOME:-$HOME/.local/share}/qutebrowser/greasemonkey https://www.4chan-x.net/builds/4chan-X.user.js + ``` - wget -P ${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/scripts https://raw.githubusercontent.com/singpolyma/singpolyma/master/uzbl/data/scripts/userscript.sh - chmod +x ${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/scripts/userscript.sh - echo '@on_event LOAD_COMMIT spawn @scripts_dir/userscript.sh document-start' >> ${XDG_CONFIG_HOME:-$HOME/.config}/uzbl/config - echo '@on_event LOAD_FINISH spawn @scripts_dir/userscript.sh document-end' >> ${XDG_CONFIG_HOME:-$HOME/.config}/uzbl/config - wget -P ${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/userscripts https://www.4chan-x.net/builds/4chan-X.user.js +### MS Edge +Install [Tampermonkey](https://www.microsoft.com/en-us/store/p/tampermonkey/9nblggh5162s), then **[click here to install 4chan X](https://www.4chan-x.net/builds/4chan-X.user.js)**. ### Other browsers -4chan X can be used in some browsers that do not support userscripts, such as **Microsoft Edge**, using [a local proxy](https://github.com/ccd0/4chan-x-proxy). Not all features will work. +4chan X can be used in some browsers that do not support userscripts using [a local proxy](https://github.com/ccd0/4chan-x-proxy). Not all features will work. ## Beta version New features and non-urgent bugfixes are released on the beta channel for further testing before they are moved the stable version. Please [report](https://github.com/ccd0/4chan-x/issues?q=is%3Aopen+sort%3Aupdated-desc) any issues you find, and be sure to mention which version you're using. You should back up your settings regularly to prevent them from being lost due to bugs. @@ -69,11 +83,11 @@ To install the current **beta** version but get updates from the **stable** chan - [Download Chrome extension](https://github.com/ccd0/4chan-x/raw/beta/builds/4chan-X.crx) ## Troubleshooting -If you encounter a bug, try the steps [here](https://github.com/ccd0/4chan-x/blob/master/CONTRIBUTING.md#reporting-bugs), then report it to the [issue tracker](https://github.com/ccd0/4chan-x/issues?q=is%3Aopen+sort%3Aupdated-desc). You can report bugs without a Github account via [this form](https://gitreports.com/issue/ccd0/4chan-x). If the bug seems to be caused by a script update, you can install a old version from the [changelog](https://github.com/ccd0/4chan-x/blob/master/CHANGELOG.md). +If you encounter a bug, try the steps [here](https://github.com/ccd0/4chan-x/blob/master/CONTRIBUTING.md#reporting-bugs), then report it to the [issue tracker](https://github.com/ccd0/4chan-x/issues?q=is%3Aopen+sort%3Aupdated-desc). If the bug seems to be caused by a script update, you can install a old version from the [changelog](https://github.com/ccd0/4chan-x/blob/master/CHANGELOG.md). ## More information - [Changelog](https://github.com/ccd0/4chan-x/blob/master/CHANGELOG.md) - [Frequently Asked Questions](https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions) -- [Report Bugs](https://gitreports.com/issue/ccd0/4chan-x) +- [Report Bugs](https://github.com/ccd0/4chan-x/issues) - [Contributing](https://github.com/ccd0/4chan-x/blob/master/CONTRIBUTING.md) diff --git a/builds/4chan-X-beta.crx b/builds/4chan-X-beta.crx index 48c6d79462..acfaf09349 100644 Binary files a/builds/4chan-X-beta.crx and b/builds/4chan-X-beta.crx differ diff --git a/builds/4chan-X-beta.meta.js b/builds/4chan-X-beta.meta.js index 311d466e31..942822159b 100644 --- a/builds/4chan-X-beta.meta.js +++ b/builds/4chan-X-beta.meta.js @@ -1,10 +1,10 @@ // ==UserScript== // @name 4chan X beta -// @version 1.12.0.0 +// @version 1.14.23.1 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X -// @description Cross-browser userscript for maximum lurking on 4chan. +// @description 4chan X is a script that adds various features to anonymous imageboards. // @license MIT; https://github.com/ccd0/4chan-x/blob/master/LICENSE // @include http://boards.4chan.org/* // @include https://boards.4chan.org/* @@ -12,17 +12,85 @@ // @include https://sys.4chan.org/* // @include http://www.4chan.org/* // @include https://www.4chan.org/* +// @include http://boards.4channel.org/* +// @include https://boards.4channel.org/* +// @include http://sys.4channel.org/* +// @include https://sys.4channel.org/* +// @include http://www.4channel.org/* +// @include https://www.4channel.org/* // @include http://i.4cdn.org/* // @include https://i.4cdn.org/* -// @include https://www.google.com/recaptcha/api2/anchor?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @include https://www.google.com/recaptcha/api2/frame?*&k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @include http://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @include https://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @exclude http://www.4chan.org/pass -// @exclude https://www.4chan.org/pass -// @exclude http://www.4chan.org/pass?* -// @exclude https://www.4chan.org/pass?* -// @connect i.4cdn.org +// @include http://is.4chan.org/* +// @include https://is.4chan.org/* +// @include http://is2.4chan.org/* +// @include https://is2.4chan.org/* +// @include http://is.4channel.org/* +// @include https://is.4channel.org/* +// @include http://is2.4channel.org/* +// @include https://is2.4channel.org/* +// @include https://erischan.org/* +// @include https://www.erischan.org/* +// @include https://fufufu.moe/* +// @include https://gnfos.com/* +// @include https://himasugi.blog/* +// @include https://www.himasugi.blog/* +// @include https://kakashinenpo.com/* +// @include https://www.kakashinenpo.com/* +// @include https://kissu.moe/* +// @include https://www.kissu.moe/* +// @include https://lainchan.org/* +// @include https://www.lainchan.org/* +// @include https://merorin.com/* +// @include https://ota-ch.com/* +// @include https://www.ota-ch.com/* +// @include https://ponyville.us/* +// @include https://www.ponyville.us/* +// @include https://smuglo.li/* +// @include https://notso.smuglo.li/* +// @include https://smugloli.net/* +// @include https://smug.nepu.moe/* +// @include https://sportschan.org/* +// @include https://www.sportschan.org/* +// @include https://sushigirl.us/* +// @include https://www.sushigirl.us/* +// @include https://tvch.moe/* +// @exclude http://www.4chan.org/advertise +// @exclude https://www.4chan.org/advertise +// @exclude http://www.4chan.org/advertise?* +// @exclude https://www.4chan.org/advertise?* +// @exclude http://www.4chan.org/donate +// @exclude https://www.4chan.org/donate +// @exclude http://www.4chan.org/donate?* +// @exclude https://www.4chan.org/donate?* +// @exclude http://www.4channel.org/advertise +// @exclude https://www.4channel.org/advertise +// @exclude http://www.4channel.org/advertise?* +// @exclude https://www.4channel.org/advertise?* +// @exclude http://www.4channel.org/donate +// @exclude https://www.4channel.org/donate +// @exclude http://www.4channel.org/donate?* +// @exclude https://www.4channel.org/donate?* +// @connect 4chan.org +// @connect 4channel.org +// @connect 4cdn.org +// @connect 4chenz.github.io +// @connect archive.4plebs.org +// @connect warosu.org +// @connect desuarchive.org +// @connect boards.fireden.net +// @connect arch.b4k.co +// @connect archived.moe +// @connect thebarchive.com +// @connect archiveofsins.com +// @connect archive.palanq.win +// @connect eientei.xyz +// @connect api.clyp.it +// @connect api.dailymotion.com +// @connect api.github.com +// @connect soundcloud.com +// @connect api.streamable.com +// @connect vimeo.com +// @connect www.youtube.com // @connect * // @grant GM_getValue // @grant GM_setValue @@ -31,6 +99,12 @@ // @grant GM_addValueChangeListener // @grant GM_openInTab // @grant GM_xmlhttpRequest +// @grant GM.getValue +// @grant GM.setValue +// @grant GM.deleteValue +// @grant GM.listValues +// @grant GM.openInTab +// @grant GM.xmlHttpRequest // @run-at document-start // @updateURL https://www.4chan-x.net/builds/4chan-X-beta.meta.js // @downloadURL https://www.4chan-x.net/builds/4chan-X-beta.user.js diff --git a/builds/4chan-X-beta.user.js b/builds/4chan-X-beta.user.js index ec85502081..683144c133 100644 --- a/builds/4chan-X-beta.user.js +++ b/builds/4chan-X-beta.user.js @@ -1,10 +1,10 @@ // ==UserScript== // @name 4chan X beta -// @version 1.12.0.0 +// @version 1.14.23.1 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X -// @description Cross-browser userscript for maximum lurking on 4chan. +// @description 4chan X is a script that adds various features to anonymous imageboards. // @license MIT; https://github.com/ccd0/4chan-x/blob/master/LICENSE // @include http://boards.4chan.org/* // @include https://boards.4chan.org/* @@ -12,17 +12,85 @@ // @include https://sys.4chan.org/* // @include http://www.4chan.org/* // @include https://www.4chan.org/* +// @include http://boards.4channel.org/* +// @include https://boards.4channel.org/* +// @include http://sys.4channel.org/* +// @include https://sys.4channel.org/* +// @include http://www.4channel.org/* +// @include https://www.4channel.org/* // @include http://i.4cdn.org/* // @include https://i.4cdn.org/* -// @include https://www.google.com/recaptcha/api2/anchor?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @include https://www.google.com/recaptcha/api2/frame?*&k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @include http://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @include https://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @exclude http://www.4chan.org/pass -// @exclude https://www.4chan.org/pass -// @exclude http://www.4chan.org/pass?* -// @exclude https://www.4chan.org/pass?* -// @connect i.4cdn.org +// @include http://is.4chan.org/* +// @include https://is.4chan.org/* +// @include http://is2.4chan.org/* +// @include https://is2.4chan.org/* +// @include http://is.4channel.org/* +// @include https://is.4channel.org/* +// @include http://is2.4channel.org/* +// @include https://is2.4channel.org/* +// @include https://erischan.org/* +// @include https://www.erischan.org/* +// @include https://fufufu.moe/* +// @include https://gnfos.com/* +// @include https://himasugi.blog/* +// @include https://www.himasugi.blog/* +// @include https://kakashinenpo.com/* +// @include https://www.kakashinenpo.com/* +// @include https://kissu.moe/* +// @include https://www.kissu.moe/* +// @include https://lainchan.org/* +// @include https://www.lainchan.org/* +// @include https://merorin.com/* +// @include https://ota-ch.com/* +// @include https://www.ota-ch.com/* +// @include https://ponyville.us/* +// @include https://www.ponyville.us/* +// @include https://smuglo.li/* +// @include https://notso.smuglo.li/* +// @include https://smugloli.net/* +// @include https://smug.nepu.moe/* +// @include https://sportschan.org/* +// @include https://www.sportschan.org/* +// @include https://sushigirl.us/* +// @include https://www.sushigirl.us/* +// @include https://tvch.moe/* +// @exclude http://www.4chan.org/advertise +// @exclude https://www.4chan.org/advertise +// @exclude http://www.4chan.org/advertise?* +// @exclude https://www.4chan.org/advertise?* +// @exclude http://www.4chan.org/donate +// @exclude https://www.4chan.org/donate +// @exclude http://www.4chan.org/donate?* +// @exclude https://www.4chan.org/donate?* +// @exclude http://www.4channel.org/advertise +// @exclude https://www.4channel.org/advertise +// @exclude http://www.4channel.org/advertise?* +// @exclude https://www.4channel.org/advertise?* +// @exclude http://www.4channel.org/donate +// @exclude https://www.4channel.org/donate +// @exclude http://www.4channel.org/donate?* +// @exclude https://www.4channel.org/donate?* +// @connect 4chan.org +// @connect 4channel.org +// @connect 4cdn.org +// @connect 4chenz.github.io +// @connect archive.4plebs.org +// @connect warosu.org +// @connect desuarchive.org +// @connect boards.fireden.net +// @connect arch.b4k.co +// @connect archived.moe +// @connect thebarchive.com +// @connect archiveofsins.com +// @connect archive.palanq.win +// @connect eientei.xyz +// @connect api.clyp.it +// @connect api.dailymotion.com +// @connect api.github.com +// @connect soundcloud.com +// @connect api.streamable.com +// @connect vimeo.com +// @connect www.youtube.com // @connect * // @grant GM_getValue // @grant GM_setValue @@ -31,6 +99,12 @@ // @grant GM_addValueChangeListener // @grant GM_openInTab // @grant GM_xmlhttpRequest +// @grant GM.getValue +// @grant GM.setValue +// @grant GM.deleteValue +// @grant GM.listValues +// @grant GM.openInTab +// @grant GM.xmlHttpRequest // @run-at document-start // @updateURL https://www.4chan-x.net/builds/4chan-X-beta.meta.js // @downloadURL https://www.4chan-x.net/builds/4chan-X-beta.user.js @@ -121,11 +195,11 @@ 'use strict'; -var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, Build, CSS, Callbacks, Captcha, CatalogLinks, CatalogThread, Config, Connection, CrossOrigin, CustomCSS, DataBoard, DeleteLink, DownloadLink, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, Fetcher, FileInfo, Filter, Flash, Fourchan, Gallery, Get, Header, IDColor, IDHighlight, ImageCommon, ImageExpand, ImageHover, ImageLoader, Index, Keybinds, Linkify, Main, MarkNewIPs, Menu, Metadata, Nav, NormalizeURL, Notice, PSAHiding, PassLink, Polyfill, Post, PostHiding, PostSuccessful, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, ReplyPruning, Report, ReportLink, RevealSpoilers, Sauce, Settings, SimpleDict, Thread, ThreadHiding, ThreadLinks, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, Volume; +var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, BoardConfig, CSS, Callbacks, Captcha, CatalogLinks, CatalogThread, CatalogThreadNative, Config, Connection, CopyTextLink, CrossOrigin, CustomCSS, DataBoard, DeleteLink, DownloadLink, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, Fetcher, FileInfo, Filter, Flash, Fourchan, Gallery, Get, Header, IDColor, IDHighlight, IDPostCount, ImageCommon, ImageExpand, ImageHost, ImageHover, ImageLoader, Index, Keybinds, Linkify, Main, MarkNewIPs, Menu, Metadata, ModContact, Nav, NormalizeURL, Notice, PSA, PSAHiding, PassLink, PassMessage, Polyfill, Post, PostHiding, PostJumper, PostRedirect, PostSuccessful, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, ReplyPruning, Report, ReportLink, RevealSpoilers, SW, Sauce, Settings, ShimSet, SimpleDict, Site, Test, Thread, ThreadHiding, ThreadLinks, ThreadStats, ThreadUpdater, ThreadWatcher, Time, Tinyboard, UI, Unread, UnreadIndex, Volume; var Conf, E, c, d, doc, docSet, g; -Conf = {}; +Conf = Object.create(null); c = console; d = document; doc = d.documentElement; @@ -136,9 +210,10 @@ docSet = function() { }; g = { - VERSION: '1.12.0.0', + VERSION: '1.14.23.1', NAMESPACE: '4chan X.', - boards: {} + sites: Object.create(null), + boards: Object.create(null) }; E = (function() { @@ -169,25 +244,24 @@ E.cat = function(templates) { return html; }; -E.url = function(content) { - return "data:text/html;charset=utf-8," + encodeURIComponent(content.innerHTML); -}; - Config = (function() { var Config; Config = { main: { 'Miscellaneous': { + 'Redirect to HTTPS': [true, 'Redirect to the HTTPS version of 4chan.'], 'JSON Index': [true, 'Replace the original board index with one supporting searching, sorting, infinite scrolling, and a catalog mode.'], 'Use 4chan X Catalog': [true, 'Link to 4chan X\'s catalog instead of the native 4chan one.', 1], 'Index Refresh Notifications': [false, 'Show a notice at the top of the page when the index is refreshed.', 1], + 'Follow Cursor': [true, 'Image Hover and Quote Preview move with the mouse cursor.'], 'Open Threads in New Tab': [false, 'Make links to threads in the index / 4chan X catalog open in a new tab.'], 'External Catalog': [false, 'Link to external catalog instead of the internal one.'], 'Catalog Links': [false, 'Add toggle link in header menu to turn Navigation links into links to each board\'s catalog.'], 'Announcement Hiding': [true, 'Add button to hide 4chan announcements.'], 'Desktop Notifications': [true, 'Enables desktop notifications across various 4chan X features.'], '404 Redirect': [true, 'Redirect dead threads and images to the archives.'], + 'Archive Report': [true, 'Enable reporting posts to supported archives.'], 'Exempt Archives from Encryption': [true, 'Permit loading content from, and warningless redirects to, HTTP-only archives from HTTPS pages.'], 'Keybinds': [true, 'Bind actions to keyboard shortcuts.'], 'Time Formatting': [true, 'Localize and format timestamps.'], @@ -198,13 +272,16 @@ Config = (function() { 'Thread Expansion': [true, 'Add buttons to expand threads.'], 'Index Navigation': [false, 'Add buttons to navigate between threads.'], 'Reply Navigation': [false, 'Add buttons to navigate to top / bottom of thread.'], + 'Unique ID and Capcode Navigation': [false, 'Add buttons to navigate to posts having the same unique ID or capcode.'], 'Custom Board Titles': [true, 'Allow editing of the board title and subtitle by ctrl/\u2318+clicking them.'], 'Persistent Custom Board Titles': [false, 'Force custom board titles to be persistent, even if the board titles are updated.', 1], 'Show Updated Notifications': [true, 'Show notifications when 4chan X is successfully updated.'], 'Color User IDs': [true, 'Assign unique colors to user IDs on boards that use them'], + 'Count Posts by ID': [true, 'Display number of posts in the thread when hovering over an ID.'], 'Remove Spoilers': [false, 'Remove all spoilers in text.'], 'Reveal Spoilers': [false, 'Indicate spoilers if Remove Spoilers is enabled, or make the text appear hovered if Remove Spoiler is disabled.'], 'Normalize URL': [true, 'Rewrite the URL of the current page, removing slugs and excess slashes, and changing /res/ to /thread/.'], + 'Work around CORB Bug': [true, 'Leave this checked until your garbage browser is fixed.'], 'Disable Autoplaying Sounds': [false, 'Prevent sounds on the page from autoplaying.'], 'Disable Native Extension': [true, '4chan X is NOT designed to work with the native extension.'], 'Enable Native Flash Embedding': [true, 'Activate the native extension\'s Flash embedding if the native extension is disabled.'] @@ -212,6 +289,7 @@ Config = (function() { 'Linkification': { 'Linkify': [true, 'Convert text into links where applicable.'], 'Link Title': [true, 'Replace the link of a supported site with its actual title.', 1], + 'Cover Preview': [true, 'Show preview of supported links on hover.', 1], 'Embedding': [true, 'Embed supported services. Note: Some services don\'t work on HTTPS.', 1], 'Auto-embed': [false, 'Auto-embed Linkify Embeds.', 2], 'Floating Embeds': [false, 'Embed content in a frame that remains in place when the page is scrolled.', 2] @@ -220,6 +298,8 @@ Config = (function() { 'Anonymize': [false, 'Make everyone Anonymous.'], 'Filter': [true, 'Self-moderation placebo.'], 'Filtered Backlinks': [false, 'When enabled, shows backlinks to filtered posts with a line-through decoration. Otherwise, hides the backlinks.', 1], + 'Filter in Native Catalog': [true, 'Apply 4chan X filters in native catalog.', 1], + 'MD5 Quick Filter Notifications': [true, 'Show notification when quick filtering MD5s using the button or keybind.', 1], 'Recursive Hiding': [true, 'Hide replies of hidden posts, recursively.'], 'Thread Hiding Buttons': [true, 'Add buttons to hide entire threads.'], 'Reply Hiding Buttons': [true, 'Add buttons to hide single replies.'], @@ -228,8 +308,8 @@ Config = (function() { 'Images and Videos': { 'Image Expansion': [true, 'Expand images / videos.'], 'Image Hover': [true, 'Show full image / video on mouseover.'], - 'Image Hover in Catalog': [false, 'Show full image / video on mouseover in 4chan X catalog.'], - 'Gallery': [true, 'Adds a simple and cute image gallery.'], + 'Image Hover in Catalog': [true, 'Show full image / video on mouseover in 4chan X catalog.'], + 'Gallery': [true, 'Adds a simple and cute image gallery. Has more options in the gallery menu.'], 'Fullscreen Gallery': [false, 'Open gallery in fullscreen mode.', 1], 'PDF in Gallery': [false, 'Show PDF files in gallery.', 1], 'Sauce': [true, 'Add sauce links to images.'], @@ -238,11 +318,12 @@ Config = (function() { 'Replace GIF': [false, 'Replace gif thumbnails with the actual image.'], 'Replace JPG': [false, 'Replace jpg thumbnails with the actual image.'], 'Replace PNG': [false, 'Replace png thumbnails with the actual image.'], - 'Replace WEBM': [false, 'Replace webm thumbnails with the actual webm video. Probably will degrade browser performance ;)'], - 'Image Prefetching': [false, 'Add link in header menu to turn on image preloading.'], + 'Replace WEBM': [false, 'Replace webm, mp4, and ogv thumbnails with the actual video. Probably will degrade browser performance ;)'], + 'Image Prefetching': [true, 'Add a shortcut icon to the header to turn on image preloading.'], 'Fappe Tyme': [true, 'Hide posts without images when header menu item is checked. *hint* *hint*'], 'Werk Tyme': [true, 'Hide all post images when header menu item is checked.'], 'Autoplay': [true, 'Videos begin playing immediately when opened.'], + 'Restart when Opened': [false, 'Restart GIFs and WebMs when you hover over or expand them.'], 'Show Controls': [true, 'Show controls on videos expanded inline.'], 'Click Passthrough': [false, 'Clicks on videos trigger your browser\'s default behavior. Videos can be contracted with button / dragging to the left.', 1], 'Allow Sound': [true, 'Open videos with the sound unmuted.'], @@ -253,12 +334,13 @@ Config = (function() { 'Menu': { 'Menu': [true, 'Add a drop-down menu to posts.'], 'Report Link': [true, 'Add a report link to the menu.', 1], + 'Copy Text Link': [true, 'Add a link to copy the post\'s text.', 1], 'Thread Hiding Link': [true, 'Add a link to hide entire threads.', 1], 'Reply Hiding Link': [true, 'Add a link to hide single replies.', 1], 'Delete Link': [true, 'Add post and image deletion links to the menu.', 1], 'Archive Link': [true, 'Add an archive link to the menu.', 1], 'Edit Link': [true, 'Add a link to edit the image in Tegaki, /i/\'s painting program. Requires Quick Reply.', 1], - 'Download Link': [true, 'Add a download with original filename link to the menu.', 1] + 'Download Link': [false, 'Add a download with original filename link to the menu.', 1] }, 'Monitoring': { 'Thread Updater': [true, 'Fetch and insert new replies. Has more options in the header menu and the "Advanced" tab.'], @@ -269,20 +351,24 @@ Config = (function() { 'Unread Line': [true, 'Show a line to distinguish read posts from unread ones.'], 'Remember Last Read Post': [true, 'Remember how far you\'ve read after you close the thread.'], 'Scroll to Last Read Post': [true, 'Scroll back to the last read post when reopening a thread.', 1], + 'Unread Line in Index': [false, 'Show a line between read and unread posts in threads in the index.', 1], + 'Remove Thread Excerpt': [false, 'Replace the excerpt of the thread in the tab title with the board title.'], 'Thread Stats': [true, 'Display reply and image count.'], 'IP Count in Stats': [true, 'Display the unique IP count in the thread stats.', 1], 'Page Count in Stats': [true, 'Display the page count in the thread stats.', 1], 'Updater and Stats in Header': [true, 'Places the thread updater and thread stats in the header instead of floating them.'], - 'Thread Watcher': [true, 'Bookmark threads.'], + 'Thread Watcher': [true, 'Bookmark threads. Has more options in the thread watcher menu.'], 'Fixed Thread Watcher': [true, 'Makes the thread watcher scroll with the page.', 1], - 'Toggleable Thread Watcher': [true, 'Adds a shortcut for the thread watcher and hides the watcher by default.', 1], + 'Persistent Thread Watcher': [false, 'The thread watcher will be visible when the page is loaded.', 1], 'Mark New IPs': [false, 'Label each post from a new IP with the thread\'s current IP count.'], - 'Reply Pruning': [true, 'Hide old replies in long threads. Number of replies shown can be set from header menu.'] + 'Reply Pruning': [true, 'Add option in header menu to hide old replies in long threads. Activated by default in stickies.'], + 'Prune All Threads': [false, 'Activate Reply Pruning by default in all threads.', 1] }, 'Posting and Captchas': { 'Quick Reply': [true, 'All-in-one form to reply, create threads, automate dumping and more.'], 'Persistent QR': [false, 'The Quick reply won\'t disappear after posting.', 1], - 'Auto Hide QR': [true, 'Automatically hide the quick reply when posting.', 1], + 'Auto Hide QR': [true, 'Automatically hide the quick reply when posting.', 2], + 'Open Post in New Tab': [true, 'Open new threads in a new tab, and open replies in a new tab if you\'re not already in the thread.', 1], 'Remember QR Size': [false, 'Remember the size of the Quick reply.', 1], 'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.', 1], 'Randomize Filename': [false, 'Set the filename to a random timestamp within the past year. Disabled on /f/.', 1], @@ -292,15 +378,13 @@ Config = (function() { 'Posting Success Notifications': [true, 'Show notifications on successful post creation or file uploading.', 1], 'Auto-load captcha': [false, 'Automatically load the captcha in the QR even if your post is empty.', 1], 'Post on Captcha Completion': [false, 'Submit the post immediately when the captcha is completed.', 1], - 'Captcha Fixes': [true, 'Make captcha easier to use, especially with the keyboard.'], - 'Use Recaptcha v1': [false, 'Use the old text version of Recaptcha in the post form.'], - 'Use Recaptcha v1 in Reports': [false, 'Use the text captcha in the report window.'], - 'Force Noscript Captcha': [false, 'Use the non-Javascript fallback captcha even if Javascript is enabled (Recaptcha v2 only).'], + 'Force Noscript Captcha': [false, 'Use the non-Javascript fallback captcha even if Javascript is enabled.'], 'Pass Link': [false, 'Add a 4chan Pass login link to the bottom of the page.'] }, 'Quote Links': { 'Quote Backlinks': [true, 'Add quote backlinks.'], 'OP Backlinks': [true, 'Add backlinks to the OP.', 1], + 'Bottom Backlinks': [false, 'Place backlinks at the bottom of posts.', 1], 'Quote Inlining': [true, 'Inline quoted post on click.'], 'Inline Cross-thread Quotes Only': [false, 'Don\'t inline quote links when the posts are visible in the thread.', 1], 'Quote Hash Navigation': [false, 'Include an extra link after quotes for autoscrolling to quoted posts.', 1], @@ -324,6 +408,7 @@ Config = (function() { 'Expand spoilers': [true, 'Expand all images along with spoilers.'], 'Expand videos': [true, 'Expand all images also expands videos.'], 'Expand from here': [false, 'Expand all images only from current position to thread end.'], + 'Expand thread only': [false, 'In index, expand all images only within the current thread.'], 'Advance on contract': [false, 'Advance to next post when contracting an expanded image.'] }, gallery: { @@ -338,17 +423,23 @@ Config = (function() { threadWatcher: { 'Current Board': [false, 'Only show watched threads from the current board.'], 'Auto Update Thread Watcher': [true, 'Periodically check status of watched threads.'], - 'Auto Watch': [false, 'Automatically watch threads you start.'], - 'Auto Watch Reply': [false, 'Automatically watch threads you reply to.'], + 'Auto Watch': [true, 'Automatically watch threads you start.'], + 'Auto Watch Reply': [true, 'Automatically watch threads you reply to.'], 'Auto Prune': [false, 'Automatically remove dead threads.'], - 'Show Unread Count': [true, 'Show number of unread posts in watched threads.'] + 'Show Page': [true, 'Show what page watched threads are on.'], + 'Show Unread Count': [true, 'Show number of unread posts in watched threads.'], + 'Show Site Prefix': [true, 'When multiple sites are shown in the thread watcher, add a prefix to board names to distinguish them.'], + 'Require OP Quote Link': [false, 'For purposes of thread watcher highlighting, only consider posts with a quote link to the OP as replies to the OP.'] }, filter: { + general: '', postID: "# Highlight dubs on [s4s]:\n#/(\\d)\\1$/;highlight;top:no;boards:s4s", name: "# Filter any namefags:\n#/^(?!Anonymous$)/", uniqueID: "# Filter a specific ID:\n#/Txhvk1Tl/", tripcode: "# Filter any tripfag\n#/^!/", capcode: "# Set a custom class for mods:\n#/Mod$/;highlight:mod;op:yes\n# Set a custom class for admins:\n#/Admin$/;highlight:admin;op:yes", + pass: "# Filter anyone using since4pass:\n#/./", + email: '', subject: "# Filter Generals on /v/:\n#/general/i;boards:v;op:only", comment: "# Filter Stallman copypasta on /g/:\n#/what you\'re refer+ing to as linux/i;boards:g\n# Filter posts with 20 or more quote links:\n#/(?:>>\\d(?:(?!>>\\d)[^])*){20}/\n# Filter posts like T H I S / H / I / S:\n#/^>?\\s?\\w\\s?(\\w)\\s?(\\w)\\s?(\\w).*$[\\s>]+\\1[\\s>]+\\2[\\s>]+\\3/im", flag: '', @@ -357,7 +448,7 @@ Config = (function() { filesize: '', MD5: '' }, - sauces: "# Reverse image search:\nhttps://www.google.com/searchbyimage?image_url=%IMG&safe=off\n#https://www.yandex.com/images/search?rpt=imageview&img_url=%IMG\n#//tineye.com/search?url=%IMG\n\n# Specialized reverse image search:\n//iqdb.org/?url=%IMG\nhttps://whatanime.ga/?auto&url=%IMG;text:wait\n#//3d.iqdb.org/?url=%IMG\n#//saucenao.com/search.php?url=%IMG\n\n# \"View Same\" in archives:\nhttp://eye.swfchan.com/search/?q=%name;types:swf\n#https://desustorage.org/_/search/image/%sMD5/\n#https://archive.4plebs.org/_/search/image/%sMD5/\n#https://boards.fireden.net/_/search/image/%sMD5/\n#https://foolz.fireden.net/_/search/image/%sMD5/\n\n# Other tools:\n#http://regex.info/exif.cgi?imgurl=%URL\n#//imgops.com/%URL;types:gif,jpg,png\n#//www.gif-explode.com/%URL;types:gif", + sauces: "# Known filename formats:\nhttps://www.pixiv.net/member_illust.php?mode=medium&illust_id=%$1;regexp:/^(\\d+)_p\\d+/\njavascript:void(open(\"https://www.deviantart.com/\"+%$1.replace(/_/g,\"-\")+\"/art/\"+parseInt(%$2,36)));regexp:/^\\w+_by_(\\w+)[_-]d([\\da-z]{6})\\b/\nhttps://imgur.com/%$1;regexp:/^(?![a-zA-Z][a-z]{6})(?![A-Z]{7})(?!\\d{7})([\\da-zA-Z]{7})(?: \\(\\d+\\))?\\.\\w+$/\nhttps://flickr.com/photo.gne?id=%$1;regexp:/^(\\d+)_[\\da-f]{10}(?:_\\w)*\\b/\nhttps://www.facebook.com/photo.php?fbid=%$1;regexp:/^\\d+_(\\d+)_\\d+_[no]\\b/\n\n# Reverse image search:\nhttps://www.google.com/searchbyimage?sbisrc=4chanx&image_url=%IMG&safe=off\nhttps://yandex.com/images/search?rpt=imageview&url=%IMG\n#//tineye.com/search?url=%IMG\n#//www.bing.com/images/search?q=imgurl:%IMG&view=detailv2&iss=sbi#enterInsights\n#https://lens.google.com/uploadbyurl?url=%IMG;text:lens\n\n# Specialized reverse image search:\n//iqdb.org/?url=%IMG\nhttps://trace.moe/?auto&url=%IMG;text:wait\n#//3d.iqdb.org/?url=%IMG\n#//saucenao.com/search.php?url=%IMG\n\n# \"View Same\" in archives:\nhttp://eye.swfchan.com/search/?q=%name;types:swf\n#https://desuarchive.org/_/search/image/%sMD5/\n#https://archive.4plebs.org/_/search/image/%sMD5/\n#https://boards.fireden.net/_/search/image/%sMD5/\n#https://foolz.fireden.net/_/search/image/%sMD5/\n\n# Other tools:\n#http://exif.regex.info/exif.cgi?imgurl=%URL\n#//imgops.com/start?url=%URL;types:gif,jpg,png\n#//www.gif-explode.com/%URL;types:gif", FappeT: { werk: false }, @@ -366,10 +457,12 @@ Config = (function() { 'Index Mode': 'paged', 'Previous Index Mode': 'paged', 'Index Size': 'small', - 'Show Replies': true, - 'Pin Watched Threads': false, - 'Anchor Hidden Threads': true, - 'Refreshed Navigation': false + 'Show Replies': [true, 'Show replies in the index, and also in the catalog if "Catalog hover expand" is checked.'], + 'Catalog Hover Expand': [false, 'Expand the comment and show more details when you hover over a thread in the catalog.'], + 'Catalog Hover Toggle': [true, 'Turn "Catalog hover expand" on and off by clicking in the catalog.'], + 'Pin Watched Threads': [false, 'Move watched threads to the start of the index.'], + 'Anchor Hidden Threads': [true, 'Move hidden threads to the end of the index.'], + 'Refreshed Navigation': [false, 'Refresh index when navigating through pages.'] }, Header: { 'Fixed Header': true, @@ -383,20 +476,23 @@ Config = (function() { 'Custom Board Navigation': true }, archives: { - archiveLists: 'https://mayhemydg.github.io/archives.json/archives.json', + archiveLists: 'https://4chenz.github.io/archives.json/archives.json', lastarchivecheck: 0, archiveAutoUpdate: true }, - boardnav: "[ toggle-all ]\na-replace\nc-replace\ng-replace\nk-replace\nv-replace\nvg-replace\nvr-replace\nck-replace\nco-replace\nfit-replace\njp-replace\nmu-replace\nsp-replace\ntv-replace\nvp-replace\n[external-text:\"FAQ\",\"https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions\"]", + externalCatalogURLs: "//catalog.neet.tv/%board/;boards:4chan.org: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", + boardnav: "[ toggle-all ]\n[current-index-text:\"Index\"\ncurrent-catalog-text:\"Catalog\"\ncurrent-expired-text:\"Expired\"\ncurrent-archive-text:\"Archive\"]\n[external-text:\"FAQ\",\"https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions\"]", QR: { 'QR.personas': "#options:\"sage\";boards:jp;always", sjisPreview: false }, - jsWhitelist: 'http://s.4cdn.org\nhttps://s.4cdn.org\nhttp://www.google.com\nhttps://www.google.com\nhttps://www.gstatic.com\nhttp://cdn.mathjax.org\nhttps://cdn.mathjax.org\n\'self\'\n\'unsafe-inline\'\n\'unsafe-eval\'', + jsWhitelist: '', captchaLanguage: '', time: '%m/%d/%y(%a)%H:%M:%S', + timeLocale: '', backlink: '>>%id', - fileInfo: '%l (%p%s, %r%g)', + pastedname: 'file', + fileInfo: '%l %d (%p%s, %r%g)', favicon: 'ferongr', usercss: "/* Board title rice */\ndiv.boardTitle {\n font-weight: 400 !important;\n}\n:root.yotsuba div.boardTitle {\n font-family: sans-serif !important;\n text-shadow: 1px 1px 1px rgba(100,0,0,0.6);\n}\n:root.yotsuba-b div.boardTitle {\n font-family: sans-serif !important;\n text-shadow: 1px 1px 1px rgba(105,10,15,0.6);\n}\n:root.photon div.boardTitle {\n font-family: sans-serif !important;\n text-shadow: 1px 1px 1px rgba(0,74,153,0.6);\n}\n:root.tomorrow div.boardTitle {\n font-family: sans-serif !important;\n text-shadow: 1px 1px 1px rgba(167,170,168,0.6);\n}\n", hotkeys: { @@ -412,15 +508,27 @@ Config = (function() { 'Math tags': ['Alt+m', 'Insert math tags.'], 'SJIS tags': ['Alt+a', 'Insert SJIS tags.'], 'Toggle sage': ['Alt+s', 'Toggle sage in options field.'], + 'Toggle Cooldown': ['Alt+Comma', 'Toggle custom cooldown timer.'], + 'Post from URL': ['Alt+l', 'Post from URL.'], + 'Add new post': ['Alt+n', 'Add new post to the QR dump list.'], 'Submit QR': ['Ctrl+Enter', 'Submit post.'], 'Watch': ['w', 'Watch thread.'], 'Update': ['r', 'Update the thread / refresh the index.'], 'Update thread watcher': ['Shift+r', 'Manually refresh thread watcher.'], + 'Toggle thread watcher': ['t', 'Toggle visibility of thread watcher.'], + 'Toggle threading': ['Shift+t', 'Toggle threading.'], + 'Mark thread read': ['Ctrl+0', 'Mark thread read from index (requires "Unread Line in Index").'], 'Expand image': ['Shift+e', 'Expand selected image.'], 'Expand images': ['e', 'Expand all images.'], 'Open Gallery': ['g', 'Opens the gallery.'], + 'Next Gallery Image': ['Right', 'Go to the next image in gallery mode.'], + 'Previous Gallery Image': ['Left', 'Go to the previous image in gallery mode.'], + 'Advance Gallery': ['Enter', 'Go to next image or, if Autoplay is off, play video.'], 'Pause': ['p', 'Pause/play videos in the gallery.'], 'Slideshow': ['Ctrl+Right', 'Toggle the gallery slideshow mode.'], + 'Rotate image clockwise': ['Shift+Right', 'Rotate image clockwise in gallery.'], + 'Rotate image anticlockwise': ['Shift+Left', 'Rotate image anticlockwise in gallery.'], + 'Download Gallery Image': ['Shift+j', 'Download current image in gallery.'], 'fappeTyme': ['f', 'Toggle Fappe Tyme.'], 'werkTyme': ['Shift+w', 'Toggle Werk Tyme.'], 'Front page': ['1', 'Jump to front page.'], @@ -442,6 +550,7 @@ Config = (function() { 'Previous reply': ['k', 'Select previous reply.'], 'Deselect reply': ['Shift+d', 'Deselect reply.'], 'Hide': ['x', 'Hide thread.'], + 'Quick Filter MD5': ['5', 'Add the MD5 of the selected image to the filter list.'], 'Previous Post Quoting You': ['Alt+Up', 'Scroll to the previous post that quotes you.'], 'Next Post Quoting You': ['Alt+Down', 'Scroll to the next post that quotes you.'] }, @@ -455,13 +564,26 @@ Config = (function() { 'Auto Update': [true, 'Automatically fetch new posts.'], 'Optional Increase': [false, 'Increase the intervals between updates on threads without new posts.'] }, - 'Interval': 30 + 'Interval': 5 }, customCooldown: 0, customCooldownEnabled: true, 'Thread Quotes': false, 'Max Replies': 1000, - 'Autohiding Scrollbar': false + 'Autohiding Scrollbar': false, + position: { + 'embedding.position': 'top: 50px; right: 0px;', + 'thread-stats.position': 'bottom: 0px; right: 0px;', + 'updater.position': 'bottom: 0px; left: 0px;', + 'thread-watcher.position': 'top: 50px; left: 0px;', + 'qr.position': 'top: 50px; right: 0px;' + }, + fourchanImageHost: 'i.4cdn.org', + hiddenPSAList: [{}], + knownBanners: '0.jpg,1.jpg,2.jpg,4.jpg,6.jpg,7.jpg,8.jpg,9.jpg,10.jpg,11.jpg,12.jpg,13.jpg,14.jpg,16.jpg,17.jpg,18.jpg,19.jpg,20.jpg,21.jpg,22.jpg,24.jpg,25.jpg,26.jpg,28.jpg,29.jpg,33.jpg,38.jpg,39.jpg,43.jpg,44.jpg,45.jpg,46.jpg,47.jpg,52.jpg,54.jpg,57.jpg,59.jpg,60.jpg,61.jpg,64.jpg,66.jpg,67.jpg,69.jpg,71.jpg,72.jpg,76.jpg,77.jpg,81.jpg,82.jpg,83.jpg,84.jpg,88.jpg,90.jpg,91.jpg,96.jpg,98.jpg,99.jpg,100.jpg,104.jpg,106.jpg,116.jpg,119.jpg,137.jpg,140.jpg,148.jpg,149.jpg,150.jpg,154.jpg,156.jpg,157.jpg,158.jpg,159.jpg,161.jpg,162.jpg,164.jpg,165.jpg,166.jpg,167.jpg,168.jpg,169.jpg,170.jpg,171.jpg,172.jpg,173.jpg,174.jpg,175.jpg,176.jpg,178.jpg,179.jpg,180.jpg,181.jpg,182.jpg,183.jpg,186.jpg,189.jpg,190.jpg,192.jpg,193.jpg,194.jpg,197.jpg,198.jpg,200.jpg,201.jpg,202.jpg,203.jpg,205.jpg,206.jpg,207.jpg,208.jpg,210.jpg,213.jpg,214.jpg,215.jpg,216.jpg,218.jpg,219.jpg,220.jpg,221.jpg,222.jpg,223.jpg,224.jpg,227.jpg,0.png,1.png,2.png,3.png,5.png,6.png,9.png,10.png,11.png,12.png,14.png,16.png,19.png,20.png,21.png,22.png,23.png,24.png,26.png,27.png,28.png,29.png,30.png,31.png,32.png,33.png,34.png,37.png,39.png,40.png,41.png,42.png,43.png,44.png,45.png,48.png,49.png,50.png,51.png,52.png,53.png,57.png,58.png,59.png,64.png,66.png,67.png,68.png,69.png,70.png,71.png,72.png,76.png,78.png,79.png,81.png,82.png,85.png,86.png,87.png,89.png,95.png,98.png,100.png,101.png,102.png,105.png,106.png,107.png,109.png,110.png,111.png,112.png,113.png,114.png,115.png,116.png,118.png,119.png,120.png,121.png,122.png,123.png,126.png,128.png,130.png,134.png,136.png,138.png,139.png,140.png,142.png,145.png,146.png,149.png,150.png,151.png,152.png,153.png,154.png,155.png,156.png,157.png,158.png,159.png,160.png,163.png,164.png,165.png,166.png,167.png,168.png,169.png,170.png,171.png,172.png,173.png,174.png,178.png,179.png,180.png,181.png,182.png,184.png,186.png,188.png,190.png,192.png,193.png,194.png,195.png,196.png,197.png,198.png,200.png,202.png,203.png,205.png,206.png,207.png,209.png,212.png,213.png,214.png,216.png,217.png,218.png,219.png,220.png,221.png,222.png,223.png,224.png,225.png,226.png,229.png,231.png,232.png,233.png,234.png,235.png,237.png,238.png,239.png,240.png,241.png,242.png,244.png,245.png,246.png,247.png,248.png,249.png,250.png,253.png,254.png,255.png,256.png,257.png,258.png,259.png,260.png,262.png,268.png,0.gif,1.gif,2.gif,3.gif,4.gif,5.gif,6.gif,7.gif,8.gif,9.gif,10.gif,12.gif,13.gif,14.gif,15.gif,16.gif,18.gif,19.gif,20.gif,21.gif,22.gif,23.gif,24.gif,28.gif,29.gif,30.gif,33.gif,34.gif,35.gif,36.gif,37.gif,39.gif,40.gif,42.gif,44.gif,45.gif,46.gif,48.gif,50.gif,52.gif,54.gif,55.gif,57.gif,58.gif,59.gif,60.gif,61.gif,63.gif,64.gif,66.gif,67.gif,68.gif,69.gif,70.gif,72.gif,73.gif,75.gif,76.gif,77.gif,78.gif,80.gif,81.gif,82.gif,83.gif,86.gif,87.gif,88.gif,92.gif,93.gif,94.gif,95.gif,96.gif,97.gif,98.gif,99.gif,100.gif,101.gif,102.gif,103.gif,104.gif,105.gif,106.gif,108.gif,109.gif,110.gif,111.gif,112.gif,113.gif,115.gif,116.gif,117.gif,118.gif,119.gif,120.gif,122.gif,123.gif,124.gif,127.gif,129.gif,130.gif,131.gif,134.gif,135.gif,136.gif,138.gif,139.gif,141.gif,144.gif,146.gif,148.gif,149.gif,153.gif,154.gif,155.gif,157.gif,158.gif,159.gif,160.gif,161.gif,162.gif,164.gif,166.gif,167.gif,168.gif,169.gif,170.gif,171.gif,172.gif,173.gif,174.gif,175.gif,176.gif,177.gif,178.gif,181.gif,182.gif,183.gif,185.gif,186.gif,187.gif,188.gif,189.gif,190.gif,191.gif,192.gif,193.gif,195.gif,196.gif,197.gif,200.gif,201.gif,202.gif,203.gif,204.gif,205.gif,206.gif,207.gif,208.gif,209.gif,210.gif,211.gif,212.gif,213.gif,214.gif,215.gif,216.gif,217.gif,219.gif,220.gif,221.gif,222.gif,224.gif,225.gif,226.gif,227.gif,228.gif,230.gif,232.gif,233.gif,234.gif,235.gif,238.gif,240.gif,241.gif,243.gif,244.gif,245.gif,246.gif,247.gif,249.gif,250.gif,251.gif,253.gif', + passMessageClosed: false, + 'Prerequest Captcha': false, + 'PSAseen': [[]] }; return Config; @@ -472,12 +594,12 @@ CSS = { boards: "/*!\n\ - * Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome\n\ + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome\n\ * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)\n\ */\n\ @font-face {\n\ font-family: FontAwesome;\n\ - src: url('data:application/font-woff;base64,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') format('woff');\n\ + src: url('data:application/font-woff;base64,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') format('woff');\n\ font-weight: 400;\n\ font-style: normal;\n\ }\n\ @@ -1005,7 +1127,7 @@ boards: .fa-optin-monster:before {content: \"\\f23c\";}\n\ .fa-opencart:before {content: \"\\f23d\";}\n\ .fa-expeditedssl:before {content: \"\\f23e\";}\n\ -.fa-battery-4:before, .fa-battery-full:before {content: \"\\f240\";}\n\ +.fa-battery-4:before, .fa-battery:before, .fa-battery-full:before {content: \"\\f240\";}\n\ .fa-battery-3:before, .fa-battery-three-quarters:before {content: \"\\f241\";}\n\ .fa-battery-2:before, .fa-battery-half:before {content: \"\\f242\";}\n\ .fa-battery-1:before, .fa-battery-quarter:before {content: \"\\f243\";}\n\ @@ -1115,6 +1237,47 @@ boards: .fa-themeisle:before {content: \"\\f2b2\";}\n\ .fa-google-plus-circle:before, .fa-google-plus-official:before {content: \"\\f2b3\";}\n\ .fa-fa:before, .fa-font-awesome:before {content: \"\\f2b4\";}\n\ +.fa-handshake-o:before {content: \"\\f2b5\";}\n\ +.fa-envelope-open:before {content: \"\\f2b6\";}\n\ +.fa-envelope-open-o:before {content: \"\\f2b7\";}\n\ +.fa-linode:before {content: \"\\f2b8\";}\n\ +.fa-address-book:before {content: \"\\f2b9\";}\n\ +.fa-address-book-o:before {content: \"\\f2ba\";}\n\ +.fa-vcard:before, .fa-address-card:before {content: \"\\f2bb\";}\n\ +.fa-vcard-o:before, .fa-address-card-o:before {content: \"\\f2bc\";}\n\ +.fa-user-circle:before {content: \"\\f2bd\";}\n\ +.fa-user-circle-o:before {content: \"\\f2be\";}\n\ +.fa-user-o:before {content: \"\\f2c0\";}\n\ +.fa-id-badge:before {content: \"\\f2c1\";}\n\ +.fa-drivers-license:before, .fa-id-card:before {content: \"\\f2c2\";}\n\ +.fa-drivers-license-o:before, .fa-id-card-o:before {content: \"\\f2c3\";}\n\ +.fa-quora:before {content: \"\\f2c4\";}\n\ +.fa-free-code-camp:before {content: \"\\f2c5\";}\n\ +.fa-telegram:before {content: \"\\f2c6\";}\n\ +.fa-thermometer-4:before, .fa-thermometer:before, .fa-thermometer-full:before {content: \"\\f2c7\";}\n\ +.fa-thermometer-3:before, .fa-thermometer-three-quarters:before {content: \"\\f2c8\";}\n\ +.fa-thermometer-2:before, .fa-thermometer-half:before {content: \"\\f2c9\";}\n\ +.fa-thermometer-1:before, .fa-thermometer-quarter:before {content: \"\\f2ca\";}\n\ +.fa-thermometer-0:before, .fa-thermometer-empty:before {content: \"\\f2cb\";}\n\ +.fa-shower:before {content: \"\\f2cc\";}\n\ +.fa-bathtub:before, .fa-s15:before, .fa-bath:before {content: \"\\f2cd\";}\n\ +.fa-podcast:before {content: \"\\f2ce\";}\n\ +.fa-window-maximize:before {content: \"\\f2d0\";}\n\ +.fa-window-minimize:before {content: \"\\f2d1\";}\n\ +.fa-window-restore:before {content: \"\\f2d2\";}\n\ +.fa-times-rectangle:before, .fa-window-close:before {content: \"\\f2d3\";}\n\ +.fa-times-rectangle-o:before, .fa-window-close-o:before {content: \"\\f2d4\";}\n\ +.fa-bandcamp:before {content: \"\\f2d5\";}\n\ +.fa-grav:before {content: \"\\f2d6\";}\n\ +.fa-etsy:before {content: \"\\f2d7\";}\n\ +.fa-imdb:before {content: \"\\f2d8\";}\n\ +.fa-ravelry:before {content: \"\\f2d9\";}\n\ +.fa-eercast:before {content: \"\\f2da\";}\n\ +.fa-microchip:before {content: \"\\f2db\";}\n\ +.fa-snowflake-o:before {content: \"\\f2dc\";}\n\ +.fa-superpowers:before {content: \"\\f2dd\";}\n\ +.fa-wpexplorer:before {content: \"\\f2de\";}\n\ +.fa-meetup:before {content: \"\\f2e0\";}\n\ .fa::before {\n\ font-family: FontAwesome;\n\ font-weight: 400;\n\ @@ -1190,13 +1353,11 @@ boards: font: 13px sans-serif;\n\ outline: none;\n\ transition: color .25s, border-color .25s;\n\ - transition: color .25s, border-color .25s;\n\ }\n\ -.field::-moz-placeholder,\n\ -.field:hover::-moz-placeholder {\n\ - color: #AAA !important;\n\ - font-size: 13px !important;\n\ - opacity: 1.0 !important;\n\ +.field::-moz-placeholder {\n\ + color: #AAA;\n\ + font-size: 13px;\n\ + opacity: 1;\n\ }\n\ .captch-img:hover,\n\ .field:hover {\n\ @@ -1225,10 +1386,10 @@ a[href=\"javascript:;\"] {\n\ .warning {\n\ color: red;\n\ }\n\ -#boardNavDesktop, #boardNavMobile {\n\ +:root.sw-yotsuba #boardNavDesktop, :root.sw-yotsuba #boardNavMobile {\n\ display: none !important;\n\ }\n\ -:root.hide-bottom-board-list #boardNavDesktopFoot {\n\ +:root.hide-bottom-board-list $site$boardListBottom {\n\ display: none;\n\ }\n\ body.hasDropDownNav{\n\ @@ -1241,61 +1402,137 @@ body.hasDropDownNav{\n\ border-radius: 3px;\n\ padding: 0px 2px;\n\ }\n\ +[hidden] {\n\ + display: none !important;\n\ +}\n\ /* 4chan style fixes */\n\ -.opContainer, .op {\n\ - display: block !important;\n\ - overflow: visible !important;\n\ +/* overrides 4chan CSS on div.opContainer, div.op */\n\ +:root.sw-yotsuba .opContainer, :root.sw-yotsuba .op {\n\ + display: block;\n\ + overflow: visible;\n\ }\n\ -.reply > .file > .fileText {\n\ +:root.sw-yotsuba .reply > .file > .fileText {\n\ margin: 0 20px;\n\ }\n\ -.hashlink::before {\n\ - content: ' ';\n\ - visibility: hidden;\n\ -}\n\ -.inline + .hashlink,\n\ -[hidden] {\n\ - display: none !important;\n\ +:root.sw-yotsuba #arc-list span.quote {\n\ + color: #789922;\n\ }\n\ -.fileText a {\n\ +:root.sw-yotsuba .fileText a {\n\ unicode-bidi: -moz-isolate;\n\ unicode-bidi: -webkit-isolate;\n\ }\n\ -#g-recaptcha {\n\ +:root.sw-yotsuba #g-recaptcha {\n\ min-height: 78px;\n\ height: auto;\n\ }\n\ -:root:not(.js-enabled) #postForm {\n\ +:root.sw-yotsuba:not(.js-enabled) #postForm {\n\ display: table;\n\ }\n\ -#captchaContainerAlt td:nth-child(2) {\n\ +:root.sw-yotsuba #captchaContainerAlt td:nth-child(2) {\n\ display: table-cell !important;\n\ }\n\ -canvas#tegaki-canvas {\n\ +:root.sw-yotsuba canvas#tegaki-canvas {\n\ background: none;\n\ }\n\ /* Disable obnoxious captcha fade-in. */\n\ -body > div:last-of-type {\n\ +:root.sw-yotsuba > body > div:last-of-type {\n\ transition: none !important;\n\ }\n\ /* Fix captcha scrolling to top of page. */\n\ -body > div[style*=\" top: -10000px;\"] {\n\ +:root.sw-yotsuba > body > div[style*=\" top: -10000px;\"] {\n\ visibility: hidden !important;\n\ }\n\ +/* Make long filenames wrap properly: https://github.com/ccd0/4chan-x/issues/1082 */\n\ +:root.sw-yotsuba .post > .file {\n\ + /* currently nonstandard but may be added: https://lists.w3.org/Archives/Public/www-style/2016Mar/0352.html, https://bugzilla.mozilla.org/show_bug.cgi?id=1296042 */\n\ + word-break: break-word;\n\ +}\n\ +:root.sw-yotsuba:not(.ua-webkit):not(.ua-blink) .fileText {\n\ + word-wrap: break-word;\n\ + max-width: calc(100vw - 90px);\n\ +}\n\ +:root.sw-yotsuba > body.is_catalog .thread > a > img {\n\ + display: inline-block;\n\ +}\n\ +/* Links to NSFW boards */\n\ +:root.sw-yotsuba .nwsb {\n\ + display: inline;\n\ +}\n\ +:root.sw-yotsuba .fileText {\n\ + max-width: auto;\n\ + white-space: normal;\n\ +}\n\ /* Ads */\n\ -:root:not(.ads-loaded) .ad-cnt,\n\ -:root:not(.ads-loaded) .ad-plea,\n\ -:root:not(.ads-loaded) hr.abovePostForm,\n\ -:root:not(.ads-loaded) .ad-plea-bottom + hr {\n\ +:root.sw-yotsuba .ad-cnt > *, :root.sw-yotsuba .adg-rects > *, :root.sw-yotsuba .bsa-cnt {\n\ + height: auto !important;\n\ +}\n\ +:root.sw-yotsuba:not(.ads-loaded) hr.abovePostForm,\n\ +:root.sw-yotsuba:not(.ads-loaded) .adg-rects > hr,\n\ +:root.sw-yotsuba #adg-ol + hr,\n\ +:root.sw-yotsuba .danbo-slot:empty {\n\ display: none;\n\ }\n\ -hr + div.center:not(.ad-cnt):not(.topad):not(.middlead):not(.bottomad) {\n\ +:root.sw-yotsuba .adg-rects {\n\ + margin: 0;\n\ + font-size: 0;\n\ +}\n\ +:root.sw-yotsuba div.center[style] {\n\ display: none !important;\n\ }\n\ +/* Tinyboard / vichan conflicts */\n\ +#menu > .hide-thread-link {\n\ + width: auto;\n\ + height: auto;\n\ + overflow: visible;\n\ + background-image: none;\n\ +}\n\ +#menu label.entry {\n\ + display: block;\n\ +}\n\ +#fourchanx-settings label {\n\ + display: inline;\n\ +}\n\ +.intro a[href=\"javascript:;\"],\n\ +#menu a {\n\ + margin: 0;\n\ +}\n\ +.gal-buttons.gal-buttons a {\n\ + font-size: inherit;\n\ +}\n\ +:root.sw-tinyboard.fixed.top-header:not(.autohide) .boardlist,\n\ +:root.sw-tinyboard.fixed.top-header:not(.autohide) .bar.top {\n\ + position: static;\n\ +}\n\ +:root.sw-tinyboard.fixed.top-header:not(.autohide) div.pages.top {\n\ + top: auto;\n\ + bottom: 0;\n\ +}\n\ +:root.sw-tinyboard.fixed.top-header.autohide .boardlist,\n\ +:root.sw-tinyboard.fixed.top-header.autohide .bar.top {\n\ + z-index: 3;\n\ +}\n\ +/* Tinyboard site style conflicts */\n\ +:root[data-host=\"fufufu.moe\"].fixed.top-header:not(.autohide) div.pages.top {\n\ + top: 26px;\n\ + bottom: auto;\n\ +}\n\ +:root[data-host=\"merorin.com\"].fixed.top-header:not(.autohide) span.settings {\n\ + top: 26px;\n\ +}\n\ +:root[data-host=\"fufufu.moe\"]:not(.fixed) #header-bar {\n\ + margin-top: 38px;\n\ +}\n\ +:root[data-host=\"lainchan.org\"]:not(.fixed) #header-bar {\n\ + margin-top: 17px;\n\ +}\n\ +:root[data-host=\"smuglo.li\"]:not(.fixed) #header-bar {\n\ + margin-top: 8px;\n\ +}\n\ /* Anti-autoplay */\n\ audio.controls-added {\n\ display: block;\n\ margin: auto;\n\ + white-space: normal;\n\ }\n\ :root.anti-autoplay div.embed {\n\ position: static;\n\ @@ -1304,14 +1541,12 @@ audio.controls-added {\n\ text-align: center;\n\ }\n\ :root.anti-autoplay .autoplay-removed {\n\ - display: block !important;\n\ visibility: visible !important;\n\ min-width: 640px;\n\ - min-height: 390px;\n\ + min-height: 360px;\n\ }\n\ /* fixed, z-index */\n\ #overlay,\n\ -#fourchanx-settings,\n\ #qp, #ihover,\n\ #navlinks, .fixed #header-bar,\n\ :root.float #updater,\n\ @@ -1319,11 +1554,8 @@ audio.controls-added {\n\ #qr {\n\ position: fixed;\n\ }\n\ -#fourchanx-settings {\n\ - z-index: 999;\n\ -}\n\ #overlay {\n\ - z-index: 900;\n\ + z-index: 999;\n\ }\n\ #qp, #ihover {\n\ z-index: 60;\n\ @@ -1490,56 +1722,57 @@ audio.controls-added {\n\ #toggleMsgBtn {\n\ display: none !important;\n\ }\n\ -.current {\n\ +.current,\n\ +:root.sw-yotsuba div#boardNavDesktopFoot a.current {\n\ font-weight: bold;\n\ }\n\ @media (min-width: 1300px) {\n\ - :root.fixed:not(.centered-links) #header-bar {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #header-bar {\n\ white-space: nowrap;\n\ display: -webkit-flex;\n\ display: flex;\n\ -webkit-align-items: center;\n\ align-items: center;\n\ }\n\ - :root.fixed:not(.centered-links) #board-list {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #board-list {\n\ -webkit-flex: auto;\n\ flex: auto;\n\ }\n\ - :root.fixed:not(.centered-links) #full-board-list {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list {\n\ display: -webkit-flex;\n\ display: flex;\n\ }\n\ - :root.fixed:not(.centered-links) .hide-board-list-container {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) .hide-board-list-container {\n\ -webkit-flex: none;\n\ flex: none;\n\ margin-right: 5px;\n\ }\n\ - :root.fixed:not(.centered-links) #full-board-list > .boardList {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList {\n\ -webkit-flex: auto;\n\ flex: auto;\n\ display: -webkit-flex;\n\ display: flex;\n\ width: 0px; /* XXX Fixes Edge not shrinking the board list below default size when needed */\n\ }\n\ - :root.fixed:not(.centered-links) #full-board-list > .boardList > a,\n\ - :root.fixed:not(.centered-links) #full-board-list > .boardList > span:not(.space):not(.spacer) {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > a,\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > span:not(.space):not(.spacer) {\n\ -webkit-flex: none;\n\ flex: none;\n\ padding: .17em;\n\ margin: -.17em -.32em;\n\ }\n\ - :root.fixed:not(.centered-links) #full-board-list > .boardList > span {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > span {\n\ pointer-events: none;\n\ }\n\ - :root.fixed:not(.centered-links) #full-board-list > .boardList > span.space {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > span.space {\n\ -webkit-flex: 0 .63 .63em;\n\ flex: 0 .63 .63em;\n\ }\n\ - :root.fixed:not(.centered-links) #full-board-list > .boardList > span.spacer {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > span.spacer {\n\ -webkit-flex: 0 .38 .38em;\n\ flex: 0 .38 .38em;\n\ }\n\ - :root.fixed:not(.centered-links) #shortcuts {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #shortcuts {\n\ float: initial;\n\ -webkit-flex: none;\n\ flex: none;\n\ @@ -1566,6 +1799,9 @@ audio.controls-added {\n\ left: 0;\n\ visibility: visible;\n\ }\n\ +#notifications:empty {\n\ + display: none;\n\ +}\n\ :root.fixed.top-header:not(.gallery-open) #header-bar #notifications,\n\ :root.fixed.top-header #header-bar.autohide #notifications {\n\ position: absolute;\n\ @@ -1629,6 +1865,8 @@ audio.controls-added {\n\ }\n\ #overlay {\n\ background-color: rgba(0, 0, 0, .5);\n\ + display: -webkit-flex;\n\ + display: flex;\n\ top: 0;\n\ left: 0;\n\ height: 100%;\n\ @@ -1643,16 +1881,16 @@ audio.controls-added {\n\ width: 900px;\n\ max-width: 100%;\n\ margin: auto;\n\ - padding: 3px;\n\ - top: 50%;\n\ - left: 50%;\n\ - -moz-transform: translate(-50%, -50%);\n\ - -webkit-transform: translate(-50%, -50%);\n\ - transform: translate(-50%, -50%);\n\ + padding: 5px;\n\ + display: -webkit-flex;\n\ + display: flex;\n\ + -webkit-flex-direction: column;\n\ + flex-direction: column;\n\ }\n\ #fourchanx-settings > nav {\n\ - padding: 2px 2px 0;\n\ - height: 15px;\n\ + padding: 2px 2px 8px;\n\ + display: -webkit-flex;\n\ + display: flex;\n\ }\n\ #fourchanx-settings > nav a {\n\ text-decoration: underline;\n\ @@ -1663,20 +1901,16 @@ audio.controls-added {\n\ margin: 0;\n\ }\n\ .section-container {\n\ + -webkit-flex: 1;\n\ + flex: 1;\n\ + position: relative;\n\ overflow: auto;\n\ - position: absolute;\n\ - top: 2.1em;\n\ - right: 5px;\n\ - bottom: 5px;\n\ - left: 5px;\n\ padding-right: 5px;\n\ + overscroll-behavior: contain;\n\ }\n\ .sections-list {\n\ - padding: 0 3px;\n\ - float: left;\n\ -}\n\ -.credits {\n\ - float: right;\n\ + -webkit-flex: 1;\n\ + flex: 1;\n\ }\n\ .export, .import, .reset {\n\ cursor: pointer;\n\ @@ -1743,6 +1977,9 @@ div[data-checked=\"false\"] > .suboption-list {\n\ border-left: 1px solid;\n\ border-bottom: 1px solid;\n\ }\n\ +#fourchanx-settings .section-main p {\n\ + margin: .5em 0 0;\n\ +}\n\ .section-filter ul {\n\ padding: 0;\n\ }\n\ @@ -1756,8 +1993,20 @@ div[data-checked=\"false\"] > .suboption-list {\n\ .section-main a, .section-filter a, .section-advanced a {\n\ text-decoration: underline;\n\ }\n\ +#sauce-doc-expand:not(:checked) ~ #sauce-doc {\n\ + max-height: 130px;\n\ + overflow: auto;\n\ +}\n\ +#sauce-doc > label {\n\ + float: right;\n\ + margin: 0 5px;\n\ +}\n\ +/* XXX for OneeChan */\n\ +#sauce-doc-expand + .riceCheck {\n\ + display: none;\n\ +}\n\ .section-sauce textarea {\n\ - height: 350px;\n\ + height: 430px;\n\ }\n\ .section-advanced .field[name=\"boardnav\"] {\n\ width: 100%;\n\ @@ -1765,7 +2014,9 @@ div[data-checked=\"false\"] > .suboption-list {\n\ .section-advanced textarea {\n\ height: 150px;\n\ }\n\ -.section-advanced textarea[name=\"archiveLists\"] {\n\ +.section-advanced textarea[name=\"archiveLists\"],\n\ +.section-advanced textarea[name=\"externalCatalogURLs\"],\n\ +.section-advanced textarea[name=\"knownBanners\"] {\n\ height: 75px;\n\ }\n\ .section-advanced .archive-cell {\n\ @@ -1784,6 +2035,12 @@ div[data-checked=\"false\"] > .suboption-list {\n\ font-style: normal;\n\ font-size: 11px;\n\ }\n\ +.favicon-preview > img {\n\ + vertical-align: middle;\n\ +}\n\ +.favicon-preview > img:nth-of-type(3n+1) {\n\ + margin-left: 4px;\n\ +}\n\ .section-keybinds .field {\n\ font-family: monospace;\n\ }\n\ @@ -1799,8 +2056,8 @@ div[data-checked=\"false\"] > .suboption-list {\n\ }\n\ #fourchanx-settings textarea {\n\ font-family: monospace;\n\ - min-width: 100%;\n\ - max-width: 100%;\n\ + width: 100%;\n\ + resize: vertical;\n\ }\n\ #fourchanx-settings code {\n\ color: #000;\n\ @@ -1814,8 +2071,8 @@ div[data-checked=\"false\"] > .suboption-list {\n\ #fourchanx-settings p {\n\ margin: 1em 0px;\n\ }\n\ -.unscroll {\n\ - overflow: hidden;\n\ +#fourchanx-settings table {\n\ + margin: auto;\n\ }\n\ /* Index */\n\ :root.index-loading .navLinks:not(.json-index),\n\ @@ -1853,9 +2110,26 @@ div[data-checked=\"false\"] > .suboption-list {\n\ #index-search:not([data-searching]) + #index-search-clear {\n\ display: none;\n\ }\n\ -#index-mode, #index-sort, #index-size {\n\ +#index-options {\n\ float: right;\n\ }\n\ +#lastlong-options {\n\ + display: inline-block;\n\ + vertical-align: middle;\n\ + height: 28px;\n\ + margin: -14px 0;\n\ +}\n\ +#lastlong-options > input {\n\ + padding: 0;\n\ + border: 0 !important;\n\ + text-align: center;\n\ + background: transparent;\n\ + display: block;\n\ + font-size: 12px;\n\ + height: 12px;\n\ + width: 30px;\n\ + margin: 1px 0;\n\ +}\n\ .summary {\n\ text-decoration: none;\n\ }\n\ @@ -1864,34 +2138,78 @@ div[data-checked=\"false\"] > .suboption-list {\n\ text-align: center;\n\ }\n\ .catalog-thread {\n\ - display: -webkit-inline-flex;\n\ - display: inline-flex;\n\ - text-align: left;\n\ - -webkit-flex-direction: column;\n\ - flex-direction: column;\n\ - -webkit-align-items: center;\n\ - align-items: center;\n\ - margin: 0 2px 5px;\n\ + display: inline-block;\n\ + -moz-box-sizing: border-box;\n\ + box-sizing: border-box;\n\ + border: 1px solid transparent;\n\ word-wrap: break-word;\n\ vertical-align: top;\n\ position: relative;\n\ }\n\ -.catalog-thread > a {\n\ - flex-shrink: 0;\n\ - -webkit-flex-shrink: 0;\n\ - position: relative;\n\ +/* overrides 4chan CSS on div.thread */\n\ +.catalog-thread.catalog-thread {\n\ + margin: 2px;\n\ }\n\ -.catalog-small .catalog-thread {\n\ +.catalog-small > .catalog-thread {\n\ width: 165px;\n\ - max-height: 320px;\n\ + height: 320px;\n\ }\n\ -.catalog-large .catalog-thread {\n\ +.catalog-large > .catalog-thread {\n\ width: 270px;\n\ - max-height: 410px;\n\ + height: 410px;\n\ +}\n\ +:root.catalog-hover-expand .catalog-thread:hover {\n\ + z-index: 1;\n\ +}\n\ +.catalog-container {\n\ + position: absolute;\n\ + top: -4px;\n\ + left: 0;\n\ + right: 0;\n\ + bottom: 0;\n\ +}\n\ +.catalog-container:not(:hover),\n\ +:root:not(.catalog-hover-expand) .catalog-container {\n\ + overflow: hidden;\n\ +}\n\ +.catalog-post {\n\ + position: absolute;\n\ + top: 4px;\n\ + left: 0;\n\ + right: 0;\n\ + border: 1px solid transparent;\n\ + padding-top: 20px;\n\ +}\n\ +/* overrides inline CSS from Index.cb.hoverAdjust */\n\ +:root:not(.catalog-hover-expand) .catalog-post {\n\ + left: 0 !important;\n\ + right: 0 !important;\n\ +}\n\ +/* overrides 4chan CSS on div.post */\n\ +.catalog-post.catalog-post {\n\ + margin: -21px -1px -1px;\n\ + overflow: visible;\n\ +}\n\ +.catalog-thread.noFile > * > .catalog-post {\n\ + margin-top: -7px;\n\ + padding-top: 6px;\n\ +}\n\ +:root.catalog-hover-expand .catalog-container:hover > .catalog-post {\n\ + margin-left: -61px;\n\ + margin-right: -61px;\n\ +}\n\ +:root.catalog-hover-expand .catalog-container:hover > * > :not(.catalog-replies) {\n\ + padding-left: 2px;\n\ + padding-right: 2px;\n\ +}\n\ +.catalog-link {\n\ + display: block;\n\ + position: relative;\n\ }\n\ .catalog-thumb {\n\ border-radius: 2px;\n\ box-shadow: 0 0 5px rgba(0, 0, 0, .25);\n\ + vertical-align: top;\n\ }\n\ .catalog-thumb.spoiler-file {\n\ width: 100px;\n\ @@ -1916,45 +2234,144 @@ div[data-checked=\"false\"] > .suboption-list {\n\ padding-left: 2px;\n\ }\n\ .catalog-stats > .menu-button {\n\ - text-align: center;\n\ font-weight: normal;\n\ }\n\ .catalog-stats > .menu-button > i::before {\n\ line-height: 11px;\n\ }\n\ .catalog-stats {\n\ - -webkit-flex-shrink: 0;\n\ - flex-shrink: 0;\n\ - cursor: help;\n\ font-size: 10px;\n\ font-weight: 700;\n\ - margin-top: 2px;\n\ + padding-top: 2px;\n\ }\n\ -.catalog-thread > .subject {\n\ - -webkit-flex-shrink: 0;\n\ - flex-shrink: 0;\n\ - -webkit-align-self: stretch;\n\ - align-self: stretch;\n\ - font-weight: 700;\n\ - line-height: 1;\n\ - text-align: center;\n\ +.catalog-stats > [title] {\n\ + cursor: help;\n\ }\n\ -.catalog-thread > .comment {\n\ - -webkit-flex-shrink: 1;\n\ - flex-shrink: 1;\n\ - -webkit-align-self: stretch;\n\ - align-self: stretch;\n\ +.catalog-post > .postMessage {\n\ + margin: 0;\n\ + padding-bottom: .3em;\n\ +}\n\ +.catalog-container:not(:hover) > * > .file,\n\ +.catalog-container:not(:hover) > * > .postInfo > :not(.subject),\n\ +.catalog-container:not(:hover) > * > .catalog-replies,\n\ +.catalog-container:not(:hover) .extra-linebreak,\n\ +.catalog-container:not(:hover) .abbr,\n\ +:root:not(.catalog-hover-expand) .catalog-container > * > .file,\n\ +:root:not(.catalog-hover-expand) .catalog-container > * > .postInfo > :not(.subject),\n\ +:root:not(.catalog-hover-expand) .catalog-container > * > .catalog-replies,\n\ +:root:not(.catalog-hover-expand) .catalog-container .extra-linebreak,\n\ +:root:not(.catalog-hover-expand) .catalog-container .abbr,\n\ +.catalog-thread > .catalog-container > :not(.catalog-post),\n\ +.catalog-post > .file > :not(.fileText),\n\ +.catalog-post > * > .fileText > :not(:first-child),\n\ +.catalog-post > .postInfo > :not(.subject):not(.nameBlock):not(.dateTime),\n\ +.catalog-post > .postInfo > .nameBlock > .contact-links,\n\ +.catalog-post > * > * > .posteruid,\n\ +.catalog-post > * > * > .postJumper,\n\ +:root.bottom-backlinks .catalog-post > .container,\n\ +.post:not(.catalog-post) > .catalog-link,\n\ +.post:not(.catalog-post) > .catalog-stats,\n\ +.post:not(.catalog-post) > .catalog-replies {\n\ + display: none;\n\ +}\n\ +.catalog-post > .file {\n\ + position: absolute;\n\ + left: 0;\n\ + right: 0;\n\ + top: 0;\n\ + min-height: 20px;\n\ + background-color: inherit;\n\ +}\n\ +.catalog-post > * > .fileText {\n\ + position: relative;\n\ + padding: 2px;\n\ + background-color: inherit;\n\ +}\n\ +.catalog-small .catalog-post > * .fileText {\n\ + font-size: 10px;\n\ +}\n\ +.catalog-post > * > .fileText:not(:hover) {\n\ + white-space: nowrap;\n\ overflow: hidden;\n\ - text-align: center;\n\ + text-overflow: ellipsis;\n\ }\n\ -/* /tg/ dice rolls */\n\ -.board_tg .catalog-thread > .comment > b {\n\ - font-weight: normal;\n\ +.catalog-post > * > .fileText:hover {\n\ + z-index: 1;\n\ }\n\ -.catalog-code {\n\ - background-color: #FFF;\n\ +/* overrides 4chan CSS on div.post div.postInfo */\n\ +.catalog-post > .postInfo.postInfo {\n\ + width: auto;\n\ +}\n\ +.catalog-post > * > .subject {\n\ + display: block;\n\ +}\n\ +.catalog-post > * > .dateTime {\n\ display: inline-block;\n\ + font-style: italic;\n\ +}\n\ +:root.catalog-hover-expand .catalog-container:hover > * > * > .nameBlock,\n\ +:root.catalog-hover-expand .catalog-container:hover > * > * > .dateTime,\n\ +:root.catalog-hover-expand .catalog-container:hover > * > .postMessage:not(:empty) {\n\ + padding-top: .3em;\n\ +}\n\ +.catalog-post .extra-linebreak {\n\ + content: ''; /* makes this work in Blink/WebKit */\n\ + display: block;\n\ + margin-top: .3em;\n\ +}\n\ +.catalog-reply {\n\ + text-align: left;\n\ + white-space: nowrap;\n\ + border-top: 1px solid transparent;\n\ + display: -webkit-flex;\n\ + display: flex;\n\ + -webkit-flex-direction: row;\n\ + flex-direction: row;\n\ + -webkit-align-items: stretch;\n\ + align-items: stretch;\n\ +}\n\ +.catalog-reply > * {\n\ + padding: 3px;\n\ + overflow: hidden;\n\ + -webkit-flex: none;\n\ + flex: none;\n\ +}\n\ +.catalog-reply > span {\n\ + font-style: italic;\n\ + font-weight: bold;\n\ +}\n\ +.catalog-reply-excerpt {\n\ + -webkit-flex: 1 1 auto;\n\ + flex: 1 1 auto;\n\ +}\n\ +.catalog-post .prettyprinted {\n\ max-width: 100%;\n\ + -moz-box-sizing: border-box;\n\ + box-sizing: border-box;\n\ +}\n\ +.catalog-post .MathJax_Display {\n\ + text-align: center !important;\n\ +}\n\ +.catalog-container:not(:hover) .exif,\n\ +:root:not(.catalog-hover-expand) .catalog-container .exif {\n\ + display: none !important;\n\ +}\n\ +.catalog-post > * > .exif {\n\ + border-collapse: collapse;\n\ +}\n\ +:root.catalog-hover-expand .catalog-container:hover .exif[style*=\"display: block;\"] {\n\ + display: inline-block !important;\n\ +}\n\ +.catalog-post > * > .exif,\n\ +.catalog-post > * > .exif > tbody {\n\ + background-color: inherit;\n\ +}\n\ +.catalog-post > * > .exif,\n\ +.catalog-post > * > .exif td {\n\ + min-width: 0;\n\ +}\n\ +.catalog-post > * > .exif td {\n\ + padding-top: 1px;\n\ }\n\ :root.hats-enabled .catalog-thread::after {\n\ content: '';\n\ @@ -1962,37 +2379,56 @@ div[data-checked=\"false\"] > .suboption-list {\n\ position: absolute;\n\ background-size: contain;\n\ }\n\ -:root.hats-enabled .catalog-small .catalog-thread::after {\n\ - left: -10px;\n\ - top: -65px;\n\ - width: 100px;\n\ - height: 100px;\n\ +:root.hats-enabled .catalog-small > .catalog-thread::after {\n\ + left: -8px;\n\ + top: -59px;\n\ + width: 96px;\n\ + height: 96px;\n\ +}\n\ +:root.hats-enabled:not(.werkTyme) .catalog-small > .catalog-thread:not(.noFile)::after {\n\ + left: calc(67px - .3px * var(--tn-w));\n\ }\n\ -:root.hats-enabled .catalog-large .catalog-thread::after {\n\ +:root.hats-enabled .catalog-large > .catalog-thread::after {\n\ left: -15px;\n\ - top: -105px;\n\ + top: -98px;\n\ width: 160px;\n\ height: 160px;\n\ }\n\ +:root.hats-enabled:not(.werkTyme) .catalog-large > .catalog-thread:not(.noFile)::after {\n\ + left: calc(110px - .5px * var(--tn-w));\n\ +}\n\ +/* Copy Text Link's textarea element */\n\ +textarea.copy-text-element {\n\ + height: 0;\n\ + width: 0;\n\ + position: absolute;\n\ + top: -10000px;\n\ +}\n\ /* Announcement Hiding */\n\ -:root.hide-announcement #globalMessage {\n\ +:root.hide-announcement $site$psa {\n\ display: none;\n\ }\n\ -span.hide-announcement {\n\ - font-size: 11px;\n\ - position: relative;\n\ - bottom: 5px;\n\ -}\n\ -.globalMessage, h2, h3 {\n\ - color: inherit !important;\n\ - font-size: 13px;\n\ - font-weight: 100;\n\ +.hide-announcement-button {\n\ + opacity: 0.4;\n\ + float: left;\n\ }\n\ /* Unread */\n\ -#unread-line {\n\ +.unread-line {\n\ margin: 0;\n\ border-color: rgb(255,0,0);\n\ }\n\ +.unread-line + br {\n\ + display: none;\n\ +}\n\ +.unread-mark-read {\n\ + float: right;\n\ + clear: both;\n\ + width: 100%;\n\ + text-align: right;\n\ +}\n\ +:not(.unread-thread) > .unread-mark-read {\n\ + display: none;\n\ +}\n\ /* Thread Updater */\n\ #updater {\n\ background: none;\n\ @@ -2001,10 +2437,11 @@ span.hide-announcement {\n\ }\n\ #updater > .move {\n\ position: absolute;\n\ - left: 0;\n\ top: -5px;\n\ - width: 100%;\n\ - height: 5px;\n\ + bottom: -5px;\n\ + left: -5px;\n\ + right: -5px;\n\ + z-index: -1;\n\ }\n\ #updater > div:last-child {\n\ text-align: center;\n\ @@ -2072,12 +2509,11 @@ span.hide-announcement {\n\ -webkit-flex-direction: row;\n\ flex-direction: row;\n\ }\n\ +#watched-threads .watcher-page,\n\ #watched-threads .watcher-unread {\n\ -webkit-flex: 0 0 auto;\n\ flex: 0 0 auto;\n\ -}\n\ -#watched-threads .watcher-unread::after {\n\ - content: \"\\00a0\";\n\ + margin-right: 2px;\n\ }\n\ #watched-threads .watcher-title {\n\ overflow: hidden;\n\ @@ -2085,12 +2521,15 @@ span.hide-announcement {\n\ -webkit-flex: 0 1 auto;\n\ flex: 0 1 auto;\n\ }\n\ +#watched-threads .watcher-title:not(:first-child) {\n\ + margin-left: 2px;\n\ +}\n\ +.replies-quoting-you > a, #watcher-link.replies-quoting-you, .last-page > a > .watcher-page {\n\ + color: #F00;\n\ +}\n\ #thread-watcher a {\n\ text-decoration: none;\n\ }\n\ -:root:not(.toggleable-watcher) #thread-watcher .move > .close {\n\ - display: none;\n\ -}\n\ #thread-watcher .move > .close {\n\ position: absolute;\n\ right: 0px;\n\ @@ -2127,17 +2566,24 @@ span.hide-announcement {\n\ cursor: pointer;\n\ }\n\ /* Quote */\n\ -.catalog-thread > .comment > span.quote, #arc-list span.quote {\n\ - color: #789922;\n\ +.hashlink::before {\n\ + content: ' ';\n\ + visibility: hidden;\n\ }\n\ -:root:not(.catalog-mode) .deadlink {\n\ +.inline + .hashlink {\n\ + display: none !important;\n\ +}\n\ +:root.resurrect-quotes .deadlink {\n\ text-decoration: none !important;\n\ }\n\ +.catalog-post .qmark-ct {\n\ + display: none;\n\ +}\n\ .backlink.deadlink:not(.forwardlink),\n\ .quotelink.deadlink:not(.forwardlink) {\n\ text-decoration: underline !important;\n\ }\n\ -.inlined {\n\ +:root:not(.catalog-mode) .inlined {\n\ opacity: .5;\n\ }\n\ #qp input, .forwarded {\n\ @@ -2158,11 +2604,25 @@ span.hide-announcement {\n\ .postNum + .container::before {\n\ content: \" \";\n\ }\n\ +:root.bottom-backlinks .container {\n\ + display: block;\n\ + clear: both;\n\ + margin: 0 4px;\n\ +}\n\ +:root.bottom-backlinks .backlink {\n\ + font-size: 90%;\n\ +}\n\ .inline {\n\ border: 1px solid;\n\ display: table;\n\ margin: 2px 0;\n\ }\n\ +.container ~ .inline {\n\ + margin-left: 20px;\n\ +}\n\ +:root.catalog-mode .inline {\n\ + display: none;\n\ +}\n\ .inline .post {\n\ border: 0 !important;\n\ background-color: transparent !important;\n\ @@ -2200,7 +2660,7 @@ span.hide-announcement {\n\ .expanded-image > .post > .file > .fileThumb > img[data-md5] {\n\ display: none;\n\ }\n\ -.full-image {\n\ +.full-image[data-file-i-d] {\n\ display: none;\n\ cursor: pointer;\n\ }\n\ @@ -2230,6 +2690,13 @@ span.hide-announcement {\n\ .fileThumb > .warning {\n\ clear: both;\n\ }\n\ +#ihover {\n\ + pointer-events: none;\n\ + /* XXX https://code.google.com/p/chromium/issues/detail?id=168840, https://bugs.webkit.org/show_bug.cgi?id=94158 */\n\ + max-height: 95vh;\n\ + max-height: calc(100vh - 25px);\n\ + max-width: 100vw;\n\ +}\n\ /* WEBM Metadata */\n\ .webm-title > a::before {\n\ content: \"title\";\n\ @@ -2262,22 +2729,29 @@ input[name=\"Default Volume\"] {\n\ margin: 0px;\n\ }\n\ /* Fappe and Werk Tyme */\n\ -:root.fappeTyme .thread > .noFile,\n\ -:root.fappeTyme .threadContainer > .noFile {\n\ +:root.fappeTyme $site$replyOriginal.noFile,\n\ +:root.fappeTyme $site$replyOriginal.noFile + br {\n\ display: none;\n\ }\n\ -:root.werkTyme .postContainer:not(.noFile) .fileThumb,\n\ +:root.werkTyme $site$thumbLink,\n\ +:root.werkTyme $site$file$thumb,\n\ :root.werkTyme .catalog-thumb:not(.deleted-file):not(.no-file),\n\ :root:not(.werkTyme) .werkTyme-filename {\n\ display: none;\n\ }\n\ .werkTyme-filename {\n\ font-weight: bold;\n\ + font-size: 110%;\n\ }\n\ -:root.werkTyme .catalog-thread > a {\n\ +:root.werkTyme .catalog-link {\n\ + box-shadow: 0 0 5px rgba(0, 0, 0, .25);\n\ + padding: 8px;\n\ text-align: center;\n\ - -webkit-align-self: stretch;\n\ - align-self: stretch;\n\ +}\n\ +:root.werkTyme .catalog-thumb {\n\ + box-shadow: none;\n\ + padding: 0;\n\ + vertical-align: middle;\n\ }\n\ .indicator {\n\ background: rgba(255,0,0,0.8);\n\ @@ -2308,41 +2782,46 @@ input[name=\"Default Volume\"] {\n\ .qphl {\n\ outline: 2px solid rgba(216, 94, 49, .8);\n\ }\n\ -:root.highlight-you .quotesYou.opContainer,\n\ -:root.highlight-you .quotesYou > .reply {\n\ +:root.highlight-you .quotesYou$site$highlightable$op,\n\ +:root.highlight-you .quotesYou$site$highlightable$reply {\n\ border-left: 3px solid rgba(221, 0, 0, .8);\n\ }\n\ -:root.highlight-own .yourPost.opContainer,\n\ -:root.highlight-own .yourPost > .reply {\n\ +:root.highlight-own .yourPost$site$highlightable$op,\n\ +:root.highlight-own .yourPost$site$highlightable$reply {\n\ border-left: 3px dashed rgba(221, 0, 0, .8);\n\ }\n\ -.filter-highlight.opContainer,\n\ -.filter-highlight > .reply {\n\ +.filter-highlight$site$highlightable$op,\n\ +.filter-highlight$site$highlightable$reply {\n\ box-shadow: inset 5px 0 rgba(221, 0, 0, .5);\n\ }\n\ -:root.highlight-own .yourPost > div.sideArrows,\n\ -:root.highlight-you .quotesYou > div.sideArrows,\n\ -.filter-highlight > div.sideArrows {\n\ +:root.highlight-own .yourPost > $site$sideArrows,\n\ +:root.highlight-you .quotesYou > $site$sideArrows,\n\ +.filter-highlight > $site$sideArrows {\n\ color: rgba(221, 0, 0, .8);\n\ }\n\ -:root.highlight-own .yourPost.opContainer::after,\n\ -:root.highlight-you .quotesYou.opContainer::after,\n\ -.filter-highlight.opContainer::after {\n\ +:root.highlight-own .yourPost$site$highlightable$op::after,\n\ +:root.highlight-you .quotesYou$site$highlightable$op::after,\n\ +.filter-highlight$site$highlightable$op::after {\n\ content: \"\";\n\ display: block;\n\ clear: both;\n\ }\n\ -.filter-highlight .catalog-thumb,\n\ -.filter-highlight .werkTyme-filename {\n\ +:root:not(.werkTyme) .catalog-thread.filter-highlight .catalog-thumb,\n\ +:root.werkTyme .catalog-thread.filter-highlight:not(:hover),\n\ +:root.werkTyme:not(.catalog-hover-expand) .catalog-thread.filter-highlight,\n\ +:root.werkTyme.catalog-hover-expand .catalog-thread.filter-highlight > .catalog-container:hover > .catalog-post,\n\ +:root.catalog $site$catalog$thread.filter-highlight$site$highlightable$catalog {\n\ box-shadow: 0 0 3px 3px rgba(255, 0, 0, .5);\n\ }\n\ -.catalog-thread.watched .catalog-thumb,\n\ -.catalog-thread.watched .werkTyme-filename {\n\ +:root:not(.werkTyme) .catalog-thread.watched .catalog-thumb,\n\ +:root:root.werkTyme .catalog-thread.watched:not(:hover),\n\ +:root:root.werkTyme:not(.catalog-hover-expand) .catalog-thread.watched,\n\ +:root.werkTyme.catalog-hover-expand .catalog-thread.watched > .catalog-container:hover > .catalog-post {\n\ border: 2px solid rgba(255, 0, 0, .75);\n\ }\n\ /* Spoiler text */\n\ -:root.reveal-spoilers s,\n\ -:root.reveal-spoilers s > a {\n\ +:root.reveal-spoilers $site$spoiler,\n\ +:root.reveal-spoilers $site$spoiler > a {\n\ color: white !important;\n\ }\n\ :root.reveal-spoilers .removed-spoiler::before {\n\ @@ -2358,6 +2837,13 @@ input[name=\"Default Volume\"] {\n\ margin-right: 4px;\n\ padding: 2px;\n\ }\n\ +$site$infoRoot a.hide-reply-button {\n\ + margin-right: 6px;\n\ + padding: 0;\n\ +}\n\ +.replacedSideArrows {\n\ + float: left;\n\ +}\n\ .hide-thread-button:not(:hover),\n\ .hide-reply-button:not(:hover) {\n\ opacity: 0.4;\n\ @@ -2369,19 +2855,41 @@ input[name=\"Default Volume\"] {\n\ }\n\ .hide-thread-button {\n\ margin-top: -1px;\n\ + width: 11px;\n\ }\n\ -.stub ~ * {\n\ +.stub ~ :not(.threadDivider) {\n\ display: none !important;\n\ }\n\ .stub input {\n\ display: inline-block;\n\ }\n\ -.thread[hidden] + hr {\n\ +$site$thread[hidden] + hr {\n\ + display: none;\n\ +}\n\ +:root.reply-hide $site$sideArrows {\n\ display: none;\n\ }\n\ -:root.reply-hide div.sideArrows {\n\ +:root.sw-yotsuba.thread-hide .party-hat {\n\ + left: 19px;\n\ +}\n\ +/* Anonymize */\n\ +:root.anonymize $site$info$name,\n\ +:root.sw-yotsuba.anonymize .post-author:not([class*=capcode]) {\n\ + font-size: 0;\n\ +}\n\ +:root.anonymize $site$info$tripcode,\n\ +:root.sw-yotsuba.anonymize .n-pu {\n\ display: none;\n\ }\n\ +:root.anonymize $site$info$name::before,\n\ +:root.sw-yotsuba.anonymize .post-author:not([class*=capcode])::before {\n\ + content: \"Anonymous\";\n\ + font-size: 10pt;\n\ +}\n\ +:root.sw-yotsuba.anonymize .flashListing .name::before,\n\ +:root.sw-yotsuba.anonymize .post-last > .post-author:not([class*=capcode])::before {\n\ + font-size: 9pt;\n\ +}\n\ /* QR */\n\ :root.hide-original-post-form #togglePostFormLink,\n\ #qr.autohide:not(.focus):not(:hover):not(:active) > form,\n\ @@ -2464,8 +2972,8 @@ input[name=\"Default Volume\"] {\n\ #qr.reply-to-thread input[data-name=\"sub\"]:not(.force-show),\n\ body:not(.board_f) #qr select[name=\"filetag\"],\n\ #qr.reply-to-thread select[name=\"filetag\"],\n\ -body:not(.board_jp) #sjis-toggle,\n\ -body:not(.board_sci) #tex-preview-button,\n\ +#qr:not(.has-sjis) #sjis-toggle,\n\ +#qr:not(.has-math) #tex-preview-button,\n\ #qr.tex-preview .textarea > :not(#tex-preview),\n\ #qr:not(.tex-preview) #tex-preview {\n\ display: none;\n\ @@ -2506,11 +3014,12 @@ input.field.tripped:not(:hover):not(:focus) {\n\ text-shadow: none !important;\n\ }\n\ #qr textarea {\n\ - min-width: 100%;\n\ + min-width: 300px;\n\ resize: both;\n\ }\n\ .field {\n\ -moz-box-sizing: border-box;\n\ + box-sizing: border-box;\n\ margin: 0px;\n\ padding: 2px 4px 3px;\n\ }\n\ @@ -2518,22 +3027,6 @@ input.field.tripped:not(:hover):not(:focus) {\n\ position: relative;\n\ top: 2px;\n\ }\n\ -/* Recaptcha v1 */\n\ -.captcha-img {\n\ - margin: 0px;\n\ - text-align: center;\n\ - background-image: #fff;\n\ - font-size: 0px;\n\ - min-height: 59px;\n\ - min-width: 302px;\n\ -}\n\ -.captcha-input {\n\ - width: 100%;\n\ - margin: 1px 0 0;\n\ -}\n\ -#qr.captcha-v1 #qr-captcha-iframe {\n\ - display: none;\n\ -}\n\ /* Recaptcha v2 */\n\ #qr .captcha-root {\n\ position: relative;\n\ @@ -2542,14 +3035,18 @@ input.field.tripped:not(:hover):not(:focus) {\n\ margin: auto;\n\ width: 304px;\n\ }\n\ -/* scrollable with scroll bar hidden; prevents scroll on space press */\n\ -:root.ua-blink #qr .captcha-container > div {\n\ +/* XXX scrollable with scroll bar hidden; prevents scroll on space press */\n\ +:root.ua-blink #qr .captcha-container > div,\n\ +:root.ua-edge #qr .captcha-container > div {\n\ overflow: hidden;\n\ }\n\ -:root.ua-blink #qr .captcha-container > div > div:first-of-type {\n\ +:root.ua-blink #qr .captcha-container > div > div:first-of-type,\n\ +:root.ua-edge #qr .captcha-container > div > div:first-of-type {\n\ overflow-y: scroll;\n\ overflow-x: hidden;\n\ - padding-right: 15px;\n\ + padding-right: 30px;\n\ + height: 99%;\n\ + width: 100%;\n\ }\n\ #qr .captcha-counter {\n\ display: block;\n\ @@ -2563,6 +3060,7 @@ input.field.tripped:not(:hover):not(:focus) {\n\ }\n\ #qr .captcha-counter > a {\n\ pointer-events: auto;\n\ + display: inline-block; /* XXX https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8851747/ */\n\ }\n\ #qr:not(.captcha-open) .captcha-counter > a {\n\ display: block;\n\ @@ -2776,6 +3274,7 @@ input[type=\"checkbox\"]:checked ~ .checkbox-letter {\n\ }\n\ .qr-preview {\n\ -moz-box-sizing: border-box;\n\ + box-sizing: border-box;\n\ counter-increment: thumbnails;\n\ cursor: move;\n\ display: inline-block;\n\ @@ -2856,7 +3355,8 @@ a:only-of-type > .remove {\n\ position: absolute;\n\ bottom: 20px;\n\ right: 10px;\n\ - -moz-transform: translateY(-50%);\n\ + -webkit-transform: translateY(-50%);\n\ + transform: translateY(-50%);\n\ }\n\ .textarea {\n\ position: relative;\n\ @@ -2875,6 +3375,13 @@ a:only-of-type > .remove {\n\ #char-count.warning {\n\ color: red;\n\ }\n\ +#split-post {\n\ + font-size: 8pt;\n\ + position: absolute;\n\ + bottom: 2px;\n\ + left: 2px;\n\ + cursor: pointer;\n\ +}\n\ /* Menu */\n\ .menu-button:not(.fa-bars) {\n\ display: inline-block;\n\ @@ -2889,7 +3396,7 @@ a:only-of-type > .remove {\n\ margin: 2px;\n\ vertical-align: middle;\n\ }\n\ -.post .menu-button,\n\ +.postInfo > .menu-button,\n\ #thread-watcher .menu-button {\n\ width: 18px;\n\ height: 15px;\n\ @@ -2898,6 +3405,7 @@ a:only-of-type > .remove {\n\ #menu {\n\ position: fixed;\n\ outline: none;\n\ + font-weight: normal;\n\ }\n\ #menu, .submenu {\n\ border-radius: 3px;\n\ @@ -2981,6 +3489,9 @@ a:only-of-type > .remove {\n\ cursor: text !important;\n\ }\n\ /* Embedding */\n\ +.embedder:not(.embedded) > span {\n\ + display: none;\n\ +}\n\ #embedding {\n\ padding: 1px 4px 1px 4px;\n\ position: fixed;\n\ @@ -3116,6 +3627,10 @@ a:only-of-type > .remove {\n\ overflow-x: scroll !important;\n\ }\n\ .gal-image a {\n\ + display: -webkit-flex;\n\ + display: flex;\n\ + -webkit-align-items: flex-start;\n\ + align-items: flex-start;\n\ margin: auto;\n\ line-height: 0;\n\ max-width: 100%;\n\ @@ -3124,6 +3639,11 @@ a:only-of-type > .remove {\n\ width: 100%;\n\ height: 100%;\n\ }\n\ +.gal-image img,\n\ +.gal-image video {\n\ + -webkit-flex: none;\n\ + flex: none;\n\ +}\n\ .gal-fit-width .gal-image img,\n\ .gal-fit-width .gal-image video {\n\ max-width: 100%;\n\ @@ -3180,59 +3700,86 @@ a:only-of-type > .remove {\n\ bottom: 2px;\n\ vertical-align: baseline;\n\ }\n\ -.gal-buttons,\n\ -.gal-name,\n\ -.gal-count {\n\ +.gal-labels {\n\ position: fixed;\n\ - right: 195px;\n\ + bottom: 6px;\n\ + display: -webkit-flex;\n\ + display: flex;\n\ + -webkit-flex-direction: column;\n\ + flex-direction: column;\n\ + -webkit-align-items: flex-end;\n\ + align-items: flex-end;\n\ }\n\ -.gal-hide-thumbnails .gal-buttons,\n\ -.gal-hide-thumbnails .gal-count,\n\ -.gal-hide-thumbnails .gal-name {\n\ - right: 44px;\n\ +:root:not(.show-sauce) .gal-sauce {\n\ + display: none;\n\ }\n\ -.gal-name {\n\ - bottom: 6px;\n\ +.gal-name,\n\ +.gal-count,\n\ +.gal-sauce {\n\ background: rgba(0,0,0,0.6) !important;\n\ border-radius: 3px;\n\ padding: 1px 5px 2px 5px;\n\ + margin-top: 3px;\n\ + color: #ffffff !important;\n\ text-decoration: none !important;\n\ - color: white !important;\n\ +}\n\ +.gal-sauce a {\n\ + color: #ffffff !important;\n\ }\n\ .gal-name:hover,\n\ -.gal-buttons a:hover {\n\ +.gal-buttons a:hover,\n\ +.gal-sauce a:hover {\n\ color: rgb(95, 95, 101) !important;\n\ }\n\ :root.gal-pdf .gal-buttons a:hover {\n\ color: rgb(204, 204, 204) !important;\n\ }\n\ -.gal-count {\n\ - bottom: 27px;\n\ - background: rgba(0,0,0,0.6) !important;\n\ - border-radius: 3px;\n\ - padding: 1px 5px 2px 5px;\n\ - color: #ffffff !important;\n\ +.gal-buttons,\n\ +.gal-labels {\n\ + position: fixed;\n\ + right: 195px;\n\ }\n\ -:root:not(.gal-fit-width):not(.gal-pdf) .gal-name {\n\ - bottom: 23px !important;\n\ +.gal-hide-thumbnails .gal-buttons,\n\ +.gal-hide-thumbnails .gal-labels {\n\ + right: 44px;\n\ }\n\ -:root:not(.gal-fit-width):not(.gal-pdf) .gal-count {\n\ - bottom: 44px !important;\n\ +:root:not(.gal-fit-width):not(.gal-pdf) .gal-labels {\n\ + bottom: 23px !important;\n\ }\n\ :root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-buttons,\n\ -:root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-name,\n\ -:root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-count {\n\ +:root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-labels {\n\ right: 178px !important;\n\ }\n\ -:root.gal-hide-thumbnails:.gal-fit-height:not(.gal-pdf) .gal-buttons,\n\ -:root.gal-hide-thumbnails:.gal-fit-height:not(.gal-pdf) .gal-name,\n\ -:root.gal-hide-thumbnails:.gal-fit-height:not(.gal-pdf) .gal-count {\n\ +:root.gal-hide-thumbnails.gal-fit-height:not(.gal-pdf) .gal-buttons,\n\ +:root.gal-hide-thumbnails.gal-fit-height:not(.gal-pdf) .gal-labels {\n\ right: 28px !important;\n\ }\n\ :root.gallery-open.fixed #header-bar:not(.autohide),\n\ :root.gallery-open.fixed #header-bar:not(.autohide) #shortcuts .fa::before {\n\ visibility: hidden;\n\ }\n\ +/* Mod Contact Links */\n\ +.contact-links {\n\ + margin-left: 2px;\n\ +}\n\ +.move-note > a {\n\ + text-decoration: underline;\n\ +}\n\ +.invisible {\n\ + font-size: 0;\n\ +}\n\ +/* PostJumper */\n\ +.postJumper > .prev,\n\ +.postJumper > .next {\n\ + font-size: 120%;\n\ +}\n\ +/* PSA */\n\ +.fcx-announcement {\n\ + text-align: center;\n\ +}\n\ +.fcx-announcement a {\n\ + text-decoration: underline;\n\ +}\n\ /* General */\n\ :root.yotsuba .dialog {\n\ background-color: #F0E0D6;\n\ @@ -3242,6 +3789,13 @@ a:only-of-type > .remove {\n\ :root.yotsuba .field.focus {\n\ border-color: #EA8;\n\ }\n\ +/* 4chan style fixes */\n\ +:root.yotsuba.highlight-you .quotesYou$site$highlightable$reply {\n\ + border-left: 3px solid rgba(221, 0, 0, .8) !important;\n\ +}\n\ +:root.yotsuba.highlight-own .yourPost$site$highlightable$reply {\n\ + border-left: 3px dashed rgba(221, 0, 0, .8) !important;\n\ +}\n\ /* Header */\n\ :root.yotsuba #header-bar.dialog {\n\ background-color: rgba(240,224,214,0.98);\n\ @@ -3262,6 +3816,16 @@ a:only-of-type > .remove {\n\ :root.yotsuba .suboption-list > div:last-of-type {\n\ background-color: #F0E0D6;\n\ }\n\ +/* Catalog */\n\ +:root.yotsuba.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #F0E0D6;\n\ +}\n\ +:root.yotsuba.werkTyme .catalog-thread:not(:hover),\n\ +:root.yotsuba.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.yotsuba.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.yotsuba.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #D9BFB7;\n\ +}\n\ /* Quote */\n\ :root.yotsuba .backlink.deadlink {\n\ color: #00E !important;\n\ @@ -3299,8 +3863,12 @@ a:only-of-type > .remove {\n\ :root.yotsuba .focused.entry {\n\ background: rgba(255, 255, 255, .33);\n\ }\n\ +/* Unread */\n\ +:root.yotsuba .unread-mark-read {\n\ + background-color: rgba(240,224,214,0.5);\n\ +}\n\ /* Thread Watcher */\n\ -:root.yotsuba .replies-quoting-you > a, :root.yotsuba #watcher-link.disabled.replies-quoting-you {\n\ +:root.yotsuba .replies-quoting-you > a, :root.yotsuba #watcher-link.replies-quoting-you, :root.yotsuba .last-page > a > .watcher-page {\n\ color: #F00;\n\ }\n\ /* Watcher Favicon */\n\ @@ -3317,6 +3885,13 @@ a:only-of-type > .remove {\n\ :root.yotsuba-b .field.focus {\n\ border-color: #98E;\n\ }\n\ +/* 4chan style fixes */\n\ +:root.yotsuba-b.highlight-you .quotesYou$site$highlightable$reply {\n\ + border-left: 3px solid rgba(221, 0, 0, .8) !important;\n\ +}\n\ +:root.yotsuba-b.highlight-own .yourPost$site$highlightable$reply {\n\ + border-left: 3px dashed rgba(221, 0, 0, .8) !important;\n\ +}\n\ /* Header */\n\ :root.yotsuba-b #header-bar.dialog {\n\ background-color: rgba(214,218,240,0.98);\n\ @@ -3337,6 +3912,16 @@ a:only-of-type > .remove {\n\ :root.yotsuba-b .suboption-list > div:last-of-type {\n\ background-color: #D6DAF0;\n\ }\n\ +/* Catalog */\n\ +:root.yotsuba-b.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #D6DAF0;\n\ +}\n\ +:root.yotsuba-b.werkTyme .catalog-thread:not(:hover),\n\ +:root.yotsuba-b.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.yotsuba-b.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.yotsuba-b.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #B7C5D9;\n\ +}\n\ /* Quote */\n\ :root.yotsuba-b .backlink.deadlink {\n\ color: #34345C !important;\n\ @@ -3374,8 +3959,12 @@ a:only-of-type > .remove {\n\ :root.yotsuba-b .focused.entry {\n\ background: rgba(255, 255, 255, .33);\n\ }\n\ +/* Unread */\n\ +:root.yotsuba-b .unread-mark-read {\n\ + background-color: rgba(214,218,240,0.5);\n\ +}\n\ /* Thread Watcher */\n\ -:root.yotsuba-b .replies-quoting-you > a, :root.yotsuba-b #watcher-link.disabled.replies-quoting-you {\n\ +:root.yotsuba-b .replies-quoting-you > a, :root.yotsuba-b #watcher-link.replies-quoting-you {\n\ color: #F00;\n\ }\n\ /* Watcher Favicon */\n\ @@ -3412,6 +4001,16 @@ a:only-of-type > .remove {\n\ :root.futaba .suboption-list > div:last-of-type {\n\ background-color: #F0E0D6;\n\ }\n\ +/* Catalog */\n\ +:root.futaba.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #F0E0D6;\n\ +}\n\ +:root.futaba.werkTyme .catalog-thread:not(:hover),\n\ +:root.futaba.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.futaba.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.futaba.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #D9BFB7;\n\ +}\n\ /* Quote */\n\ :root.futaba .backlink.deadlink {\n\ color: #00E !important;\n\ @@ -3424,6 +4023,10 @@ a:only-of-type > .remove {\n\ :root.futaba .indicator {\n\ color: #F0E0D6;\n\ }\n\ +/* Anonymize */\n\ +:root.futaba.anonymize $site$info$name::before {\n\ + font-size: 12pt;\n\ +}\n\ /* QR */\n\ .futaba #dump-list::-webkit-scrollbar-thumb {\n\ background-color: #F0E0D6;\n\ @@ -3449,8 +4052,12 @@ a:only-of-type > .remove {\n\ :root.futaba .focused.entry {\n\ background: rgba(255, 255, 255, .33);\n\ }\n\ +/* Unread */\n\ +:root.futaba .unread-mark-read {\n\ + background-color: rgba(240,224,214,0.5);\n\ +}\n\ /* Thread Watcher */\n\ -:root.futaba .replies-quoting-you > a, :root.futaba #watcher-link.disabled.replies-quoting-you {\n\ +:root.futaba .replies-quoting-you > a, :root.futaba #watcher-link.replies-quoting-you, :root.futaba .last-page > a > .watcher-page {\n\ color: #F00;\n\ }\n\ /* Watcher Favicon */\n\ @@ -3487,6 +4094,16 @@ a:only-of-type > .remove {\n\ :root.burichan .suboption-list > div:last-of-type {\n\ background-color: #D6DAF0;\n\ }\n\ +/* Catalog */\n\ +:root.burichan.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #D6DAF0;\n\ +}\n\ +:root.burichan.werkTyme .catalog-thread:not(:hover),\n\ +:root.burichan.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.burichan.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.burichan.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #B7C5D9;\n\ +}\n\ /* Quote */\n\ :root.burichan .backlink.deadlink {\n\ color: #34345C !important;\n\ @@ -3499,6 +4116,10 @@ a:only-of-type > .remove {\n\ :root.burichan .indicator {\n\ color: #D6DAF0;\n\ }\n\ +/* Anonymize */\n\ +:root.burichan.anonymize $site$info$name::before {\n\ + font-size: 12pt;\n\ +}\n\ /* QR */\n\ .burichan #dump-list::-webkit-scrollbar-thumb {\n\ background-color: #D6DAF0;\n\ @@ -3524,8 +4145,12 @@ a:only-of-type > .remove {\n\ :root.burichan .focused.entry {\n\ background: rgba(255, 255, 255, .33);\n\ }\n\ +/* Unread */\n\ +:root.burichan .unread-mark-read {\n\ + background-color: rgba(214,218,240,0.5);\n\ +}\n\ /* Thread Watcher */\n\ -:root.burichan .replies-quoting-you > a, :root.burichan #watcher-link.disabled.replies-quoting-you {\n\ +:root.burichan .replies-quoting-you > a, :root.burichan #watcher-link.replies-quoting-you, :root.burichan .last-page > a > .watcher-page {\n\ color: #F00;\n\ }\n\ /* Watcher Favicon */\n\ @@ -3538,6 +4163,16 @@ a:only-of-type > .remove {\n\ background-color: #282A2E;\n\ border-color: #111;\n\ }\n\ +/* 4chan style fixes */\n\ +:root.tomorrow #arc-list span.quote {\n\ + color: #B5BD68;\n\ +}\n\ +:root.tomorrow.highlight-you .quotesYou$site$highlightable$reply {\n\ + border-left: 3px solid rgba(145, 182, 214, .8) !important;\n\ +}\n\ +:root.tomorrow.highlight-own .yourPost$site$highlightable$reply {\n\ + border-left: 3px dashed rgba(145, 182, 214, .8) !important;\n\ +}\n\ /* Header */\n\ :root.tomorrow #header-bar.dialog {\n\ background-color: rgba(40,42,46,0.9);\n\ @@ -3562,13 +4197,16 @@ a:only-of-type > .remove {\n\ background-color: #282A2E;\n\ }\n\ /* Catalog */\n\ -:root.tomorrow .catalog-code {\n\ - background-color: rgba(255, 255, 255, 0.1);\n\ +:root.tomorrow.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #282A2E;\n\ }\n\ -/* Quote */\n\ -:root.tomorrow .catalog-thread > .comment > span.quote, :root.tomorrow #arc-list span.quote {\n\ - color: #B5BD68;\n\ +:root.tomorrow.werkTyme .catalog-thread:not(:hover),\n\ +:root.tomorrow.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.tomorrow.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.tomorrow.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #111;\n\ }\n\ +/* Quote */\n\ :root.tomorrow .backlink.deadlink {\n\ color: #81A2BE !important;\n\ }\n\ @@ -3584,29 +4222,33 @@ a:only-of-type > .remove {\n\ :root.tomorrow .qphl {\n\ outline: 2px solid rgba(145, 182, 214, .8);\n\ }\n\ -:root.tomorrow.highlight-you .quotesYou.opContainer,\n\ -:root.tomorrow.highlight-you .quotesYou > .reply {\n\ +:root.tomorrow.highlight-you .quotesYou$site$highlightable$op,\n\ +:root.tomorrow.highlight-you .quotesYou$site$highlightable$reply {\n\ border-left: 3px solid rgba(145, 182, 214, .8);\n\ }\n\ -:root.tomorrow.highlight-own .yourPost.opContainer,\n\ -:root.tomorrow.highlight-own .yourPost > .reply {\n\ +:root.tomorrow.highlight-own .yourPost$site$highlightable$op,\n\ +:root.tomorrow.highlight-own .yourPost$site$highlightable$reply {\n\ border-left: 3px dashed rgba(145, 182, 214, .8);\n\ }\n\ -:root.tomorrow .opContainer.filter-highlight,\n\ -:root.tomorrow .filter-highlight > .reply {\n\ +:root.tomorrow .filter-highlight$site$highlightable$op,\n\ +:root.tomorrow .filter-highlight$site$highlightable$reply {\n\ box-shadow: inset 5px 0 rgba(145, 182, 214, .5);\n\ }\n\ -:root.tomorrow.highlight-own .yourPost > div.sideArrows,\n\ -:root.tomorrow.highlight-you .quotesYou > div.sideArrows,\n\ -:root.tomorrow .filter-highlight > div.sideArrows {\n\ +:root.tomorrow.highlight-own .yourPost > $site$sideArrows,\n\ +:root.tomorrow.highlight-you .quotesYou > $site$sideArrows,\n\ +:root.tomorrow .filter-highlight > $site$sideArrows {\n\ color: rgb(155, 185, 210);\n\ }\n\ -:root.tomorrow .filter-highlight .catalog-thumb,\n\ -:root.tomorrow .filter-highlight .werkTyme-filename {\n\ +:root.tomorrow .catalog-thread.filter-highlight .catalog-thumb,\n\ +:root.tomorrow.werkTyme .catalog-thread.filter-highlight:not(:hover),\n\ +:root.tomorrow.werkTyme:not(.catalog-hover-expand) .catalog-thread.filter-highlight,\n\ +:root.tomorrow.werkTyme.catalog-hover-expand .catalog-thread.filter-highlight > .catalog-container:hover > .catalog-post {\n\ box-shadow: 0 0 3px 3px rgba(64, 192, 255, .7);\n\ }\n\ :root.tomorrow .catalog-thread.watched .catalog-thumb,\n\ -:root.tomorrow .catalog-thread.watched .werkTyme-filename {\n\ +:root.tomorrow.werkTyme .catalog-thread.watched:not(:hover),\n\ +:root.tomorrow.werkTyme:not(.catalog-hover-expand) .catalog-thread.watched,\n\ +:root.tomorrow.werkTyme.catalog-hover-expand .catalog-thread.watched > .catalog-container:hover > .catalog-post {\n\ border: 2px solid rgb(64, 192, 255);\n\ }\n\ /* QR */\n\ @@ -3669,11 +4311,14 @@ a:only-of-type > .remove {\n\ background: rgba(0, 0, 0, .33);\n\ }\n\ /* Unread */\n\ -:root.tomorrow #unread-line {\n\ +:root.tomorrow .unread-line {\n\ border-color: rgb(197, 200, 198);\n\ }\n\ +:root.tomorrow .unread-mark-read {\n\ + background-color: rgba(40,42,46,0.5);\n\ +}\n\ /* Thread Watcher */\n\ -:root.tomorrow .replies-quoting-you > a, :root.tomorrow #watcher-link.disabled.replies-quoting-you {\n\ +:root.tomorrow .replies-quoting-you > a, :root.tomorrow #watcher-link.replies-quoting-you, :root.tomorrow .last-page > a > .watcher-page {\n\ color: #F00 !important;\n\ }\n\ /* Watcher Favicon */\n\ @@ -3690,6 +4335,16 @@ a:only-of-type > .remove {\n\ :root.photon .field.focus {\n\ border-color: #EA8;\n\ }\n\ +/* 4chan style fixes */\n\ +:root.photon #arc-list tr:nth-of-type(odd) span.quote {\n\ + color: #C0E17A;\n\ +}\n\ +:root.photon.highlight-you .quotesYou$site$highlightable$reply {\n\ + border-left: 3px solid rgba(221, 0, 0, .8) !important;\n\ +}\n\ +:root.photon.highlight-own .yourPost$site$highlightable$reply {\n\ + border-left: 3px dashed rgba(221, 0, 0, .8) !important;\n\ +}\n\ /* Header */\n\ :root.photon #header-bar.dialog {\n\ background-color: rgba(221,221,221,0.98);\n\ @@ -3711,13 +4366,16 @@ a:only-of-type > .remove {\n\ background-color: #DDD;\n\ }\n\ /* Catalog */\n\ -:root.photon .catalog-code {\n\ - background-color: rgba(150, 150, 150, 0.2);\n\ +:root.photon.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #DDD;\n\ }\n\ -/* Quote */\n\ -:root.photon #arc-list tr:nth-of-type(odd) span.quote {\n\ - color: #C0E17A;\n\ +:root.photon.werkTyme .catalog-thread:not(:hover),\n\ +:root.photon.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.photon.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.photon.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #CCC;\n\ }\n\ +/* Quote */\n\ :root.photon .backlink.deadlink {\n\ color: #F60 !important;\n\ }\n\ @@ -3754,8 +4412,12 @@ a:only-of-type > .remove {\n\ :root.photon .focused.entry {\n\ background: rgba(255, 255, 255, .33);\n\ }\n\ +/* Unread */\n\ +:root.photon .unread-mark-read {\n\ + background-color: rgba(221,221,221,0.5);\n\ +}\n\ /* Thread Watcher */\n\ -:root.photon .replies-quoting-you > a, :root.photon #watcher-link.disabled.replies-quoting-you {\n\ +:root.photon .replies-quoting-you > a, :root.photon #watcher-link.replies-quoting-you, :root.photon .last-page > a > .watcher-page {\n\ color: #00F !important;\n\ }\n\ /* Watcher Favicon */\n\ @@ -3763,72 +4425,271 @@ a:only-of-type > .remove {\n\ {\n\ background-image: url(\"data:image/svg+xml,\");\n\ }\n\ +/* General */\n\ +:root.spooky .dialog {\n\ + background-color: #171526;\n\ + border-color: #707070;\n\ +}\n\ +:root.spooky .field:focus,\n\ +:root.spooky .field.focus {\n\ + border-color: #98E;\n\ +}\n\ +/* 4chan style fixes */\n\ +:root.spooky #arc-list span.quote {\n\ + color: #634C2C;\n\ +}\n\ +:root.spooky.highlight-you .quotesYou$site$highlightable$reply {\n\ + border-left: 3px solid rgba(145, 182, 214, .8) !important;\n\ +}\n\ +:root.spooky.highlight-own .yourPost$site$highlightable$reply {\n\ + border-left: 3px dashed rgba(145, 182, 214, .8) !important;\n\ +}\n\ +/* Header */\n\ +:root.spooky #header-bar.dialog {\n\ + background-color: rgba(23,21,38,0.98);\n\ +}\n\ +:root.spooky:not(.fixed) #header-bar, :root.spooky #notifications {\n\ + font-size: 9pt;\n\ +}\n\ +:root.spooky #header-bar, :root.spooky #notifications {\n\ + color: #C49756;\n\ +}\n\ +:root.spooky #board-list a, :root.spooky #shortcuts a {\n\ + color: #FE9600;\n\ +}\n\ +:root.spooky.shortcut-icons .native-settings {\n\ + background-image: url('//s.4cdn.org/image/favicon-ws.ico');\n\ +}\n\ +/* Settings */\n\ +:root.spooky #fourchanx-settings fieldset, :root.spooky .section-main div::before {\n\ + border-color: #707070;\n\ +}\n\ +:root.spooky .suboption-list > div:last-of-type {\n\ + background-color: #171526;\n\ +}\n\ +/* Catalog */\n\ +:root.spooky.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #171526;\n\ +}\n\ +:root.spooky.werkTyme .catalog-thread:not(:hover),\n\ +:root.spooky.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.spooky.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.spooky.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #707070;\n\ +}\n\ +/* Quote */\n\ +:root.spooky .backlink.deadlink {\n\ + color: #FE9600 !important;\n\ +}\n\ +:root.spooky .inline {\n\ + border-color: #707070;\n\ + background-color: rgba(255, 255, 255, .14);\n\ +}\n\ +/* Fappe and Werk Tyme */\n\ +:root.spooky .indicator {\n\ + color: #171526;\n\ +}\n\ +/* Highlighting */\n\ +:root.spooky .qphl {\n\ + outline: 2px solid rgba(145, 182, 214, .8);\n\ +}\n\ +:root.spooky.highlight-you .quotesYou$site$highlightable$op,\n\ +:root.spooky.highlight-you .quotesYou$site$highlightable$reply {\n\ + border-left: 3px solid rgba(145, 182, 214, .8);\n\ +}\n\ +:root.spooky.highlight-own .yourPost$site$highlightable$op,\n\ +:root.spooky.highlight-own .yourPost$site$highlightable$reply {\n\ + border-left: 3px dashed rgba(145, 182, 214, .8);\n\ +}\n\ +:root.spooky .filter-highlight$site$highlightable$op,\n\ +:root.spooky .filter-highlight$site$highlightable$reply {\n\ + box-shadow: inset 5px 0 rgba(145, 182, 214, .5);\n\ +}\n\ +:root.spooky.highlight-own .yourPost > $site$sideArrows,\n\ +:root.spooky.highlight-you .quotesYou > $site$sideArrows,\n\ +:root.spooky .filter-highlight > $site$sideArrows {\n\ + color: rgb(155, 185, 210);\n\ +}\n\ +/* QR */\n\ +.spooky #dump-list::-webkit-scrollbar-thumb {\n\ + background-color: #171526;\n\ + border-color: #707070;\n\ +}\n\ +:root.spooky .qr-preview {\n\ + background-color: rgba(0, 0, 0, .15);\n\ +}\n\ +:root.spooky #qr .field {\n\ + background-color: rgb(26, 27, 29);\n\ + color: rgb(197,200,198);\n\ + border-color: rgb(40, 41, 42);\n\ +}\n\ +:root.spooky #qr .field:focus,\n\ +:root.spooky #qr .field.focus {\n\ + border-color: rgb(254, 150, 0) !important;\n\ + background-color: rgb(30,32,36);\n\ +}\n\ +:root.spooky .persona button {\n\ + background: linear-gradient(to bottom, #2E3035, #222427) no-repeat;\n\ + color: rgb(197,200,198);\n\ + border-color: rgb(40, 41, 42);\n\ + outline: none;\n\ +}\n\ +:root.spooky .persona button::-moz-focus-inner {\n\ + border: none;\n\ +}\n\ +:root.spooky .persona button:focus {\n\ + border-color: rgb(254, 150, 0);\n\ +}\n\ +:root.spooky #qr.sjis-preview #sjis-toggle,\n\ +:root.spooky #qr.tex-preview #tex-preview-button {\n\ + background: rgb(26, 27, 29);\n\ +}\n\ +:root.spooky #qr select,\n\ +:root.spooky #file-n-submit > input,\n\ +:root.spooky #qr-draw-button {\n\ + border-color: rgb(40, 41, 42);\n\ +}\n\ +:root.spooky #qr-filename {\n\ + color: rgb(197,200,198);\n\ +}\n\ +:root.spooky .qr-link {\n\ + border-color: rgb(8, 6, 23) rgb(8, 6, 23) rgb(0, 0, 8);\n\ + background: linear-gradient(#262435, #171526) repeat scroll 0% 0% transparent;\n\ +}\n\ +:root.spooky .qr-link:hover {\n\ + background: #1A1829;\n\ +}\n\ +/* Menu */\n\ +:root.spooky #menu {\n\ + color: #FE9600;\n\ +}\n\ +:root.spooky .entry {\n\ + font-size: 10pt;\n\ +}\n\ +:root.spooky .focused.entry {\n\ + background: rgba(255, 255, 255, .33);\n\ +}\n\ +/* Unread */\n\ +:root.spooky .unread-line {\n\ + border-color: rgb(197, 200, 198);\n\ + visibility: visible;\n\ + opacity: 1;\n\ +}\n\ +:root.spooky .unread-mark-read {\n\ + background-color: rgba(23,21,38,0.5);\n\ +}\n\ +/* Thread Watcher */\n\ +:root.spooky .replies-quoting-you > a, :root.spooky #watcher-link.replies-quoting-you, :root.spooky .last-page > a > .watcher-page {\n\ + color: #F00 !important;\n\ +}\n\ +/* Watcher Favicon */\n\ +:root.spooky .watch-thread-link\n\ +{\n\ + background-image: url(\"data:image/svg+xml,\");\n\ +}\n\ /* Link Title Favicons */\n\ -.linkify.audio {\n\ +.linkify.audio::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAitJREFUOE9jYCAWKJWwavr0KyXWb/FIbDtUFFyzJx6nVofE2Xo5nXsj0rqPNSR0nVkR2Hjmgmfd+U9Otdf+m5Vf/6+SfeU/R9ChVVgNYDRtlfJuuPA/rPfe/4QpD/6nznj0P27Kw/9unff/69Xf+69c/+C/SO7N/0z+OAxgMmmRCe++/r9i3ev/KWvf/vdY8PK/bt/9/wrNV3/IN5y/IVt1YqNg4pGTTP4HsbuA2bhZ2qvpyn+xjIObxAp3VwqlrgngLFyryVy5nhPmZJHANS2cwYexG8BmVC/pWn3hP4NZlzWuQDJI3dIiFnUUuwEsQAOcq87jNcC7fHeLUtJxHF4AGmBWeAavAWH1+1rUUk7giAWjOknllON4DXAs2NEiG4/DBQxAF/CFHfrPYI4jDFSLuJVjNrUJhB/B7gIGo1pJRt99GAZYJK7wLJ1z7Xzl4vu/7aqv/GRBj0bjqAX2qb0nJ7mXH17C4HcUxQA+hymWtSue/C5a9up/9Ozn/7Vr7v1nRY7GqMb91T3b3v6vWvPmf/S0p/9ZQk+DDLCBRSOz06Jqk+o7/21nvfqvsebDf7kZL/5zBaxphkezd+OFn7HzXvz3Wvjmv9a8N//5Ek//ZTBpVYUrMG2X5wjcdl68+uI/wa5Lr3hSNjczGFeywOVZ/bbcVGp//F9izfv/Ql03f3P4LC/HSEQquYwMFnUCDJ7dzBhyjGZNQpye89M5gpfnMvtNUyE2h4PUAQBovvT7lyNljwAAAABJRU5ErkJggg==') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.clyp {\n\ +.linkify.bitchute::before {\n\ + content: \"\";\n\ + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAadQTFRFAAAAwzw8xDs7cY6O0iws0ysrtF9f0Sws1CwsyzU1zTIy1igoyzQ01icnY7i4t0hI0S4u0ysr1Soq1ikp1ikp1Soq0ysr0S4uu0VFzjEx1Csr1ygo2Ccn2Ccn1ygo1CoqzjExzjAw1Skp2Ccn2Ccn1ikp0TAwxzY21Soq1SoqyzQ00iws1ygo1ygo0yws1Soq1Skp1igo1igo1igo1igo1igo1igo1Coq1Soq0C0t1ygo1ygo0S4unV5e1Csr1ygo1ygo1CsruUdHxzg41Skp1ygo1CsryTU10C4u1igo1ycn1ygo0i0txjo60S0t1ikp1ygo1ikp1Cws1Coq1Coq0yws0S0tyzQ00iws1Soq0ysr0i0txDs72Ccn2CUl2CYm2CQk3EFB2S8v2zw82jY24FZW3D0931FR3EBA3UND8LS04FVV7qys4V9f4WBg+erq766u9t7e7qqq2Ckp54KC9+Pj6pSU+Ojo5XNz9NHR6YqK8bu765ub5G5u9M3N6ImJ88vL5XV165eX3UVF6pWV3UhI2Soq2jU12Coq2jQ02Cgo2Sws////FaxLuAAAAF10Uk5TAAAAAAAAAAAAAAAAAAAAASJnoLy9oWolAhBz1vr72XgTGKf8/a4cCpuiDVvz9mS6xOvy9vzg6aGsPOToRAFv9fh2Awm07XgIMd765UEDOsfemVhhY00nBommbCkEI8horgAAAL5JREFUKBUFwbFKA0EUQNF7387sMq4EmzRpLSSdIBYKFv6Af2prnSYkRT4gWFgkCBJQ0EIFdcZzBCeqqh4qdk7VW2ChPusw02sAYKU7z7wEAAA2piQKFbrWSHazc1J0XWs5pdxPDykcVX+7Y9UxUsSo+s7PibqPFBRV/C5qi4i/UkrJrc7L47Bt4ZWnUaMCAE9GSrtKBQD2fR+bnAEAeOn7dUTOwApe35bDsPz0zsniQlV98IN0tJ3f6P0XAMA/kxou7OXCdnoAAAAASUVORK5CYII=') center left no-repeat!important;\n\ + padding-left: 18px;\n\ +}\n\ +.linkify.clyp::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAwUExURTSY22ey5E2l4KbS75rM7Y3F64C/6f///8zl9nS45r/f9PL5/UGe3bPY8Vqr4v///wNjrzUAAAABYktHRA8YugDZAAAAB3RJTUUH4AINEi85AIH95AAAAE9JREFUCNdjYMAGGBWgDGYHCM2a3hkAZmi0dzSBaKaO9o5moCqmLiCjYzNQyw4QowIodQzI6E0AKcpo72gE6+Jyb1kAMehUA9RktgdYbQYAjGIVNGGXBJkAAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.dailymotion {\n\ +.linkify.dailymotion::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAnUExURQBk3ff6/trp+kKO5wZt3xx54q7P9Ozz/IS17zOG5WKh653E8sbc9/GbbcoAAABZSURBVAjXY2BAASyhDhAGc9oECMOjyAAiESEEYrBYpLWBGcwHxcvBjDDxHelghpF0yDQwY3kVgweEUeEQDWbMEepqAjO8FMsLIeYsU8o+BrbCdWboTAe4AwALXxWGjW41FwAAAABJRU5ErkJggg==') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.gfycat {\n\ +.linkify.gfycat::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAjVBMVEWn3gCo3gSr3w2t4BSu4Bav4Ri35C+45DK45DO55DXA50rA50vB50zC6E/D6FTF6VjG6VvL62vN7G/P7XbQ7XfW74vY8JDa8ZTe8qDe8qLf86Pi9Kzj9K7k9LHp9sDp98Lq98Ps+Mr0++L5/O75/fD6/fH6/fL6/fP7/fT7/fb8/ff8/vj8/vn+/v7///91X4cfAAAAcklEQVR42o3M2xKBUACF4aVQckrIuRJK6H//x2sme4/MuPDfre9i6c/Cc3U5Dj87BuAxsXvGu6JvIIXEHRWwNHCHQNrCzkAFkbSBg4EM8i+Yw7PXBa3zRfuxVyf/Bis7nKwGKAcWxgC8prI5Sc315OlnDfzpDar2S9/oAAAAAElFTkSuQmCC') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.gist {\n\ +.linkify.gist::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABblBMVEXc3NykpKTW1tbb29ugoKCdnZ0AAAACAgIEDRcKCgoMDAwODg4QIzYRDAoTExMUDwwVAg0WICsaEw8aGhoiCBklGxUmERwwKCQ7LSU7Ozs8LSZFLyNINi1JNyxJNy1KSklMOi5VR1FXV1daQTRkZGRseYZwU0F4eHh7dnR8bWV/YE6IdGiKcGCKkJaNgYeNjY2RdGOScWCUcWCZmZmhoaGkpKSoqKirfmaurq6xsbG1tbW6urq+vr7AbmzBb23CwsLGxsbHx8fHyMjJycnJysrMzMzOiYbPi4fQ0NDRoYbT09PU1NTW1tbY2NjZqIzZ2dnb29vd3d3f39/i4uLktZrk5OTl5eXm5ubn5+fo6Ojq6urs7OzttKLu7u7wuqbw8PDx8fHz8/P4+Pj5+fn7uZj8vpz9ya79ybD/tZf/upr/wZ//w6H/xKH/xaL/xrH/yqj/y7T/zqv/z7D/07D/17n/2Lv/2Lz/3L//38n/4Mk3Q/ZuAAAABnRSTlMSFcbGzc5MNKFvAAAA1klEQVQoz2NgYPZHAswMDEwRSclwkBTBxOARn4gE4j0YXBOiJNUDg7y8Ar1UlOITXBkcY73Z2Li42dg42dn4wmIdGeyjQ7nZoEA4PNqewSZKlw0O9KJsGKwjBdl4ZeWkJGQUhNjEIq0ZrMI5+D0ri7Jz8itCRAXCrRgsQ3mUy+xicrPSbfO0REItGSyCVaVL3ONSU9LcCtQUgy0YzIJ85M1LizMzCsv9xF2CzBhMAwN99TV1DI0MtDWcAgNNGUycA5CAswkDi5kDwrMOZiwMjKzGSICVEQDhZj0UQV7PewAAAABJRU5ErkJggg==') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.image {\n\ +.linkify.image::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAs5JREFUOE+lk/tvi1EYx98/xT8gW4REIpGFMEQWl2FiM9ZMZhm2xRAyOsmujFFmdFRHu0tWm87UypxStr69zPauN5e5rHVp3IYhbOvHy+wHEQlxkm+ek+d8nm9OznkeSfrfldmgJC7QyUlTymsJTfuTZ25z4HdWYwyLreYhtpgekGPw0+kKvo1Eo+IXRSIiEhkWZuc9tqnsJD9EqTUopCxjSGTpB0iueczSo1HyW8cpsExQ1DbxI2pt45j9cXpexul4FEd79RnZphAa/SD7WvuFtO6UItbU9LC+YQxNI2w0wwYT5LRAdhOU3oBTIXC9gXP3oUSGgz2vST3gYHejR0jptT1C332f8yrUEYHrz8CgxDnpm6DKCUfc0KnmXa/AEVPPwnDcD0cvetA2uYRk67Ive/lpjO7YBO1PPuF8Df3vwf4cbNE4tqdw7YVq8HYyHx6FvhE1hkMEg8HDUqvFkjT4aIjMqkqyqkswDSrcfBfH+Q561YLAZ/B+BLda6FXlU/cPv0AoEPhuoP1h4Av7Wbh9E/Py15NWWUjeSR3nZDfeN+N0DY9hG/7K1eGP3P0S5/EYRFUF/IOTBrUXHPm9fT6mr1xEwupkZqxbzLyiDJYUZ5NSnkdqdSHpxyrYdFpPgdmAsdfJwPMI/Yr65bf7tZLGGBQ7DNdJWFtIYvoOZmbuZE7OXpIKKli86zAr9p9gTVktWTVnKTI2U95uRWe3U2IJUDbVB5p6hVm5x5m9Vc/cnedZUNzC8lILaQesZBy6hEZ3maKzgvJWFzVWD9XtXvVGQbSWASFtMATVRlJIKbOTWtlJXaeXepuPM1f6MNp9GLt8mLvvYLmp0OhQ2Fwvk6m7xaqDTvY0eYWUVtcnllXfYlGpnfklVuraHHg8HjxuN+6fktUHlWWZPaZeUo/ILK0UKttBcbNbSB9GP0yLxWJJUxoZGUn80zD9C/vXQ/4NHY10h3M1zmQAAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.installgentoo {\n\ +.linkify.installgentoo::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABcVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB3dIYAAAAAAAAAAAAbGh4BBAcCBgoBBgoCBwsCCQ/QzucCCA7MyuXZ1eUBBQmTh8fo5/i9svIAAADh3vQAAAACCA0CCQ8CCQ4DDBQbGCUDChDr6vgAAAAAAAAREBIDCxK6tdfe2fTv7/cDCxIDDBQEDRUHDhgMJjXk4PZdXWdLUFoUNEYOKDgSMUMRLUBneI4eTGj08/QmW3onW3rTzvfOx/giU3IiVHMkWHdEaYJobHv3+PokWHpua6TNy9xZgZ+1quz8/foQKj0XPFInWn0nW38tZ4o6fqg8gq48grA9hrU/i7pAhrNAiLdBjLtEjr1FksNIjr5Il8pImMtKWnNqhL97odKFqti5q/q5rPq60+nCt/vLw/vPx/jV0vHY0/rc1/rg2/vh3fzn4fzu6/vx8vf19Pv19Pz49/v5+Pv8/Pv8/fr9/vv+/frziVtUAAAAT3RSTlMABQYHCAoNDhARGRobL0ZOV1xdXV5fYGBmZnB0eX2MjZSaoaGio6mqqqustLq7zubo6Ojo6evt7u/x8fLy9/f4+Pj5+vr6+vr6+/39/v7+XKgUSwAAAMhJREFUKM9jYGDg4OZmZgABKINT1dBAhBHIYFMxMBIDisjbhoZbCTExsCu5hoeY8DEwcOkEx8fY6MqpucTGB0izglVEplcU5/gmRYWBVQDNMK+s0hN3SvMyBpsBNJxXw0NfwTEjVQZqHQMHj5RfWW5mliSEC7TPzK6yJD/bXZQRzGdXcisqLy309okA2Q4Eis4peQWmstqBCdGW/CABraC45ERBBs3A6Fh/AbAKTwsHa34QZW8NVsGuLqwswQSjQICTmYMFQaEDAAF8JHLfKGswAAAAAElFTkSuQmCC') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.liveleak {\n\ +.linkify.liveleak::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAlNJREFUGBkFwU2LVmUYAODrPu8Z5x1xSpRBXQyFoLsBE+wfiO5atJOgnf9DUPwFgtGinUgEaQsRhHYuMtpEiEWuG5iNjuOcj+c8z911xXcXL/68c3Dw1fzhg0QgEQAAEYGUKXFie9vxlSs/xk/rdavjGEkmkWSih65z4osv9GfOiK6LzEyZ2uGh4dUrmzs72ddlUUhkoiMr4PT167589Mh6c1N0nSRlqrX67dat+PDyZXRT19m5edPnt28rGFHxMcJ6d9fprS1/37tneP3aemPD1uamUydPOru3p5DdGOH0tWsu3LhhxIQJM2qEpRT/Pn3q/du3AhARSmvGTH0lplKMrVkiYpVpQaJlighzhDkzhmEA0fcWoqAfyaFW4zTlgCABxlrNmY4ylUzLsiREprFWc0T2M+ZSjKWY0AEaltZUjJixZJIpuk5pTWlNP2BYFvOyKJkCAKU1tTXHrZlqVWolUxdhxsfVSj9FmJfFMM9GdICGGa01HyMstYpMIFPJVNDPmYZSTOPoOEKHzNRlKpmWWh1j6TpLa2SKTKVWU6Z+Qolwdm/P9QcPZKa2LH69e9eIMs+WCL/cv2/98CGZPrt61am+V9APq1X89eyZ/968obVYaiXT4dGREgG+vnPHeHgYMsH2+fP+efEihtVKv7SWw/6+9/v7KYLMhIywTJPamvOXLomukyRsrNf+ePzYkpl9dJ3SWgSCSCQCfz5/7pMLF2yfO6eLiAQcHRz4/cmT+HR7O+Ob3d0fNt69+7a2BiICQCJbA0EgE5lpvbXl1OXL3/8Pfax4+6SjSukAAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.pastebin {\n\ +.linkify.pastebin::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAB1FBMVEUAAAAAAAAAAABWYWwAAABbY3BbYm5dZnFdZXJeZnMEBAQHCAhYYGpdZnFdZnBgaHIlJyomKCooKi09QkdESU5eZGtdYmhdYmleY2lrcXdqb3Rqb3Rqb3SSmJ+SlJeWmJutr7GtrrCWm6ChpKhbW1tmZmZvb290dHR3d3d4eHh5eXl6enp8fHx+gIJ/f3+CgoKDg4OEhISFhYWHh4eKioqKjI2Li4uMjIyOjo6Pj4+QkJCRkZGSkpKUlJSVl5mWlpaYmZqZm52ampqbm5ucnJydnZ2enp6fn5+hoaGioqKkpKSkpaalpaWmp6mmp6qnqauoqKioqquoqq2qqqqrrK2srKysra6srrCsrrGurq6vr6+wsLCxsbGysrKztLa0tLS1t7m2tra3t7e4uLi5ubm6urq7u7u8vLy9vb2+vr7AwMDAwsTBwcHExcfFxcXFxsnGxsbHx8fIyMjJycnMzMzNzc3Ozs7O0NLPz8/Q0NDR0dHR09XT09PV1dXV1dbV1tfV19rW1tbX19fX19jY2tzZ2dnZ2tva2tra3N3a3N7c3Nze3t7f39/f4OHg4ODi4uLl5+jm5ubs7Ozs7e3u7u7v7+/v8PDw8PDx8fHy8vLz8/P29vYSoLMZAAAAJHRSTlMABAUGCwsNHCAiLzMzMzZEYGJwgIuOnJycnqmqq9bc3+/w8fkZ0N/uAAAA/klEQVQoU2NgYGDl5YMDdgYGBmZZ3964CYFtIR3e9Q7K/AwMHI55KfaFmcHWMy3K3MwlGRg4wz0zdYpcorRbNbL0LaWAAp3ts2umV8wo6MupTauQBgqUG03VL7W3sfZSb1erAgm02M+yzYrVCXUy6zapAQlUx/dEdyX3J3ZHVUYVywAF8o2rDNN1Go2jzGLMokAC2QbuSc42mXmaOXop9iAtCXrJ5qXWjT59Abl2ESJAAX/tSIMMiyrrqQ3T6uS5gQK6kSqpqkUermGTexQFmYACflqR+hlWZSamzQpCLEDPsSmVVDT1TJw0JUhOAMRnYOARFRMTE5cQF+ZiBPIAII5B3EVG0b4AAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.soundcloud {\n\ - background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABsklEQVQ4y5WTy2pUQRCGv2rbzDjJeAlIBmOyipGIIJqFEBDElwh4yULGeRFXPoEIBl/AvQ/gC2RnxCAoxijiwks852S6+3dxzslcHJCpTXVX11/Xv0097gLPgVNMJxnQNfX4zsqleWbnpoMf/oa9d988MM9MC/rp+E0a+A0dsVobMNMCOO8B6McRoABJI+A6gJmN3D2A8jgEBCEkSEMBrcrsDAzDWWn3AjgKFaDMmgRqniGFgsaDp1jrLOngDf1XT1D+A1dFc4MKAkkiCVKjjVu7g9+4Rzx4i1u6hjXbuMWr0O5QPNvCu7IaCZwEKQukLGDrm5x8uI0tr6MkiGlkiv7yLfzN+6S5i6QsIMABkEfcxhbWWYMkVAOjxvYAjc3HNHrbKI9VBQBFwF25XQKSBjqIf1YBuAurEMrczgDygD6/x2LCpFLXLUyQ+PoldphhBhYfIX09XU1+Flaukz7uYqs3SHs7cG4BmTsmkBUF9mmXEwa28BNLPaQPLepuNcbGSWQquQC2/Kdcox1FUGkcB0ykck1nA2+wTzMs8stGnP4rbWGw74EuS/GFQWfK7/wF6P4F7fzIAYkdmdEAAAAASUVORK5CYII=') center left no-repeat!important;\n\ +.linkify.peertube::before {\n\ + content: \"\";\n\ + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABIFBMVEUhHyAAAABzPBnxaA3CWBEnJSYbGRptbW16enpzc3PTayWhb04hHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyApIh+0UhMfHiBWMhvsZg7zaQ0hHyAhHyAXHCHzaQ3xaA3xaA3xaA3xaA0hHyAhHyDxaA3xaA3xaA3xaA3xaA3xaA0oJickIiMdGxwUEhPxaA3xaA3xaA1sbGxwcHB3d3eFhYXxaA3xaA3xaA1zc3Nzc3Nzc3Nzc3Nzc3PxaA3xaA3xaA1zc3Nzc3NtdHjxaA3xaA1yc3STcFnvaA/yaAxzc3N4c2/FbDFzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3MhHyDxaA1zc3MAAAAfljyVAAAAXHRSTlMAAAAAAAAAAAAAAAAZkjMBHOLXYArj8p0u2VsJ1XaGL/OhKyXc1WEN2gwk2/SjKgEYiS4B/tYFGosqAdleAxzj12ML9Z8s850rJWbYeYMs1F8Koiri1V0MGZY0AYbIBFIAAAABYktHRAH/Ai3eAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4wYXFBUVX81QWQAAAKxJREFUGNNVz9UWgkAQANDBtdbu7lZsxe7ubpH//wxBPKDzNvdMAmi0Oj0QQgAYjCazBX7BStvsDqHoAzTtdLklf+Dx+vwICRAIhsKRaCyOvpAwJ6Up8pXOZHOIAFm+UCzJEQuvMhWrIFBUa/WGkodmq40Ad7q9/kDFwnA05lpYYCbT2ZykFvxQDhhmuVpvcvxaHra7vfp72KflcMSYEOB0vlyx+By+3R9PMSfe+P0enM1454kAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDYtMjRUMDM6MjE6MjEtMDc6MDDse6MAAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTA2LTI0VDAzOjIxOjIxLTA3OjAwnSYbvAAAAABJRU5ErkJggg==') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.twitchtv {\n\ - background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAYUExURf///2RBpWRBpWRBpWRBpWRBpWRBpf///+zQyUYAAAAGdFJOUwFdZX0lTzs4r5oAAAABYktHRAcWYYjrAAAAB3RJTUUH4AINEi42iSXRNAAAAD1JREFUCNdjYEiDAAZGGIMtjQEEUBlMCWoEGci6mGEMsxQgIy0BiB3AjLS0FAYQIw0kwABipoI1AhkBQBIAFCIXxiHgq80AAAAASUVORK5CYII=') center left no-repeat!important;\n\ +.linkify.soundcloud::before {\n\ + content: \"\";\n\ + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABsklEQVQ4y5WTy2pUQRCGv2rbzDjJeAlIBmOyipGIIJqFEBDElwh4yULGeRFXPoEIBl/AvQ/gC2RnxCAoxijiwks852S6+3dxzslcHJCpTXVX11/Xv0097gLPgVNMJxnQNfX4zsqleWbnpoMf/oa9d988MM9MC/rp+E0a+A0dsVobMNMCOO8B6McRoABJI+A6gJmN3D2A8jgEBCEkSEMBrcrsDAzDWWn3AjgKFaDMmgRqniGFgsaDp1jrLOngDf1XT1D+A1dFc4MKAkkiCVKjjVu7g9+4Rzx4i1u6hjXbuMWr0O5QPNvCu7IaCZwEKQukLGDrm5x8uI0tr6MkiGlkiv7yLfzN+6S5i6QsIMABkEfcxhbWWYMkVAOjxvYAjc3HNHrbKI9VBQBFwF25XQKSBjqIf1YBuAurEMrczgDygD6/x2LCpFLXLUyQ+PoldphhBhYfIX09XU1+Flaukz7uYqs3SHs7cG4BmTsmkBUF9mmXEwa28BNLPaQPLepuNcbGSWQquQC2/Kdcox1FUGkcB0ykck1nA2+wTzMs8stGnP4rbWGw74EuS/GFQWfK7/wF6P4F7fzIAYkdmdEAAAAASUVORK5CYII=') center left no-repeat!important;\n\ + padding-left: 18px;\n\ +}\n\ +.linkify.streamable::before {\n\ + content: \"\";\n\ + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABXFBMVEUPkPoNj/qExv0PkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoNj/oPkPoNj/oNj/qExv0PkPpruvwPkPornfoVk/opnPpnufwPkPqExv0Nj/oPkPoNj/oPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoOj/opnPsVk/oMjvoOkPoTkfo6pPsblfo3ovva7v7////v9/5Sr/whmPry+f5htvze8P7W7P5itvyl1v0imPu84P3o9P50v/zN6P73+/8lmvs8pfs+pfsKjvr9/v9EqfsNj/oom/v8/v9nufxAp/tJq/sQkPrb7v6t2f0IjPoclvr6/f9luPwUkvrp9f7h8f5ruvy/4f4kmftpuvwxoPum1v32+/8jmfpMrPvu9/7z+f9UsPs7pPv8/f/4/P9oufwalfpDqPsMj/ounvtVsPsnm/qzfQQ9AAAALXRSTlMAAAAggMzw0IYkBPb4iAamsgZ+jPwogpDO1vTYlPoulL4KivyUCiqO1PL01i67tUAWAAAAAWJLR0Q4oAel1gAAAAd0SU1FB+MGFxMuDXVcMbIAAADdSURBVBjTY2AAAmYWVjY2dg5OBgZGJiCXi4VbFwx4ePlAAlz8unAgIAgUENJFAsJMDMw8unp6+gaGRsYmpoa6IqIMYrp6ZuYWllbW5hY2toZ64gwSurp29g6OTs4urm7uHrqSDGy6nhZeet5WPr5+/gGBelJAgSCLYL+Q0LBw3YjIKKAAu250TGxcvE1CYlJySqquNAOHrl9aukVGZla2RU6uoZ4MA6esrl9evnWBYWFRMdBaOQYGXmSHyQNdyieA4CsogjzHpyQL4SqrqIJ9y8Cgpq6hqaWtogPyPgDmvSxRxBWM9AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxOS0wNi0yNFQwMjo0NjoxMy0wNzowMCKUvXUAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTktMDYtMjRUMDI6NDY6MTMtMDc6MDBTyQXJAAAAAElFTkSuQmCC') center left no-repeat!important;\n\ + padding-left: 18px;\n\ +}\n\ +.linkify.twitchtv::before {\n\ + content: \"\";\n\ + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAYUExURf///2RBpWRBpWRBpWRBpWRBpWRBpf///+zQyUYAAAAGdFJOUwFdZX0lTzs4r5oAAAABYktHRAcWYYjrAAAAB3RJTUUH4AINEi42iSXRNAAAAD1JREFUCNdjYEiDAAZGGIMtjQEEUBlMCWoEGci6mGEMsxQgIy0BiB3AjLS0FAYQIw0kwABipoI1AhkBQBIAFCIXxiHgq80AAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.twitter {\n\ +.linkify.twitter::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAEsUExURf///1Cf21Gg3FGi31Gh3VKj4FGh3lKj4VKk4lKl41Ol5FOn51Sp6VSo6FOn5lCf21Gg3FGh3VGi31Gi31Gh3lGg3FGg3FGg3FGg3FGh3lGg3FGi31Kk4lKj4FGh3lGi31Kk4lGh3lGg3FGh3lOm5FOm5VGi31Kj4VSo6FGi31Gh3VGg3FKj4FOn51Gi31So6FWr7VOl5FGi31On51Sq6lKk4lOo51Sp6VOm5FSq61Ws7VOn51Oo51Sq61Ol5FOm5FSq61Wr7VOo51On51Sr7FWs7VSp6lGg3FGh3VOm5FWr7VSp6lKj4VOm5FSo6FSr7FWs7VWs7VWr7VSq6lOo51Om5FOo51So6FOm5VOl5FSq61Ws7VSr7FSp6lSp6VWs7lWr7VKk4lSq6v///6E3MNsAAABVdFJOUwAAAAAAAAAAAAAAAAAAAB0Ii+3xnBVTJhfsMKb+qTEp9GwBF/7lLAbo0m4pLkUTdvk2Ev3+EZnOBo/3Z8ffCRzH/D0OqPxiLnvx3UI8m9n1++GwXQZNS29BAAAAAWJLR0QAiAUdSAAAAAd0SU1FB+ACDRIwBwy67tEAAADKSURBVBjTY2BAB4xogIGRH8IQEBQSFhEVE2eQkJQC8ZmkQ8PCI2Rk5RjkIxUUlRgZlVWioqNjYlXVGNQ14iI1tbR14qLj4+MTdJkZ9PQNosJCE0OjgPz4KEMWBiPjhPiEmKQokIJ4E1MmBmazhHg4MGdlYmCzsLSC8ROsmRkZmFht4Eps7ViADmOzd4DyHZ2YmYACTOzOLmATXd04mIBOd/eQ9owFCXh5c7KB/MLi4+vnHxAYFBzCwcYEEmBi5uLm4eHl42RmAnsSAMZBLgZiFUQ5AAAAAElFTkSuQmCC') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.video {\n\ +.linkify.video::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAxgDGAP8nNqN7AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gMZBjQQLEEqGwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAA5SURBVDjLY2AYaMDIwMDwn1JD/lPCZhpwL+B1wf///ykzgBhDiAoDfIYQZQAjIyP5BuDTPJqQqAQAvW0ZAMk8+EEAAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.vimeo {\n\ +.linkify.vidlii::before {\n\ + content: \"\";\n\ + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAACvlBMVEUCWv8HXf8AWv8AAAD///8AVP+bqP8AWv8AVO4AOqUAGkgAyf8APa0AL4QABAsASdEAVv8AUv8AUv8AVP8AWP8BWv8JXv8RYv8QYv8DW/8DXP8xdv9RiP9Af/8IXf8AKP8KXv8JXf8NYf8aaf8ATP0UZP0AVf8AT/8AT/8AVv8ATedvnPVAf/8AT/sYZvl0o/8PYf8udf8aa/8FXf8AVf8AOrRBe/Nvn/8AUv0aaPkXZ/8ATv8AKYQZYuwIXf8ca/wTZP8ASP8AED0HUNwZaf8xdPwDWv8AAAAAQMRcjvQAU/8AMZssb/Jmmf8AU/8AJXsRW+dSif8AUv8AAAAASdQtdP8ATv8AQ/8AQv8APbtKgfQud/8XZ/8TZP8FXP8AKIIcZO4wdP8AF08KU95tnv4gafhZi/Rnl/ZzofcocP8AAAAAQ8Q4efRwnvVmlvVcjvgrcfsAQsQAOK0APrwAQcUEStMLXPgDWv8AHE8APLEARdIAQ80ASeEAVf8AOJkAAAAAAAAAAAAABBMAJJIAY/9rmP+vxv90n/+buPv29/7C1P+zx/n///2Crv/7+fjs8f++z/f///3l6fX9/f/L2fj9/P5ilv9Nh/3h6f6vyf/D0vT///2lwP/Z5Pf3+P9OiP9klvr9/Puzyf+QsPX//fnW4v/k6vfv8/86ev94pfj///uRtf/y8vby9f9Fgv9EgPzt7/jj7f8mcf+eufj///x1pP/Z4fT///52pf9Uivv09fnV4v8ncf64yvj7+/6vxPX///yyyf9ynvr6+vvG1/8ocv3O2/fz9v53ofX8+/nb5v+YuPz//vy0yv8vdP3e5/fn7v/p7PX09//b5P7///6eu/9Df/zq7vjc5//I1vT//v3+/v////9+q/9Tivnn6fPy8/rW4fzI1/2qwv6YtPT8+fX39/jz9PqJrveTsvqfpuxrAAAAhXRSTlMAAAAAAAAAAAAAAAAAAAAABSlERA45nrSzP3TZ7e12Ao2LusMcrJYhFwaR/uhCwP/x5tZzBWHy+n3OvA8u17jmpwgPrOz5jAF2+3FA7PdYG8fuPQaX5jAGAV/39MCmdy/e/RGz/vj5/f/rAXj4//z13n52i5qmmFQ1lqOQaTgIBAYKEAYAKGjtAgAAAKNJREFUGBkFwT0uRGEYBtD3ZJ77uT8iGrXCAixCr7OCyRA2oCKqiUYkOgoJwhqUbMAKLEChVYhk4pxswvcWfFGVEbYtuJutqir9Ibc0uh0+V+mf5gY69yN2PzKJiTjCg8qa3uLRAJpKM9AMoL1VOi9zJ4CQ9z0jwHX+RAwAURUxAMSB/L7u35wCGlKaHrDkPGVmwhlc6FN6l1iHKxupn+djAPgHrEwa+qrzy0oAAAAASUVORK5CYII=') center left no-repeat!important;\n\ + padding-left: 18px;\n\ +}\n\ +.linkify.vimeo::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAYFBMVEUAAAAIdZUKh6sLlLkLmr4LmsAMp88NrdYVW3MZj7Acstkrt9s1e5E7vN5EfI9JvdtKwuBijp5kpbl30eiDt8aG1uqRr7qTyNehxM+k4PCy3enB3OTg6Ovv9PXw+fz////L9U5WAAAAAXRSTlMAQObYZgAAAIFJREFUeNplz90OwiAMBWAQpAoyxclkP3je/y0H2AQXz0WT8100rRD6kNI9/cRroemQL3hXhoujZYj4OHoAmBvYGcBISwbWBvfXCrytnIDUQMkbsBpagMA7zhtQdyTFQAmIG7IkYniiZuh3XGsPqoOZkMOJOpAcLqUzNFGGu/57fwc1hgtp0mVSyQAAAABJRU5ErkJggg==') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.vine {\n\ +.linkify.vine::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAHCUExURQAAAAC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+j////54tRLAAAACUdFJOUwAAAEK+9/e+QQIDAwEqzskfUZmUNHz2mrT++V1w+f5tCanNFUDwfEjtjAyyvg027Hki27QMBJzaHE/1+FkNsN0iZvv6bxyAlB589lQeyud0KB8PQO+ZBUrc+eXgcRG/3CoATe316Wxw/P6BAgBt+fp4IAwh0d4zM9q7Fm76qi605EMSrvfX/PRtAivF9IAJNMLxhA2KYlJ9AAAAAWJLR0SVCGB6gwAAAAd0SU1FB+ACDRI2MOJd7FgAAADrSURBVBjTLY9VWwJgGEPfiYWBha2YYHcHditgd3d3odjdivvBfgK727nYsyPiCrw03j6+fv6AaAMCgyAI1lElJBQSFh6hBxDJqOiY2Lh4SEKiIQlITmFqWrqRJkhGJrOA7Bzm5uUXsBBSVMySUpSVs6KyqrqmFmKuY30D0NjU3NLa1t6h9jvZ1Q30WGi19fb1KzAwyKFhYGSUY+MTkwpMTXNmFpibX+Di0rICWFldW9/A5tb2zu7ePtTrg0MeHePklPYzuDRw7uDF5RWvbwC32O0d7x8en55f4DHF6xv5/vHp6f/k6/vH+evuf1LAObptvSvrAAAAAElFTkSuQmCC') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.vocaroo {\n\ +.linkify.vocaroo::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAw9JREFUOE9jYMABuMwYmCyTJKUCGlSnFSy02TTzeOyCiQcDViX26qVz2TAyYtWmEMwuoZ3M7V40LcB79pHkc0svpvzY8jD//87nxf+3Pyn8v/ZO8v+VNyP/2mZJumI1QCWSI8232Hjumitlfw5+qPp/9l8TCt76JP//xkdx/wsXWCzjtWFkwTCkbWFe9plPk/+ga4Txz/xt/D/hkN//gMXif21a+NbyWjIwoRiy6GDT5rP/mlFsPfyp5n/NpOj/22+0gMUXXIz/H7hC/L/bFKFbPDZMrHAD5H35OPt2J9zacDv/f3V7xv9FhwrBGubsT/1//Pjx/1GJ/mD+/nfl/1v3Ovy3KRJNQbHdOlXCvOO03/+pm1P/v3v37n90hhtYw9HPtf8Xb2v937cmHswHeWPRxYj/LvkK3igGKARwicTO07118H3V/5kbi/4vPZMJtK3s/6YH2f+Pfq1B8VbjWrdnMu5s4nAD9CNFhKwz5DTUvLl419zKvAcLtG1P84BRl/b/5M/6/6f/NPzf/qzo84yj0Uus0xUU4Zor54bm9+4OfZG02OCuoAMTb9ZkC9ull1Nvrr2Z+XvRpaRfc65H/68F+jl9svEhzyLFWoccWVc+eyTHq/twydjlKRln7jX9bNMkMJnbhoFRL1xCqmKx6/yi2fYXa/c5/e846PV/5fW0/7OPx/yfcjzop34ulxdGGvDuU8mMXaX507lBuiN6ueadmQeT/p/93vf/1O+G//sP5fw/eL3o/5JLif8zVxs+Tlir9S26UyeFQQvJGBE7FvaFZ9LfN+1y+WjbItSb3GmXvXd15v8zroH/HxgE/D+aGPx/18vi/z07PeZNPRKxe/Kh0Ae8toxscCO4zBkYXArk9C1SxJUYjBkYPPIVtbbuTftz3cz//2O9wP/75iSAXdO72/dt2HL5F6YlfBW4MiJYXMiBiW3t7azHBx+V/t89N+H/8a+1//e9K/9attDp5LQjYX8SuvVL8RoAkmxa65299Erq1FnHo0qrl7t4BddriIs4MrM3rfWcFd+pGwVSAwBZ0bKP8yrZPAAAAABJRU5ErkJggg==') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.youtube {\n\ +.linkify.youtube::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAMCAYAAABr5z2BAAABIklEQVQoz53LvUrDUBjG8bOoOammSf1IoBSvoCB4JeIqOHgBLt6AIMRBBQelWurQ2kERnMRBsBUcIp5FJSBI5oQsJVkkUHh8W0o5nhaFHvjBgef/Mq+Q46RJBMkI/vE+aOus956tnEswIZe1LV0QyJ5sE2GzgZfVMtRNIdiDpccEssdlB1mW4bvTwdvWJtRdErM7U+8S/FJykCRJX5qm+KpVce8UMNLRLbulz4iSjTAMh6Iowsd5BeNadp3nUF0VlxAEwZBotXC0Usa4ll3meZdA1iguwvf9vpvDA2wvmKgYGtSud8suDB4TyGr2PF49D/vra9jRZ1BVdknMzgwuCGSnZEObwu6sBnVTCHZiaC7BhFx2PKdxUidiAH/4lLo9Mv0DELVs9qsOHXwAAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ @@ -3850,12 +4711,56 @@ report: }\n\ #captchaContainerAlt td:nth-child(2) {\n\ display: table-cell !important;\n\ -}\n", +}\n\ +/* Archive reports */\n\ +#archive-report {\n\ + padding: 3px;\n\ +}\n\ +#archive-report-enabled {\n\ + vertical-align: middle;\n\ +}\n\ +#archive-report > label {\n\ + display: block;\n\ +}\n\ +#archive-report-reason {\n\ + display: block;\n\ + width: 98%;\n\ +}\n\ +.archive-report-success {\n\ + color: green;\n\ +}\n\ +.archive-report-error {\n\ + color: red;\n\ +}", www: "#captcha-cnt {\n\ height: auto;\n\ -}\n" +}\n\ +:root:not(.js-enabled) #form {\n\ + display: block;\n\ +}\n\ +#bd > div[style], #bd > div[style] > * {\n\ + height: auto !important;\n\ + margin: 0 !important;\n\ + font-size: 0;\n\ +}\n", + +sub: function(css) { + var variables = { + site: g.SITE.selectors + }; + return css.replace(/\$[\w\$]+/g, function(name) { + var words = name.slice(1).split('$'); + var sel = variables; + for (var i = 0; i < words.length; i++) { + if (typeof sel !== 'object') return ':not(*)'; + sel = $.getOwn(sel, words[i]); + } + if (typeof sel !== 'string') return ':not(*)'; + return sel; + }); +} }; @@ -3917,104 +4822,180 @@ $ = (function() { } }; + $.dict = function() { + return Object.create(null); + }; + + $.dict.clone = function(obj) { + var arr, i, j, key, map, ref, val; + if (typeof obj !== 'object' || obj === null) { + return obj; + } else if (obj instanceof Array) { + arr = []; + for (i = j = 0, ref = obj.length; j < ref; i = j += 1) { + arr.push($.dict.clone(obj[i])); + } + return arr; + } else { + map = Object.create(null); + for (key in obj) { + val = obj[key]; + map[key] = $.dict.clone(val); + } + return map; + } + }; + + $.dict.json = function(str) { + return $.dict.clone(JSON.parse(str)); + }; + + $.hasOwn = function(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); + }; + + $.getOwn = function(obj, key) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + return obj[key]; + } else { + return void 0; + } + }; + $.ajax = (function() { - var lastModified; - lastModified = {}; - return function(url, options, extra) { - var err, event, form, i, len, r, ref, ref1, type, upCallbacks, whenModified; + var pageXHR; + try { + pageXHR = window.wrappedJSObject && !XMLHttpRequest.wrappedJSObject ? XPCNativeWrapper(window.wrappedJSObject.XMLHttpRequest) : XMLHttpRequest; + } catch (error) { + pageXHR = XMLHttpRequest; + } + return function(url, options) { + var err, form, headers, key, onloadend, onprogress, r, ref, responseType, timeout, type, value, withCredentials; if (options == null) { options = {}; } - if (extra == null) { - extra = {}; + if (options.responseType == null) { + options.responseType = 'json'; } - type = extra.type, whenModified = extra.whenModified, upCallbacks = extra.upCallbacks, form = extra.form; - url = url.replace(/^((?:https?:)?\/\/(?:\w+\.)?4c(?:ha|d)n\.org)\/adv\//, '$1//adv/'); - r = new XMLHttpRequest(); - type || (type = form && 'post' || 'get'); + options.type || (options.type = options.form && 'post' || 'get'); + url = url.replace(/^((?:https?:)?\/\/(?:\w+\.)?(?:4chan|4channel|4cdn)\.org)\/adv\//, '$1//adv/'); + onloadend = options.onloadend, timeout = options.timeout, responseType = options.responseType, withCredentials = options.withCredentials, type = options.type, onprogress = options.onprogress, form = options.form, headers = options.headers; + r = new pageXHR(); try { r.open(type, url, true); - if (whenModified) { - if (((ref = lastModified[whenModified]) != null ? ref[url] : void 0) != null) { - r.setRequestHeader('If-Modified-Since', lastModified[whenModified][url]); - } - $.on(r, 'load', function() { - return (lastModified[whenModified] || (lastModified[whenModified] = {}))[url] = r.getResponseHeader('Last-Modified'); - }); - } - if (/\.json$/.test(url)) { - if (options.responseType == null) { - options.responseType = 'json'; - } - } - $.extend(r, options); - $.extend(r.upload, upCallbacks); + ref = headers || {}; + for (key in ref) { + value = ref[key]; + r.setRequestHeader(key, value); + } + $.extend(r, { + onloadend: onloadend, + timeout: timeout, + responseType: responseType, + withCredentials: withCredentials + }); + $.extend(r.upload, { + onprogress: onprogress + }); $.on(r, 'error', function() { if (!r.status) { - return c.error("4chan X failed to load: " + url); + return c.warn("4chan X failed to load: " + url); } }); r.send(form); - } catch (_error) { - err = _error; + } catch (error) { + err = error; if (err.result !== 0x805e0006) { throw err; } - ref1 = ['error', 'loadend']; - for (i = 0, len = ref1.length; i < len; i++) { - event = ref1[i]; - r["on" + event] = options["on" + event]; - $.queueTask($.event, event, null, r); - } + r.onloadend = onloadend; + $.queueTask($.event, 'error', null, r); + $.queueTask($.event, 'loadend', null, r); } return r; }; })(); + $.lastModified = $.dict(); + + $.whenModified = function(url, bucket, cb, options) { + var ajax, headers, params, r, ref, t, timeout, url0; + if (options == null) { + options = {}; + } + timeout = options.timeout, ajax = options.ajax; + params = []; + if ($.engine === 'blink') { + params.push("s=" + bucket); + } + if (url.split('/')[2] === 'a.4cdn.org') { + params.push("t=" + (Date.now())); + } + url0 = url; + if (params.length) { + url += '?' + params.join('&'); + } + headers = $.dict(); + if ((t = (ref = $.lastModified[bucket]) != null ? ref[url0] : void 0) != null) { + headers['If-Modified-Since'] = t; + } + r = (ajax || $.ajax)(url, { + onloadend: function() { + var base; + ((base = $.lastModified)[bucket] || (base[bucket] = $.dict()))[url0] = this.getResponseHeader('Last-Modified'); + return cb.call(this); + }, + timeout: timeout, + headers: headers + }); + return r; + }; + (function() { var reqs; - reqs = {}; + reqs = $.dict(); $.cache = function(url, cb, options) { - var err, req, rm; - if (req = reqs[url]) { - if (req.readyState === 4) { + var ajax, onloadend, req; + if (options == null) { + options = {}; + } + ajax = options.ajax; + if ((req = reqs[url])) { + if (req.callbacks) { + req.callbacks.push(cb); + } else { $.queueTask(function() { - return cb.call(req, req.evt, true); + return cb.call(req, { + isCached: true + }); }); - } else { - req.callbacks.push(cb); } return req; } - rm = function() { - return delete reqs[url]; - }; - try { - if (!(req = $.ajax(url, options))) { - return; + onloadend = function() { + var fn1, j, len, ref; + if (!this.status) { + delete reqs[url]; } - } catch (_error) { - err = _error; - return; - } - $.on(req, 'load', function(e) { - var fn1, i, len, ref; - this.evt = e; ref = this.callbacks; fn1 = (function(_this) { return function(cb) { return $.queueTask(function() { - return cb.call(_this, e, false); + return cb.call(_this, { + isCached: false + }); }); }; })(this); - for (i = 0, len = ref.length; i < len; i++) { - cb = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + cb = ref[j]; fn1(cb); } return delete this.callbacks; + }; + req = (ajax || $.ajax)(url, { + onloadend: onloadend }); - $.on(req, 'abort error', rm); req.callbacks = [cb]; return reqs[url] = req; }; @@ -4030,12 +5011,16 @@ $ = (function() { $.cb = { checked: function() { - $.set(this.name, this.checked); - return Conf[this.name] = this.checked; + if ($.hasOwn(Conf, this.name)) { + $.set(this.name, this.checked); + return Conf[this.name] = this.checked; + } }, value: function() { - $.set(this.name, this.value.trim()); - return Conf[this.name] = this.value; + if ($.hasOwn(Conf, this.name)) { + $.set(this.name, this.value.trim()); + return Conf[this.name] = this.value; + } } }; @@ -4108,19 +5093,19 @@ $ = (function() { }; $.addClass = function() { - var className, classNames, el, i, len; + var className, classNames, el, j, len; el = arguments[0], classNames = 2 <= arguments.length ? slice.call(arguments, 1) : []; - for (i = 0, len = classNames.length; i < len; i++) { - className = classNames[i]; + for (j = 0, len = classNames.length; j < len; j++) { + className = classNames[j]; el.classList.add(className); } }; $.rmClass = function() { - var className, classNames, el, i, len; + var className, classNames, el, j, len; el = arguments[0], classNames = 2 <= arguments.length ? slice.call(arguments, 1) : []; - for (i = 0, len = classNames.length; i < len; i++) { - className = classNames[i]; + for (j = 0, len = classNames.length; j < len; j++) { + className = classNames[j]; el.classList.remove(className); } }; @@ -4150,13 +5135,13 @@ $ = (function() { }; $.nodes = function(nodes) { - var frag, i, len, node; + var frag, j, len, node; if (!(nodes instanceof Array)) { return nodes; } frag = $.frag(); - for (i = 0, len = nodes.length; i < len; i++) { - node = nodes[i]; + for (j = 0, len = nodes.length; j < len; j++) { + node = nodes[j]; frag.appendChild(node); } return frag; @@ -4195,19 +5180,19 @@ $ = (function() { }; $.on = function(el, events, handler) { - var event, i, len, ref; + var event, j, len, ref; ref = events.split(' '); - for (i = 0, len = ref.length; i < len; i++) { - event = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + event = ref[j]; el.addEventListener(event, handler, false); } }; $.off = function(el, events, handler) { - var event, i, len, ref; + var event, j, len, ref; ref = events.split(' '); - for (i = 0, len = ref.length; i < len; i++) { - event = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + event = ref[j]; el.removeEventListener(event, handler, false); } }; @@ -4230,6 +5215,7 @@ $ = (function() { } return root.dispatchEvent(new CustomEvent(event, { bubbles: true, + cancelable: true, detail: detail })); }; @@ -4243,8 +5229,8 @@ $ = (function() { return new CustomEvent('x', { detail: {} }); - } catch (_error) { - err = _error; + } catch (error) { + err = error; unsafeConstructors = { Object: unsafeWindow.Object, Array: unsafeWindow.Array @@ -4268,13 +5254,18 @@ $ = (function() { } return root.dispatchEvent(new CustomEvent(event, { bubbles: true, + cancelable: true, detail: clone(detail) })); }; } })(); - $.open = typeof GM_openInTab !== "undefined" && GM_openInTab !== null ? GM_openInTab : function(url) { + $.modifiedClick = function(e) { + return e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0; + }; + + $.open = (typeof GM !== "undefined" && GM !== null ? GM.openInTab : void 0) != null ? GM.openInTab : typeof GM_openInTab !== "undefined" && GM_openInTab !== null ? GM_openInTab : function(url) { return window.open(url, '_blank'); }; @@ -4324,23 +5315,23 @@ $ = (function() { } })(); - $.globalEval = function(code, data) { - var script; - script = $.el('script', { - textContent: code - }); - if (data) { - $.extend(script.dataset, data); - } - $.add(d.head || doc, script); - return $.rm(script); - }; - $.global = function(fn, data) { + var script; if (doc) { - return $.globalEval("(" + fn + ")();", data); + script = $.el('script', { + textContent: "(" + fn + ").call(document.currentScript.dataset);" + }); + if (data) { + $.extend(script.dataset, data); + } + $.add(d.head || doc, script); + $.rm(script); + return script.dataset; } else { - return fn(); + try { + fn.call(data); + } catch (error) {} + return data; } }; @@ -4363,6 +5354,34 @@ $ = (function() { return video.mozHasAudio || !!video.webkitAudioDecodedByteCount; }; + $.luma = function(rgb) { + return rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114; + }; + + $.unescape = function(text) { + if (text == null) { + return text; + } + return text.replace(/<[^>]*>/g, '').replace(/&(amp|#039|quot|lt|gt|#44);/g, function(c) { + return { + '&': '&', + ''': "'", + '"': '"', + '<': '<', + '>': '>', + ',': ',' + }[c]; + }); + }; + + $.isImage = function(url) { + return /\.(jpe?g|jfif|png|gif|bmp|webp|avif|jxl)$/i.test(url); + }; + + $.isVideo = function(url) { + return /\.(webm|mp4|ogv)$/i.test(url); + }; + $.engine = (function() { if (/Edge\//.test(navigator.userAgent)) { return 'edge'; @@ -4380,252 +5399,368 @@ $ = (function() { $.platform = 'userscript'; - try { - localStorage.getItem('x'); - $.hasStorage = true; - } catch (_error) { - $.hasStorage = false; - } + $.hasStorage = (function() { + try { + if (localStorage.getItem(g.NAMESPACE + 'hasStorage') === 'true') { + return true; + } + localStorage.setItem(g.NAMESPACE + 'hasStorage', 'true'); + return localStorage.getItem(g.NAMESPACE + 'hasStorage') === 'true'; + } catch (error) { + return false; + } + })(); $.item = function(key, val) { var item; - item = {}; + item = $.dict(); item[key] = val; return item; }; - $.syncing = {}; - - if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { - $.getValue = GM_getValue; - $.listValues = function() { - return GM_listValues(); - }; - } else if ($.hasStorage) { - $.getValue = function(key) { - return localStorage[key]; + $.oneItemSugar = function(fn) { + return function(key, val, cb) { + if (typeof key === 'string') { + return fn($.item(key, val), cb); + } else { + return fn(key, val); + } }; - $.listValues = function() { - var key, results; + }; + + $.syncing = $.dict(); + + $.securityCheck = function(data) { + if (location.protocol !== 'https:') { + return delete data['Redirect to HTTPS']; + } + }; + + if (((typeof GM !== "undefined" && GM !== null ? GM.deleteValue : void 0) != null) && window.BroadcastChannel && (typeof GM_addValueChangeListener === "undefined" || GM_addValueChangeListener === null)) { + $.syncChannel = new BroadcastChannel(g.NAMESPACE + 'sync'); + $.on($.syncChannel, 'message', function(e) { + var cb, key, ref, results, val; + ref = e.data; results = []; - for (key in localStorage) { - if (key.slice(0, g.NAMESPACE.length) === g.NAMESPACE) { - results.push(key); + for (key in ref) { + val = ref[key]; + if ((cb = $.syncing[key])) { + results.push(cb($.dict.json(JSON.stringify(val)), key)); } } return results; + }); + $.sync = function(key, cb) { + return $.syncing[key] = cb; }; - } else { - $.getValue = function() {}; - $.listValues = function() { - return []; - }; - } - - if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { - $.setValue = GM_setValue; - $.deleteValue = GM_deleteValue; - } else if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { - $.oldValue = {}; - $.setValue = function(key, val) { - GM_setValue(key, val); - if (key in $.syncing) { - $.oldValue[key] = val; - if ($.hasStorage) { - return localStorage[key] = val; - } + $.forceSync = function() {}; + $["delete"] = function(keys, cb) { + var key; + if (!(keys instanceof Array)) { + keys = [keys]; } - }; - $.deleteValue = function(key) { - GM_deleteValue(key); - if (key in $.syncing) { - delete $.oldValue[key]; - if ($.hasStorage) { - return localStorage.removeItem(key); + return Promise.all((function() { + var j, len, results; + results = []; + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + results.push(GM.deleteValue(g.NAMESPACE + key)); } - } - }; - if (!$.hasStorage) { - $.cantSync = true; - } - } else if ($.hasStorage) { - $.oldValue = {}; - $.setValue = function(key, val) { - if (key in $.syncing) { - $.oldValue[key] = val; - } - return localStorage[key] = val; - }; - $.deleteValue = function(key) { - if (key in $.syncing) { - delete $.oldValue[key]; - } - return localStorage.removeItem(key); + return results; + })()).then(function() { + var items, j, key, len; + items = $.dict(); + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + items[key] = void 0; + } + $.syncChannel.postMessage(items); + return typeof cb === "function" ? cb() : void 0; + }); }; - } else { - $.setValue = function() {}; - $.deleteValue = function() {}; - $.cantSync = $.cantSet = true; - } - - if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { - $.sync = function(key, cb) { - return $.syncing[key] = GM_addValueChangeListener(g.NAMESPACE + key, function(key2, oldValue, newValue, remote) { - if (remote) { - if (newValue !== void 0) { - newValue = JSON.parse(newValue); + $.get = $.oneItemSugar(function(items, cb) { + var key, keys; + keys = Object.keys(items); + return Promise.all((function() { + var j, len, results; + results = []; + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + results.push(GM.getValue(g.NAMESPACE + key)); + } + return results; + })()).then(function(values) { + var i, j, len, val; + for (i = j = 0, len = values.length; j < len; i = ++j) { + val = values[i]; + if (val) { + items[keys[i]] = $.dict.json(val); } - return cb(newValue, key); } + return cb(items); + }); + }); + $.set = $.oneItemSugar(function(items, cb) { + var key, val; + $.securityCheck(items); + return Promise.all((function() { + var results; + results = []; + for (key in items) { + val = items[key]; + results.push(GM.setValue(g.NAMESPACE + key, JSON.stringify(val))); + } + return results; + })()).then(function() { + $.syncChannel.postMessage(items); + return typeof cb === "function" ? cb() : void 0; + }); + }); + $.clear = function(cb) { + return GM.listValues().then(function(keys) { + return $["delete"](keys.map(function(key) { + return key.replace(g.NAMESPACE, ''); + }), cb); + })["catch"](function() { + return $["delete"](Object.keys(Conf).concat(['previousversion', 'QR Size', 'QR.persona']), cb); }); }; - $.forceSync = function() {}; - } else if ((typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) || $.hasStorage) { - $.sync = function(key, cb) { - key = g.NAMESPACE + key; - $.syncing[key] = cb; - return $.oldValue[key] = $.getValue(key); - }; - (function() { - var onChange; - onChange = function(arg) { - var cb, key, newValue; - key = arg.key, newValue = arg.newValue; - if (!(cb = $.syncing[key])) { - return; + } else { + if (typeof GM_deleteValue === "undefined" || GM_deleteValue === null) { + $.perProtocolSettings = true; + } + if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { + $.getValue = GM_getValue; + $.listValues = function() { + return GM_listValues(); + }; + } else if ($.hasStorage) { + $.getValue = function(key) { + return localStorage.getItem(key); + }; + $.listValues = function() { + var key, results; + results = []; + for (key in localStorage) { + if (key.slice(0, g.NAMESPACE.length) === g.NAMESPACE) { + results.push(key); + } } - if (newValue != null) { - if (newValue === $.oldValue[key]) { - return; + return results; + }; + } else { + $.getValue = function() {}; + $.listValues = function() { + return []; + }; + } + if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { + $.setValue = GM_setValue; + $.deleteValue = GM_deleteValue; + } else if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { + $.oldValue = $.dict(); + $.setValue = function(key, val) { + GM_setValue(key, val); + if (key in $.syncing) { + $.oldValue[key] = val; + if ($.hasStorage) { + return localStorage.setItem(key, val); } - $.oldValue[key] = newValue; - return cb(JSON.parse(newValue), key.slice(g.NAMESPACE.length)); - } else { - if ($.oldValue[key] == null) { - return; + } + }; + $.deleteValue = function(key) { + GM_deleteValue(key); + if (key in $.syncing) { + delete $.oldValue[key]; + if ($.hasStorage) { + return localStorage.removeItem(key); } + } + }; + if (!$.hasStorage) { + $.cantSync = true; + } + } else if ($.hasStorage) { + $.oldValue = $.dict(); + $.setValue = function(key, val) { + if (key in $.syncing) { + $.oldValue[key] = val; + } + return localStorage.setItem(key, val); + }; + $.deleteValue = function(key) { + if (key in $.syncing) { delete $.oldValue[key]; - return cb(void 0, key.slice(g.NAMESPACE.length)); } + return localStorage.removeItem(key); }; - $.on(window, 'storage', onChange); - return $.forceSync = function(key) { - key = g.NAMESPACE + key; - return onChange({ - key: key, - newValue: $.getValue(key) + } else { + $.setValue = function() {}; + $.deleteValue = function() {}; + $.cantSync = $.cantSet = true; + } + if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { + $.sync = function(key, cb) { + return $.syncing[key] = GM_addValueChangeListener(g.NAMESPACE + key, function(key2, oldValue, newValue, remote) { + if (remote) { + if (newValue !== void 0) { + newValue = $.dict.json(newValue); + } + return cb(newValue, key); + } }); }; - })(); - } else { - $.sync = function() {}; - $.forceSync = function() {}; - } - - $["delete"] = function(keys) { - var i, key, len; - if (!(keys instanceof Array)) { - keys = [keys]; - } - for (i = 0, len = keys.length; i < len; i++) { - key = keys[i]; - $.deleteValue(g.NAMESPACE + key); - } - }; - - $.get = function(key, val, cb) { - var items; - if (typeof cb === 'function') { - items = $.item(key, val); + $.forceSync = function() {}; + } else if ((typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) || $.hasStorage) { + $.sync = function(key, cb) { + key = g.NAMESPACE + key; + $.syncing[key] = cb; + return $.oldValue[key] = $.getValue(key); + }; + (function() { + var onChange; + onChange = function(arg) { + var cb, key, newValue; + key = arg.key, newValue = arg.newValue; + if (!(cb = $.syncing[key])) { + return; + } + if (newValue != null) { + if (newValue === $.oldValue[key]) { + return; + } + $.oldValue[key] = newValue; + return cb($.dict.json(newValue), key.slice(g.NAMESPACE.length)); + } else { + if ($.oldValue[key] == null) { + return; + } + delete $.oldValue[key]; + return cb(void 0, key.slice(g.NAMESPACE.length)); + } + }; + $.on(window, 'storage', onChange); + return $.forceSync = function(key) { + key = g.NAMESPACE + key; + return onChange({ + key: key, + newValue: $.getValue(key) + }); + }; + })(); } else { - items = key; - cb = val; + $.sync = function() {}; + $.forceSync = function() {}; } - return $.queueTask($.getSync, items, cb); - }; - - $.getSync = function(items, cb) { - var key, val2; - for (key in items) { - if ((val2 = $.getValue(g.NAMESPACE + key))) { - items[key] = JSON.parse(val2); + $["delete"] = function(keys) { + var j, key, len; + if (!(keys instanceof Array)) { + keys = [keys]; } - } - return cb(items); - }; - - $.set = function(keys, val, cb) { - var key, value; - if (typeof keys === 'string') { - $.setValue(g.NAMESPACE + keys, JSON.stringify(val)); - } else { - for (key in keys) { - value = keys[key]; - $.setValue(g.NAMESPACE + key, JSON.stringify(value)); + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + $.deleteValue(g.NAMESPACE + key); } - cb = val; - } - return typeof cb === "function" ? cb() : void 0; - }; - - $.clear = function(cb) { - var id; - $["delete"](Object.keys(Conf)); - $["delete"](['previousversion', 'AutoWatch', 'QR Size', 'captchas', 'QR.persona', 'hiddenPSA']); - $["delete"]((function() { - var i, len, ref, results; - ref = ['embedding', 'updater', 'thread-stats', 'thread-watcher', 'qr']; - results = []; - for (i = 0, len = ref.length; i < len; i++) { - id = ref[i]; - results.push(id + ".position"); + }; + $.get = $.oneItemSugar(function(items, cb) { + return $.queueTask($.getSync, items, cb); + }); + $.getSync = function(items, cb) { + var err, key, val2; + for (key in items) { + if ((val2 = $.getValue(g.NAMESPACE + key))) { + try { + items[key] = $.dict.json(val2); + } catch (error) { + err = error; + if (!/^(?:undefined)*$/.test(val2)) { + throw err; + } + } + } } - return results; - })()); - try { - $["delete"]($.listValues().map(function(key) { - return key.replace(g.NAMESPACE, ''); - })); - } catch (_error) {} - return typeof cb === "function" ? cb() : void 0; - }; + return cb(items); + }; + $.set = $.oneItemSugar(function(items, cb) { + $.securityCheck(items); + return $.queueTask(function() { + var key, value; + for (key in items) { + value = items[key]; + $.setValue(g.NAMESPACE + key, JSON.stringify(value)); + } + return typeof cb === "function" ? cb() : void 0; + }); + }); + $.clear = function(cb) { + $["delete"](Object.keys(Conf)); + $["delete"](['previousversion', 'QR Size', 'QR.persona']); + try { + $["delete"]($.listValues().map(function(key) { + return key.replace(g.NAMESPACE, ''); + })); + } catch (error) {} + return typeof cb === "function" ? cb() : void 0; + }; + } return $; }).call(this); $$ = (function() { - var slice = [].slice; + var $$, + slice = [].slice; - return function(selector, root) { + $$ = function(selector, root) { if (root == null) { root = d.body; } return slice.call(root.querySelectorAll(selector)); }; + return $$; + }).call(this); CrossOrigin = (function() { - var CrossOrigin; + var CrossOrigin, Request; CrossOrigin = { binary: function(url, cb, headers) { - var options, ref, workaround; + var fallback, gmOptions; if (headers == null) { - headers = {}; + headers = $.dict(); } - url = url.replace(/^((?:https?:)?\/\/(?:\w+\.)?4c(?:ha|d)n\.org)\/adv\//, '$1//adv/'); - workaround = $.engine === 'gecko' && (typeof GM_info !== "undefined" && GM_info !== null) && /^[0-2]\.|^3\.[01](?!\d)/.test(GM_info.version); - workaround || (workaround = /PaleMoon\//.test(navigator.userAgent)); - workaround || (workaround = (typeof GM_info !== "undefined" && GM_info !== null ? (ref = GM_info.script) != null ? ref.includeJSB : void 0 : void 0) != null); - options = { + url = url.replace(/^((?:https?:)?\/\/(?:\w+\.)?(?:4chan|4channel|4cdn)\.org)\/adv\//, '$1//adv/'); + fallback = function() { + return $.ajax(url, { + headers: headers, + responseType: 'arraybuffer', + onloadend: function() { + if (this.status && this.response) { + return cb(new Uint8Array(this.response), this.getAllResponseHeaders()); + } else { + return cb(null); + } + } + }); + }; + if (!(((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) != null) || (typeof GM_xmlhttpRequest !== "undefined" && GM_xmlhttpRequest !== null))) { + fallback(); + return; + } + gmOptions = { method: "GET", url: url, headers: headers, + responseType: 'arraybuffer', + overrideMimeType: 'text/plain; charset=x-user-defined', onload: function(xhr) { - var contentDisposition, contentType, data, i, r, ref1, ref2; - if (workaround) { + var data, i, r; + if (xhr.response instanceof ArrayBuffer) { + data = new Uint8Array(xhr.response); + } else { r = xhr.responseText; data = new Uint8Array(r.length); i = 0; @@ -4633,12 +5768,8 @@ CrossOrigin = (function() { data[i] = r.charCodeAt(i); i++; } - } else { - data = new Uint8Array(xhr.response); } - contentType = (ref1 = xhr.responseHeaders.match(/Content-Type:\s*(.*)/i)) != null ? ref1[1] : void 0; - contentDisposition = (ref2 = xhr.responseHeaders.match(/Content-Disposition:\s*(.*)/i)) != null ? ref2[1] : void 0; - return cb(data, contentType, contentDisposition); + return cb(data, xhr.responseHeaders); }, onerror: function() { return cb(null); @@ -4647,27 +5778,28 @@ CrossOrigin = (function() { return cb(null); } }; - if (workaround) { - options.overrideMimeType = 'text/plain; charset=x-user-defined'; - } else { - options.responseType = 'arraybuffer'; + try { + return ((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) || GM_xmlhttpRequest)(gmOptions); + } catch (error) { + return fallback(); } - return GM_xmlhttpRequest(options); }, file: function(url, cb) { - return CrossOrigin.binary(url, function(data, contentType, contentDisposition) { - var blob, match, mime, name, ref, ref1, ref2, ref3; + return CrossOrigin.binary(url, function(data, headers) { + var blob, contentDisposition, contentType, match, mime, name, ref, ref1, ref2, ref3, ref4; if (data == null) { return cb(null); } - name = (ref = url.match(/([^\/]+)\/*$/)) != null ? ref[1] : void 0; + name = (ref = url.match(/([^\/?#]+)\/*(?:$|[?#])/)) != null ? ref[1] : void 0; + contentType = (ref1 = headers.match(/Content-Type:\s*(.*)/i)) != null ? ref1[1] : void 0; + contentDisposition = (ref2 = headers.match(/Content-Disposition:\s*(.*)/i)) != null ? ref2[1] : void 0; mime = (contentType != null ? contentType.match(/[^;]*/)[0] : void 0) || 'application/octet-stream'; - match = (contentDisposition != null ? (ref1 = contentDisposition.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)) != null ? ref1[1] : void 0 : void 0) || (contentType != null ? (ref2 = contentType.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)) != null ? ref2[1] : void 0 : void 0); + match = (contentDisposition != null ? (ref3 = contentDisposition.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)) != null ? ref3[1] : void 0 : void 0) || (contentType != null ? (ref4 = contentType.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)) != null ? ref4[1] : void 0 : void 0); if (match) { name = match.replace(/\\"/g, '"'); } - if ((typeof GM_info !== "undefined" && GM_info !== null ? (ref3 = GM_info.script) != null ? ref3.includeJSB : void 0 : void 0) != null) { - mime = QR.typeFromExtension[name.match(/[^.]*$/)[0].toLowerCase()] || 'application/octet-stream'; + if (/^text\/plain;\s*charset=x-user-defined$/i.test(mime)) { + mime = $.getOwn(QR.typeFromExtension, name.match(/[^.]*$/)[0].toLowerCase()) || 'application/octet-stream'; } blob = new Blob([data], { type: mime @@ -4676,43 +5808,117 @@ CrossOrigin = (function() { return cb(blob); }); }, - json: (function() { - var callbacks, responses; - callbacks = {}; - responses = {}; - return function(url, cb) { - if (responses[url]) { - cb(responses[url]); - return; - } - if (callbacks[url]) { - callbacks[url].push(cb); - return; - } - callbacks[url] = [cb]; - return GM_xmlhttpRequest({ - method: "GET", - url: url + '', - onload: function(xhr) { - var j, len, ref, response; - response = JSON.parse(xhr.responseText); - ref = callbacks[url]; - for (j = 0, len = ref.length; j < len; j++) { - cb = ref[j]; - cb(response); + Request: Request = (function() { + function Request() {} + + Request.prototype.status = 0; + + Request.prototype.statusText = ''; + + Request.prototype.response = null; + + Request.prototype.responseHeaderString = null; + + Request.prototype.getResponseHeader = function(headerName) { + var header, i, j, key, len, ref, ref1, ref2, val; + if ((this.responseHeaders == null) && (this.responseHeaderString != null)) { + this.responseHeaders = $.dict(); + ref = this.responseHeaderString.split('\r\n'); + for (j = 0, len = ref.length; j < len; j++) { + header = ref[j]; + if ((i = header.indexOf(':')) >= 0) { + key = header.slice(0, i).trim().toLowerCase(); + val = header.slice(i + 1).trim(); + this.responseHeaders[key] = val; } - delete callbacks[url]; - return responses[url] = response; - }, - onerror: function() { - return delete callbacks[url]; - }, - onabort: function() { - return delete callbacks[url]; } - }); + } + return (ref1 = (ref2 = this.responseHeaders) != null ? ref2[headerName.toLowerCase()] : void 0) != null ? ref1 : null; + }; + + Request.prototype.abort = function() {}; + + Request.prototype.onloadend = function() {}; + + return Request; + + })(), + ajax: function(url, options) { + var gmOptions, gmReq, headers, onloadend, req, responseType, timeout; + if (options == null) { + options = {}; + } + onloadend = options.onloadend, timeout = options.timeout, responseType = options.responseType, headers = options.headers; + if (responseType == null) { + responseType = 'json'; + } + if (!(((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) != null) || (typeof GM_xmlhttpRequest !== "undefined" && GM_xmlhttpRequest !== null))) { + return $.ajax(url, options); + } + req = new CrossOrigin.Request(); + req.onloadend = onloadend; + gmOptions = { + method: 'GET', + url: url, + headers: headers, + timeout: timeout, + onload: function(xhr) { + var response; + try { + response = (function() { + switch (responseType) { + case 'json': + if (xhr.responseText) { + return JSON.parse(xhr.responseText); + } else { + return null; + } + break; + default: + return xhr.responseText; + } + })(); + $.extend(req, { + response: response, + status: xhr.status, + statusText: xhr.statusText, + responseHeaderString: xhr.responseHeaders + }); + } catch (error) {} + return req.onloadend(); + }, + onerror: function() { + return req.onloadend(); + }, + onabort: function() { + return req.onloadend(); + }, + ontimeout: function() { + return req.onloadend(); + } }; - })() + try { + gmReq = ((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) || GM_xmlhttpRequest)(gmOptions); + } catch (error) { + return $.ajax(url, options); + } + if (gmReq && typeof gmReq.abort === 'function') { + req.abort = function() { + try { + return gmReq.abort(); + } catch (error) {} + }; + } + return req; + }, + cache: function(url, cb) { + return $.cache(url, cb, { + ajax: CrossOrigin.ajax + }); + }, + permission: function(cb) { + return cb(); + } }; return CrossOrigin; @@ -4728,12 +5934,35 @@ Board = (function() { }; function Board(ID) { + var ref; this.ID = ID; + this.boardID = this.ID; + this.siteID = g.SITE.ID; this.threads = new SimpleDict(); this.posts = new SimpleDict(); + this.config = ((ref = BoardConfig.boards) != null ? ref[this.ID] : void 0) || {}; g.boards[this] = this; } + Board.prototype.cooldowns = function() { + var c, c2, i, key, len, ref; + c2 = (this.config || {}).cooldowns || {}; + c = { + thread: c2.threads || 0, + reply: c2.replies || 0, + image: c2.images || 0, + thread_global: 300 + }; + if (d.cookie.indexOf('pass_enabled=1') >= 0) { + ref = ['reply', 'image']; + for (i = 0, len = ref.length; i < len; i++) { + key = ref[i]; + c[key] = Math.ceil(c[key] / 2); + } + } + return c; + }; + return Board; })(); @@ -4752,6 +5981,8 @@ Callbacks = (function() { Callbacks.CatalogThread = new Callbacks('Catalog Thread'); + Callbacks.CatalogThreadNative = new Callbacks('Catalog Thread'); + function Callbacks(type) { this.type = type; this.keys = []; @@ -4766,19 +5997,26 @@ Callbacks = (function() { return this[name] = cb; }; - Callbacks.prototype.execute = function(node, keys) { + Callbacks.prototype.execute = function(node, keys, force) { var err, errors, i, len, name, ref, ref1, ref2; if (keys == null) { keys = this.keys; } + if (force == null) { + force = false; + } + if (node.callbacksExecuted && !force) { + return; + } + node.callbacksExecuted = true; for (i = 0, len = keys.length; i < len; i++) { name = keys[i]; try { if ((ref = this[name]) != null) { ref.call(node); } - } catch (_error) { - err = _error; + } catch (error) { + err = error; if (!errors) { errors = []; } @@ -4811,17 +6049,19 @@ CatalogThread = (function() { }; function CatalogThread(root, thread) { + var post; this.thread = thread; this.ID = this.thread.ID; this.board = this.thread.board; + post = this.thread.OP.nodes.post; this.nodes = { root: root, - thumb: $('.catalog-thumb', root), - icons: $('.catalog-icons', root), - postCount: $('.post-count', root), - fileCount: $('.file-count', root), - pageCount: $('.page-count', root), - comment: $('.comment', root) + thumb: $('.catalog-thumb', post), + icons: $('.catalog-icons', post), + postCount: $('.post-count', post), + fileCount: $('.file-count', post), + pageCount: $('.page-count', post), + replies: null }; this.thread.catalogView = this; } @@ -4834,6 +6074,34 @@ CatalogThread = (function() { }).call(this); +CatalogThreadNative = (function() { + var CatalogThreadNative; + + CatalogThreadNative = (function() { + CatalogThreadNative.prototype.toString = function() { + return this.ID; + }; + + function CatalogThreadNative(root) { + this.nodes = { + root: root, + thumb: $(g.SITE.selectors.catalog.thumb, root) + }; + this.siteID = g.SITE.ID; + this.boardID = this.nodes.thumb.parentNode.pathname.split(/\/+/)[1]; + this.board = g.boards[this.boardID] || new Board(this.boardID); + this.ID = this.threadID = +(root.dataset.id || root.id).match(/\d*$/)[0]; + this.thread = this.board.threads.get(this.ID) || new Thread(this.ID, this.board); + } + + return CatalogThreadNative; + + })(); + + return CatalogThreadNative; + +}).call(this); + Connection = (function() { var Connection, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; @@ -4861,15 +6129,15 @@ Connection = (function() { }; Connection.prototype.onMessage = function(e) { - var base, data, type, value; + var data, type, value; if (!(e.source === this.targetWindow() && e.origin === this.origin && typeof e.data === 'string' && e.data.slice(0, g.NAMESPACE.length) === g.NAMESPACE)) { return; } data = JSON.parse(e.data.slice(g.NAMESPACE.length)); for (type in data) { value = data[type]; - if (typeof (base = this.cb)[type] === "function") { - base[type](value); + if ($.hasOwn(this.cb, type)) { + this.cb[type](value); } } }; @@ -4887,13 +6155,13 @@ DataBoard = (function() { bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; DataBoard = (function() { - DataBoard.keys = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads', 'customTitles']; + DataBoard.keys = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads', 'watcherLastModified', 'customTitles']; - function DataBoard(key, sync, dontClean) { + function DataBoard(key1, sync, dontClean) { var init; - this.key = key; + this.key = key1; this.onSync = bind(this.onSync, this); - this.data = Conf[this.key]; + this.initData(Conf[this.key]); $.sync(this.key, this.onSync); if (!dontClean) { this.clean(); @@ -4910,71 +6178,208 @@ DataBoard = (function() { $.on(d, '4chanXInitFinished', init); } - DataBoard.prototype.save = function(cb) { - return $.set(this.key, this.data, cb); + DataBoard.prototype.initData = function(data1) { + var base, boards, lastChecked, name, ref; + this.data = data1; + if (this.data.boards) { + ref = this.data, boards = ref.boards, lastChecked = ref.lastChecked; + this.data['4chan.org'] = { + boards: boards, + lastChecked: lastChecked + }; + delete this.data.boards; + delete this.data.lastChecked; + } + return (base = this.data)[name = g.SITE.ID] || (base[name] = { + boards: $.dict() + }); }; - DataBoard.prototype["delete"] = function(arg) { - var boardID, postID, ref, threadID; - boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID; - $.forceSync(this.key); - if (postID) { - if (!((ref = this.data.boards[boardID]) != null ? ref[threadID] : void 0)) { - return; - } - delete this.data.boards[boardID][threadID][postID]; - this.deleteIfEmpty({ - boardID: boardID, - threadID: threadID - }); - } else if (threadID) { - if (!this.data.boards[boardID]) { - return; - } - delete this.data.boards[boardID][threadID]; - this.deleteIfEmpty({ - boardID: boardID - }); - } else { - delete this.data.boards[boardID]; + DataBoard.prototype.changes = []; + + DataBoard.prototype.save = function(change, cb) { + change(); + this.changes.push(change); + return $.get(this.key, { + boards: $.dict() + }, (function(_this) { + return function(items) { + var i, len, needSync, ref; + if (!_this.changes.length) { + return; + } + needSync = (items[_this.key].version || 0) > (_this.data.version || 0); + if (needSync) { + _this.initData(items[_this.key]); + ref = _this.changes; + for (i = 0, len = ref.length; i < len; i++) { + change = ref[i]; + change(); + } + } + _this.changes = []; + _this.data.version = (_this.data.version || 0) + 1; + return $.set(_this.key, _this.data, function() { + if (needSync) { + if (typeof _this.sync === "function") { + _this.sync(); + } + } + return typeof cb === "function" ? cb() : void 0; + }); + }; + })(this)); + }; + + DataBoard.prototype.forceSync = function(cb) { + return $.get(this.key, { + boards: $.dict() + }, (function(_this) { + return function(items) { + var change, i, len, ref; + if ((items[_this.key].version || 0) > (_this.data.version || 0)) { + _this.initData(items[_this.key]); + ref = _this.changes; + for (i = 0, len = ref.length; i < len; i++) { + change = ref[i]; + change(); + } + if (typeof _this.sync === "function") { + _this.sync(); + } + } + return typeof cb === "function" ? cb() : void 0; + }; + })(this)); + }; + + DataBoard.prototype["delete"] = function(arg, cb) { + var boardID, postID, siteID, threadID; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID; + siteID || (siteID = g.SITE.ID); + if (!this.data[siteID]) { + return; } - return this.save(); + return this.save((function(_this) { + return function() { + var ref; + if (postID) { + if (!((ref = _this.data[siteID].boards[boardID]) != null ? ref[threadID] : void 0)) { + return; + } + delete _this.data[siteID].boards[boardID][threadID][postID]; + return _this.deleteIfEmpty({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }); + } else if (threadID) { + if (!_this.data[siteID].boards[boardID]) { + return; + } + delete _this.data[siteID].boards[boardID][threadID]; + return _this.deleteIfEmpty({ + siteID: siteID, + boardID: boardID + }); + } else { + return delete _this.data[siteID].boards[boardID]; + } + }; + })(this), cb); }; DataBoard.prototype.deleteIfEmpty = function(arg) { - var boardID, threadID; - boardID = arg.boardID, threadID = arg.threadID; - $.forceSync(this.key); + var boardID, siteID, threadID; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID; + if (!this.data[siteID]) { + return; + } if (threadID) { - if (!Object.keys(this.data.boards[boardID][threadID]).length) { - delete this.data.boards[boardID][threadID]; + if (!Object.keys(this.data[siteID].boards[boardID][threadID]).length) { + delete this.data[siteID].boards[boardID][threadID]; return this.deleteIfEmpty({ + siteID: siteID, boardID: boardID }); } - } else if (!Object.keys(this.data.boards[boardID]).length) { - return delete this.data.boards[boardID]; + } else if (!Object.keys(this.data[siteID].boards[boardID]).length) { + return delete this.data[siteID].boards[boardID]; } }; - DataBoard.prototype.set = function(arg, cb) { - var base, base1, base2, boardID, postID, threadID, val; - boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID, val = arg.val; - $.forceSync(this.key); + DataBoard.prototype.set = function(data, cb) { + return this.save((function(_this) { + return function() { + return _this.setUnsafe(data); + }; + })(this), cb); + }; + + DataBoard.prototype.setUnsafe = function(arg) { + var base, base1, base2, base3, boardID, postID, siteID, threadID, val; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID, val = arg.val; + siteID || (siteID = g.SITE.ID); + (base = this.data)[siteID] || (base[siteID] = { + boards: $.dict() + }); if (postID !== void 0) { - ((base = ((base1 = this.data.boards)[boardID] || (base1[boardID] = {})))[threadID] || (base[threadID] = {}))[postID] = val; + return ((base1 = ((base2 = this.data[siteID].boards)[boardID] || (base2[boardID] = $.dict())))[threadID] || (base1[threadID] = $.dict()))[postID] = val; } else if (threadID !== void 0) { - ((base2 = this.data.boards)[boardID] || (base2[boardID] = {}))[threadID] = val; + return ((base3 = this.data[siteID].boards)[boardID] || (base3[boardID] = $.dict()))[threadID] = val; } else { - this.data.boards[boardID] = val; + return this.data[siteID].boards[boardID] = val; + } + }; + + DataBoard.prototype.extend = function(arg, cb) { + var boardID, postID, siteID, threadID, val; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID, val = arg.val; + return this.save((function(_this) { + return function() { + var key, oldVal, subVal; + oldVal = _this.get({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + postID: postID, + defaultValue: $.dict() + }); + for (key in val) { + subVal = val[key]; + if (typeof subVal === 'undefined') { + delete oldVal[key]; + } else { + oldVal[key] = subVal; + } + } + return _this.setUnsafe({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + postID: postID, + val: oldVal + }); + }; + })(this), cb); + }; + + DataBoard.prototype.setLastChecked = function(key) { + if (key == null) { + key = 'lastChecked'; } - return this.save(cb); + return this.save((function(_this) { + return function() { + return _this.data[key] = Date.now(); + }; + })(this)); }; DataBoard.prototype.get = function(arg) { - var ID, board, boardID, defaultValue, i, len, postID, thread, threadID, val; - boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID, defaultValue = arg.defaultValue; - if (board = this.data.boards[boardID]) { + var ID, board, boardID, defaultValue, i, len, postID, ref, siteID, thread, threadID, val; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID, defaultValue = arg.defaultValue; + siteID || (siteID = g.SITE.ID); + if (board = (ref = this.data[siteID]) != null ? ref.boards[boardID] : void 0) { if (threadID == null) { if (postID != null) { for (thread = i = 0, len = board.length; i < len; thread = ++i) { @@ -4994,53 +6399,66 @@ DataBoard = (function() { return val || defaultValue; }; - DataBoard.prototype.forceSync = function() { - return $.forceSync(this.key); - }; - DataBoard.prototype.clean = function() { - var boardID, now, ref, val; - $.forceSync(this.key); - ref = this.data.boards; + var boardID, now, ref, ref1, siteID, val; + siteID = g.SITE.ID; + ref = this.data[siteID].boards; for (boardID in ref) { val = ref[boardID]; this.deleteIfEmpty({ + siteID: siteID, boardID: boardID }); } now = Date.now(); - if ((this.data.lastChecked || 0) < now - 2 * $.HOUR) { - this.data.lastChecked = now; - for (boardID in this.data.boards) { + if (!((now - 2 * $.HOUR < (ref1 = this.data[siteID].lastChecked || 0) && ref1 <= now))) { + this.data[siteID].lastChecked = now; + for (boardID in this.data[siteID].boards) { this.ajaxClean(boardID); } } }; DataBoard.prototype.ajaxClean = function(boardID) { - return $.cache("//a.4cdn.org/" + boardID + "/threads.json", (function(_this) { - return function(e1) { - var ref; - if ((ref = e1.target.status) !== 200 && ref !== 404) { + var base, siteID, that, threadsList; + that = this; + siteID = g.SITE.ID; + threadsList = typeof (base = g.SITE.urls).threadsListJSON === "function" ? base.threadsListJSON({ + siteID: siteID, + boardID: boardID + }) : void 0; + if (!threadsList) { + return; + } + return $.cache(threadsList, function() { + var archiveList, base1, response1; + if (this.status !== 200) { + return; + } + archiveList = typeof (base1 = g.SITE.urls).archiveListJSON === "function" ? base1.archiveListJSON({ + siteID: siteID, + boardID: boardID + }) : void 0; + if (!archiveList) { + return that.ajaxCleanParse(boardID, this.response); + } + response1 = this.response; + return $.cache(archiveList, function() { + if (!(this.status === 200 || (!g.SITE.archivedBoardsKnown && this.status === 404))) { return; } - return $.cache("//a.4cdn.org/" + boardID + "/archive.json", function(e2) { - var ref1; - if ((ref1 = e2.target.status) !== 200 && ref1 !== 404) { - return; - } - return _this.ajaxCleanParse(boardID, e1.target.response, e2.target.response); - }); - }; - })(this)); + return that.ajaxCleanParse(boardID, response1, this.response); + }); + }); }; DataBoard.prototype.ajaxCleanParse = function(boardID, response1, response2) { - var ID, board, i, j, k, len, len1, len2, page, ref, thread, threads; - if (!(board = this.data.boards[boardID])) { + var ID, board, i, j, k, len, len1, len2, page, ref, siteID, thread, threads; + siteID = g.SITE.ID; + if (!(board = this.data[siteID].boards[boardID])) { return; } - threads = {}; + threads = $.dict(); if (response1) { for (i = 0, len = response1.length; i < len; i++) { page = response1[i]; @@ -5062,17 +6480,19 @@ DataBoard = (function() { } } } - this.data.boards[boardID] = threads; + this.data[siteID].boards[boardID] = threads; this.deleteIfEmpty({ + siteID: siteID, boardID: boardID }); - return this.save(); + return $.set(this.key, this.data); }; DataBoard.prototype.onSync = function(data) { - this.data = data || { - boards: {} - }; + if (!((data.version || 0) > (this.data.version || 0))) { + return; + } + this.initData(data); return typeof this.sync === "function" ? this.sync() : void 0; }; @@ -5090,23 +6510,36 @@ Fetcher = (function() { Fetcher = (function() { function Fetcher(boardID1, threadID, postID1, root, quoter) { - var post; + var board, post, ref, that, thread; this.boardID = boardID1; this.threadID = threadID; this.postID = postID1; this.root = root; this.quoter = quoter; - if (post = g.posts[this.boardID + "." + this.postID]) { + if (post = g.posts.get(this.boardID + "." + this.postID)) { + this.insert(post); + return; + } + if ((post = (ref = Index.replyData) != null ? ref[this.boardID + "." + this.postID] : void 0) && (thread = g.threads.get(this.boardID + "." + this.threadID))) { + board = g.boards[this.boardID]; + post = new Post(g.SITE.Build.postFromObject(post, this.boardID), thread, board, { + isFetchedQuote: true + }); + Main.callbackNodes('Post', [post]); this.insert(post); return; } this.root.textContent = "Loading post No." + this.postID + "..."; if (this.threadID) { - $.cache("//a.4cdn.org/" + this.boardID + "/thread/" + this.threadID + ".json", (function(_this) { - return function(e, isCached) { - return _this.fetchedPost(e.target, isCached); - }; - })(this)); + that = this; + $.cache(g.SITE.urls.threadJSON({ + boardID: this.boardID, + threadID: this.threadID + }), function(arg) { + var isCached; + isCached = arg.isCached; + return that.fetchedPost(this, isCached); + }); } else { this.archivedPost(); } @@ -5117,6 +6550,7 @@ Fetcher = (function() { if (!this.root.parentNode) { return; } + this.quoter || (this.quoter = post); clone = post.addClone(this.quoter.context, $.hasClass(this.root, 'dialog')); Main.callbackNodes('Post', [clone]); nodes = clone.nodes; @@ -5140,26 +6574,26 @@ Fetcher = (function() { } $.rmAll(this.root); $.add(this.root, nodes.root); - return $.event('PostsInserted'); + return $.event('PostsInserted', null, this.root); }; Fetcher.prototype.fetchedPost = function(req, isCached) { - var api, board, k, len, post, posts, status, thread; - if (post = g.posts[this.boardID + "." + this.postID]) { + var api, board, k, len, post, posts, status, that, thread; + if (post = g.posts.get(this.boardID + "." + this.postID)) { this.insert(post); return; } status = req.status; if (status !== 200) { - if (this.archivedPost()) { + if (status && this.archivedPost()) { return; } $.addClass(this.root, 'warning'); - this.root.textContent = status === 404 ? "Thread No." + this.threadID + " 404'd." : "Error " + req.statusText + " (" + req.status + ")."; + this.root.textContent = status === 404 ? "Thread No." + this.threadID + " 404'd." : !status ? 'Connection Error' : "Error " + req.statusText + " (" + req.status + ")."; return; } posts = req.response.posts; - Build.spoilerRange[this.boardID] = posts[0].custom_spoiler; + g.SITE.Build.spoilerRange[this.boardID] = posts[0].custom_spoiler; for (k = 0, len = posts.length; k < len; k++) { post = posts[k]; if (post.no === this.postID) { @@ -5168,15 +6602,17 @@ Fetcher = (function() { } if (post.no !== this.postID) { if (isCached) { - api = "//a.4cdn.org/" + this.boardID + "/thread/" + this.threadID + ".json"; + api = g.SITE.urls.threadJSON({ + boardID: this.boardID, + threadID: this.threadID + }); $.cleanCache(function(url) { return url === api; }); - $.cache(api, (function(_this) { - return function(e) { - return _this.fetchedPost(e.target, false); - }; - })(this)); + that = this; + $.cache(api, function() { + return that.fetchedPost(this, false); + }); return; } if (this.archivedPost()) { @@ -5187,15 +6623,16 @@ Fetcher = (function() { return; } board = g.boards[this.boardID] || new Board(this.boardID); - thread = g.threads[this.boardID + "." + this.threadID] || new Thread(this.threadID, board); - post = new Post(Build.postFromObject(post, this.boardID), thread, board); - post.isFetchedQuote = true; + thread = g.threads.get(this.boardID + "." + this.threadID) || new Thread(this.threadID, board); + post = new Post(g.SITE.Build.postFromObject(post, this.boardID), thread, board, { + isFetchedQuote: true + }); Main.callbackNodes('Post', [post]); return this.insert(post); }; Fetcher.prototype.archivedPost = function() { - var archive, url; + var archive, encryptionOK, that, url; if (!Conf['Resurrect Quotes']) { return false; } @@ -5206,41 +6643,31 @@ Fetcher = (function() { return false; } archive = Redirect.data.post[this.boardID]; - if (/^https:\/\//.test(url) || location.protocol === 'http:') { - $.cache(url, (function(_this) { - return function(e) { - return _this.parseArchivedPost(e.target.response, url, archive); - }; - })(this), { - responseType: 'json', - withCredentials: archive.withCredentials - }); - return true; - } else if (Conf['Exempt Archives from Encryption']) { - CrossOrigin.json(url, (function(_this) { - return function(response) { - var key, media, ref; - media = response.media; - if (media) { - for (key in media) { - if (/_link$/.test(key)) { - if (!((ref = media[key]) != null ? ref.match(/^http:\/\//) : void 0)) { - delete media[key]; - } + encryptionOK = /^https:\/\//.test(url) || location.protocol === 'http:'; + if (encryptionOK || Conf['Exempt Archives from Encryption']) { + that = this; + CrossOrigin.cache(url, function() { + var key, media, ref, ref1; + if (!encryptionOK && ((ref = this.response) != null ? ref.media : void 0)) { + media = this.response.media; + for (key in media) { + if (/_link$/.test(key)) { + if (!((ref1 = $.getOwn(media, key)) != null ? ref1.match(/^http:\/\//) : void 0)) { + delete media[key]; } } } - return _this.parseArchivedPost(response, url, archive); - }; - })(this)); + } + return that.parseArchivedPost(this.response, url, archive); + }); return true; } return false; }; Fetcher.prototype.parseArchivedPost = function(data, url, archive) { - var board, comment, greentext, i, j, key, o, post, ref, ref1, tag, text, text2, thread, val; - if (post = g.posts[this.boardID + "." + this.postID]) { + var board, comment, greentext, i, j, media_link, o, post, ref, tag, text, text2, thread, thumb_link; + if (post = g.posts.get(this.boardID + "." + this.postID)) { this.insert(post); return; } @@ -5276,26 +6703,20 @@ Fetcher = (function() { results1 = []; for (j = l = 0, len1 = ref.length; l < len1; j = ++l) { text2 = ref[j]; - results1.push({ - innerHTML: ((j % 2) ? "" + E(text2) + "" : E(text2)) - }); + results1.push({innerHTML: ((j % 2) ? "" + E(text2) + "" : E(text2))}); } return results1; })(); - text = { - innerHTML: ((greentext) ? "" + E.cat(text) + "" : E.cat(text)) - }; + text = {innerHTML: ((greentext) ? "" + E.cat(text) + "" : E.cat(text))}; results.push(text); } } return results; }).call(this); - comment = { - innerHTML: E.cat(comment) - }; + comment = {innerHTML: E.cat(comment)}; this.threadID = +data.thread_num; o = { - postID: this.postID, + ID: this.postID, threadID: this.threadID, boardID: this.boardID, isReply: this.postID !== this.threadID @@ -5313,11 +6734,18 @@ Fetcher = (function() { return 'Admin'; case 'D': return 'Developer'; + case 'V': + return 'Verified'; + case 'F': + return 'Founder'; + case 'G': + return 'Manager'; } })(), uniqueID: data.poster_hash, flagCode: data.poster_country, - flag: data.poster_country_name, + flagCodeTroll: data.troll_country_code, + flag: data.poster_country_name || data.troll_country_name, dateUTC: data.timestamp, dateText: data.fourchan_date, commentHTML: comment @@ -5325,22 +6753,31 @@ Fetcher = (function() { if (o.info.capcode) { delete o.info.uniqueID; } - if ((ref = data.media) != null ? ref.media_filename : void 0) { - ref1 = data.media; - for (key in ref1) { - val = ref1[key]; - if (/_link$/.test(key) && (val != null ? val[0] : void 0) === '/') { - data.media[key] = url.split('/', 3).join('/') + val; - } + if (data.media && !!+data.media.banned) { + o.fileDeleted = true; + } else if ((ref = data.media) != null ? ref.media_filename : void 0) { + thumb_link = data.media.thumb_link; + if ((thumb_link != null ? thumb_link[0] : void 0) === '/') { + thumb_link = url.split('/', 3).join('/') + thumb_link; + } + if (!Redirect.securityCheck(thumb_link)) { + thumb_link = ''; + } + media_link = Redirect.to('file', { + boardID: this.boardID, + filename: data.media.media_orig + }); + if (!Redirect.securityCheck(media_link)) { + media_link = ''; } o.file = { name: data.media.media_filename, - url: data.media.media_link || data.media.remote_media_link || (location.protocol + "//i.4cdn.org/" + this.boardID + "/" + (encodeURIComponent(data.media[this.boardID === 'f' ? 'media_filename' : 'media_orig']))), + url: media_link || (this.boardID === 'f' ? location.protocol + "//" + (ImageHost.flashHost()) + "/" + this.boardID + "/" + (encodeURIComponent(E(data.media.media_filename))) : location.protocol + "//" + (ImageHost.host()) + "/" + this.boardID + "/" + data.media.media_orig), height: data.media.media_h, width: data.media.media_w, MD5: data.media.media_hash, size: $.bytesToString(data.media.media_size), - thumbURL: data.media.thumb_link || (location.protocol + "//i.4cdn.org/" + this.boardID + "/" + data.media.preview_orig), + thumbURL: thumb_link || (location.protocol + "//" + (ImageHost.thumbHost()) + "/" + this.boardID + "/" + data.media.preview_orig), theight: data.media.preview_h, twidth: data.media.preview_w, isSpoiler: data.media.spoiler === '1' @@ -5352,84 +6789,44 @@ Fetcher = (function() { o.file.tag = JSON.parse(data.media.exif).Tag; } } + o.extra = $.dict(); board = g.boards[this.boardID] || new Board(this.boardID); - thread = g.threads[this.boardID + "." + this.threadID] || new Thread(this.threadID, board); - post = new Post(Build.post(o), thread, board); + thread = g.threads.get(this.boardID + "." + this.threadID) || new Thread(this.threadID, board); + post = new Post(g.SITE.Build.post(o), thread, board, { + isFetchedQuote: true + }); post.kill(); if (post.file) { post.file.thumbURL = o.file.thumbURL; } - post.isFetchedQuote = true; Main.callbackNodes('Post', [post]); return this.insert(post); }; Fetcher.prototype.archiveTags = { - '\n': { - innerHTML: "
" - }, - '[b]': { - innerHTML: "" - }, - '[/b]': { - innerHTML: "" - }, - '[spoiler]': { - innerHTML: "" - }, - '[/spoiler]': { - innerHTML: "" - }, - '[code]': { - innerHTML: "
"
-      },
-      '[/code]': {
-        innerHTML: "
" - }, - '[moot]': { - innerHTML: "
" - }, - '[/moot]': { - innerHTML: "
" - }, - '[banned]': { - innerHTML: "" - }, - '[/banned]': { - innerHTML: "" - }, + '\n': {innerHTML: "
"}, + '[b]': {innerHTML: ""}, + '[/b]': {innerHTML: ""}, + '[spoiler]': {innerHTML: ""}, + '[/spoiler]': {innerHTML: ""}, + '[code]': {innerHTML: "
"},
+      '[/code]': {innerHTML: "
"}, + '[moot]': {innerHTML: "
"}, + '[/moot]': {innerHTML: "
"}, + '[banned]': {innerHTML: ""}, + '[/banned]': {innerHTML: ""}, '[fortune]': function(text) { - return { - innerHTML: "" - }; - }, - '[/fortune]': { - innerHTML: "" - }, - '[i]': { - innerHTML: "" - }, - '[/i]': { - innerHTML: "" + return {innerHTML: ""}; }, - '[red]': { - innerHTML: "" - }, - '[/red]': { - innerHTML: "" - }, - '[green]': { - innerHTML: "" - }, - '[/green]': { - innerHTML: "" - }, - '[blue]': { - innerHTML: "" - }, - '[/blue]': { - innerHTML: "" - } + '[/fortune]': {innerHTML: ""}, + '[i]': {innerHTML: ""}, + '[/i]': {innerHTML: ""}, + '[red]': {innerHTML: ""}, + '[/red]': {innerHTML: ""}, + '[green]': {innerHTML: ""}, + '[/green]': {innerHTML: ""}, + '[blue]': {innerHTML: ""}, + '[/blue]': {innerHTML: ""} }; return Fetcher; @@ -5450,9 +6847,7 @@ Notice = (function() { this.onclose = onclose; this.close = bind(this.close, this); this.add = bind(this.add, this); - this.el = $.el('div', { - innerHTML: "
" - }); + this.el = $.el('div', {innerHTML: "
"}); this.el.style.opacity = 0; this.setType(type); $.on(this.el.firstElementChild, 'click', this.close); @@ -5508,121 +6903,152 @@ Post = (function() { return this.ID; }; - function Post(root, thread, board) { - var clone, j, len, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, ref8; + function Post(root, thread, board, flags) { + var clone, j, k, key, len, len1, ref, ref1, ref10, ref11, ref12, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, selector; this.thread = thread; this.board = board; - this.ID = +root.id.slice(2); + if (flags == null) { + flags = {}; + } + $.extend(this, flags); + this.ID = +root.id.match(/\d*$/)[0]; + this.postID = this.ID; + this.threadID = this.thread.ID; + this.boardID = this.board.ID; + this.siteID = g.SITE.ID; this.fullID = this.board + "." + this.ID; this.context = this; + this.isReply = this.ID !== this.threadID; root.dataset.fullID = this.fullID; this.nodes = this.parseNodes(root); - if (!(this.isReply = $.hasClass(this.nodes.post, 'reply'))) { + if (!this.isReply) { this.thread.OP = this; - this.thread.isArchived = !!$('.archivedIcon', this.nodes.info); - this.thread.isSticky = !!$('.stickyIcon', this.nodes.info); - this.thread.isClosed = this.thread.isArchived || !!$('.closedIcon', this.nodes.info); + ref = ['isSticky', 'isClosed', 'isArchived']; + for (j = 0, len = ref.length; j < len; j++) { + key = ref[j]; + if ((selector = g.SITE.selectors.icons[key])) { + this.thread[key] = !!$(selector, this.nodes.info); + } + } if (this.thread.isArchived) { + this.thread.isClosed = true; this.thread.kill(); } } this.info = { - nameBlock: Conf['Anonymize'] ? 'Anonymous' : this.nodes.nameBlock.textContent.trim(), - subject: ((ref = this.nodes.subject) != null ? ref.textContent : void 0) || void 0, - name: (ref1 = this.nodes.name) != null ? ref1.textContent : void 0, - tripcode: (ref2 = this.nodes.tripcode) != null ? ref2.textContent : void 0, - uniqueID: (ref3 = this.nodes.uniqueID) != null ? ref3.firstElementChild.textContent : void 0, - capcode: (ref4 = this.nodes.capcode) != null ? ref4.textContent.replace('## ', '') : void 0, - flagCode: (ref5 = this.nodes.flag) != null ? (ref6 = ref5.className.match(/flag-(\w+)/)) != null ? ref6[1].toUpperCase() : void 0 : void 0, - flag: (ref7 = this.nodes.flag) != null ? ref7.title : void 0, - date: this.nodes.date ? new Date(this.nodes.date.dataset.utc * 1000) : void 0 + subject: ((ref1 = this.nodes.subject) != null ? ref1.textContent : void 0) || void 0, + name: (ref2 = this.nodes.name) != null ? ref2.textContent : void 0, + email: this.nodes.email ? decodeURIComponent(this.nodes.email.href.replace(/^mailto:/, '')) : void 0, + tripcode: (ref3 = this.nodes.tripcode) != null ? ref3.textContent : void 0, + uniqueID: (ref4 = this.nodes.uniqueID) != null ? ref4.textContent : void 0, + capcode: (ref5 = this.nodes.capcode) != null ? ref5.textContent.replace('## ', '') : void 0, + pass: (ref6 = this.nodes.pass) != null ? ref6.title.match(/\d*$/)[0] : void 0, + flagCode: (ref7 = this.nodes.flag) != null ? (ref8 = ref7.className.match(/flag-(\w+)/)) != null ? ref8[1].toUpperCase() : void 0 : void 0, + flagCodeTroll: (ref9 = this.nodes.flag) != null ? (ref10 = ref9.className.match(/bfl-(\w+)/)) != null ? ref10[1].toUpperCase() : void 0 : void 0, + flag: (ref11 = this.nodes.flag) != null ? ref11.title : void 0, + date: this.nodes.date ? g.SITE.parseDate(this.nodes.date) : void 0 }; + if (Conf['Anonymize']) { + this.info.nameBlock = 'Anonymous'; + } else { + this.info.nameBlock = ((this.info.name || '') + " " + (this.info.tripcode || '')).trim(); + } + if (this.info.capcode) { + this.info.nameBlock += " ## " + this.info.capcode; + } + if (this.info.uniqueID) { + this.info.nameBlock += " (ID: " + this.info.uniqueID + ")"; + } this.parseComment(); this.parseQuotes(); - this.parseFile(); + this.parseFiles(); this.isDead = false; this.isHidden = false; this.clones = []; - if (g.posts[this.fullID]) { + if (g.posts.get(this.fullID)) { this.isRebuilt = true; - this.clones = g.posts[this.fullID].clones; - ref8 = this.clones; - for (j = 0, len = ref8.length; j < len; j++) { - clone = ref8[j]; + this.clones = g.posts.get(this.fullID).clones; + ref12 = this.clones; + for (k = 0, len1 = ref12.length; k < len1; k++) { + clone = ref12[k]; clone.origin = this; } } + if (!this.isFetchedQuote && this.ID > this.thread.lastPost) { + this.thread.lastPost = this.ID; + } this.board.posts.push(this.ID, this); this.thread.posts.push(this.ID, this); g.posts.push(this.fullID, this); } Post.prototype.parseNodes = function(root) { - var info, nodes, post; - post = $('.post', root); - info = $('.postInfo', post); + var base, info, key, nodes, post, ref, s, selector; + s = g.SITE.selectors; + post = $(s.post, root) || root; + info = $(s.infoRoot, post); nodes = { root: root, + bottom: this.isReply || !g.SITE.isOPContainerThread ? root : $(s.opBottom, root), post: post, info: info, - subject: $('.subject', info), - name: $('.name', info), - email: $('.useremail', info), - tripcode: $('.postertrip', info), - uniqueID: $('.posteruid', info), - capcode: $('.capcode.hand', info), - flag: $('.flag, .countryFlag', info), - date: $('.dateTime', info), - nameBlock: $('.nameBlock', info), - quote: $('.postNum > a:nth-of-type(2)', info), - reply: $('.replylink', info), - comment: $('.postMessage', post), - links: [], + comment: $(s.comment, post), quotelinks: [], - archivelinks: [] + archivelinks: [], + embedlinks: [] }; + ref = s.info; + for (key in ref) { + selector = ref[key]; + nodes[key] = $(selector, info); + } + if (typeof (base = g.SITE).parseNodes === "function") { + base.parseNodes(this, nodes); + } + nodes.uniqueIDRoot || (nodes.uniqueIDRoot = nodes.uniqueID); if ($.engine === 'edge') { Object.defineProperty(nodes, 'backlinks', { configurable: true, enumerable: true, get: function() { - return info.getElementsByClassName('backlink'); + return post.getElementsByClassName('backlink'); } }); } else { - nodes.backlinks = info.getElementsByClassName('backlink'); + nodes.backlinks = post.getElementsByClassName('backlink'); } return nodes; }; Post.prototype.parseComment = function() { - var abbr, bq, commentDisplay, j, k, len, len1, node, ref, spoilers; + var base, bq; this.nodes.comment.normalize(); - bq = this.nodes.comment.cloneNode(true); - ref = $$('.abbr + br, .exif, b, .fortune', bq); - for (j = 0, len = ref.length; j < len; j++) { - node = ref[j]; - $.rm(node); + this.nodes.commentClean = bq = this.nodes.comment.cloneNode(true); + if (typeof (base = g.SITE).cleanComment === "function") { + base.cleanComment(bq); } - if (abbr = $('.abbr', bq)) { - $.rm(abbr); + return this.info.comment = this.nodesToText(bq); + }; + + Post.prototype.commentDisplay = function() { + var base, bq; + bq = this.nodes.commentClean.cloneNode(true); + if (!(Conf['Remove Spoilers'] || Conf['Reveal Spoilers'])) { + this.cleanSpoilers(bq); } - this.info.comment = this.nodesToText(bq); - if (abbr) { - this.info.comment = this.info.comment.replace(/\n\n$/, ''); + if (typeof (base = g.SITE).cleanCommentDisplay === "function") { + base.cleanCommentDisplay(bq); } - commentDisplay = this.info.comment; - if (!(Conf['Remove Spoilers'] || Conf['Reveal Spoilers'])) { - spoilers = $$('s', bq); - if (spoilers.length) { - for (k = 0, len1 = spoilers.length; k < len1; k++) { - node = spoilers[k]; - $.replace(node, $.tn('[spoiler]')); - } - commentDisplay = this.nodesToText(bq); - } + return this.nodesToText(bq).trim().replace(/\s+$/gm, ''); + }; + + Post.prototype.commentOrig = function() { + var base, bq; + bq = this.nodes.commentClean.cloneNode(true); + if (typeof (base = g.SITE).insertTags === "function") { + base.insertTags(bq); } - return this.info.commentDisplay = commentDisplay.trim().replace(/\s+$/gm, ''); + return this.nodesToText(bq); }; Post.prototype.nodesToText = function(bq) { @@ -5636,10 +7062,19 @@ Post = (function() { return text; }; + Post.prototype.cleanSpoilers = function(bq) { + var j, len, node, spoilers; + spoilers = $$(g.SITE.selectors.spoiler, bq); + for (j = 0, len = spoilers.length; j < len; j++) { + node = spoilers[j]; + $.replace(node, $.tn('[spoiler]')); + } + }; + Post.prototype.parseQuotes = function() { var j, len, quotelink, ref; this.quotes = []; - ref = $$(':not(pre) > .quotelink', this.nodes.comment); + ref = $$(g.SITE.selectors.quotelink, this.nodes.comment); for (j = 0, len = ref.length; j < len; j++) { quotelink = ref[j]; this.parseQuote(quotelink); @@ -5648,7 +7083,7 @@ Post = (function() { Post.prototype.parseQuote = function(quotelink) { var fullID, match; - match = quotelink.href.match(/^https?:\/\/boards\.4chan\.org\/+([^\/]+)\/+(?:res|thread)\/+\d+(?:[\/?][^#]*)?#p(\d+)$/); + match = quotelink.href.match(g.SITE.regexp.quotelink); if (!(match || (this.isClone && quotelink.dataset.postID))) { return; } @@ -5656,58 +7091,85 @@ Post = (function() { if (this.isClone) { return; } - fullID = match[1] + "." + match[2]; + fullID = match[1] + "." + match[3]; if (indexOf.call(this.quotes, fullID) < 0) { return this.quotes.push(fullID); } }; - Post.prototype.parseFile = function() { - var fileEl, fileText, info, link, m, ref, ref1, ref2, size, thumb, unit; - if (!(fileEl = $('.file', this.nodes.post))) { - return; + Post.prototype.parseFiles = function() { + var docIndex, file, fileRoot, fileRoots, index, j, len; + this.files = []; + fileRoots = this.fileRoots(); + index = 0; + for (docIndex = j = 0, len = fileRoots.length; j < len; docIndex = ++j) { + fileRoot = fileRoots[docIndex]; + if ((file = this.parseFile(fileRoot))) { + file.index = index++; + file.docIndex = docIndex; + this.files.push(file); + } + } + if (this.files.length) { + return this.file = this.files[0]; + } + }; + + Post.prototype.fileRoots = function() { + var roots; + if (g.SITE.selectors.multifile) { + roots = $$(g.SITE.selectors.multifile, this.nodes.root); + if (roots.length) { + return roots; + } + } + return [this.nodes.root]; + }; + + Post.prototype.parseFile = function(fileRoot) { + var file, key, ref, ref1, selector, size, unit; + file = {}; + ref = g.SITE.selectors.file; + for (key in ref) { + selector = ref[key]; + file[key] = $(selector, fileRoot); } - if (!(link = $('.fileText > a, .fileText-original > a', fileEl))) { + file.thumbLink = (ref1 = file.thumb) != null ? ref1.parentNode : void 0; + if (!(file.text && file.link)) { return; } - if (!(info = (ref = link.nextSibling) != null ? ref.textContent.match(/\(([\d.]+ [KMG]?B).*\)/) : void 0)) { + if (!g.SITE.parseFile(this, file)) { return; } - fileText = fileEl.firstElementChild; - this.file = { - text: fileText, - link: link, - url: link.href, - name: fileText.title || link.title || link.textContent, - size: info[1], - isImage: /(jpg|png|gif)$/i.test(link.href), - isVideo: /webm$/i.test(link.href), - dimensions: (ref1 = info[0].match(/\d+x\d+/)) != null ? ref1[0] : void 0, - tag: (ref2 = info[0].match(/,[^,]*, ([a-z]+)\)/i)) != null ? ref2[1] : void 0 - }; - size = +this.file.size.match(/[\d.]+/)[0]; - unit = ['B', 'KB', 'MB', 'GB'].indexOf(this.file.size.match(/\w+$/)[0]); + $.extend(file, { + url: file.link.href, + isImage: $.isImage(file.link.href), + isVideo: $.isVideo(file.link.href) + }); + size = +file.size.match(/[\d.]+/)[0]; + unit = ['B', 'KB', 'MB', 'GB'].indexOf(file.size.match(/\w+$/)[0]); while (unit-- > 0) { size *= 1024; } - this.file.sizeInBytes = size; - if ((thumb = $('.fileThumb > [data-md5]', fileEl))) { - return $.extend(this.file, { - thumb: thumb, - thumbURL: (m = link.href.match(/\d+(?=\.\w+$)/)) ? location.protocol + "//i.4cdn.org/" + this.board + "/" + m[0] + "s.jpg" : void 0, - MD5: thumb.dataset.md5, - isSpoiler: $.hasClass(thumb.parentNode, 'imgspoiler') - }); - } + file.sizeInBytes = size; + return file; }; - Post.prototype.kill = function(file) { + Post.deadMark = $.el('span', { + textContent: '\u00A0(Dead)', + className: 'qmark-dead' + }); + + Post.prototype.kill = function(file, index) { var clone, j, k, len, len1, quotelink, ref, ref1, strong; + if (index == null) { + index = 0; + } if (file) { - if (this.isDead || this.file.isDead) { + if (this.isDead || this.files[index].isDead) { return; } - this.file.isDead = true; + this.files[index].isDead = true; $.addClass(this.nodes.root, 'deleted-file'); } else { if (this.isDead) { @@ -5730,7 +7192,7 @@ Post = (function() { ref = this.clones; for (j = 0, len = ref.length; j < len; j++) { clone = ref[j]; - clone.kill(file); + clone.kill(file, index); } if (file) { return; @@ -5741,7 +7203,7 @@ Post = (function() { if (!(!$.hasClass(quotelink, 'deadlink'))) { continue; } - quotelink.textContent = quotelink.textContent + '\u00A0(Dead)'; + $.add(quotelink, Post.deadMark.cloneNode(true)); $.addClass(quotelink, 'deadlink'); } }; @@ -5751,7 +7213,10 @@ Post = (function() { this.isDead = false; $.rmClass(this.nodes.root, 'deleted-post'); strong = $('strong.warning', this.nodes.info); - if (this.file && this.file.isDead) { + if (this.files.some(function(file) { + return file.isDead; + })) { + $.addClass(this.nodes.root, 'deleted-file'); strong.textContent = '[File deleted]'; } else { $.rm(strong); @@ -5770,7 +7235,7 @@ Post = (function() { if (!($.hasClass(quotelink, 'deadlink'))) { continue; } - quotelink.textContent = quotelink.textContent.replace('\u00A0(Dead)', ''); + $.rm($('.qmark-dead', quotelink)); $.rmClass(quotelink, 'deadlink'); } }; @@ -5782,6 +7247,7 @@ Post = (function() { }; Post.prototype.addClone = function(context, contractThumb) { + Callbacks.Post.execute(this); return new Post.Clone(this, context, contractThumb); }; @@ -5795,6 +7261,14 @@ Post = (function() { } }; + Post.prototype.setCatalogOP = function(isCatalogOP) { + this.nodes.root.classList.toggle('catalog-container', isCatalogOP); + this.nodes.root.classList.toggle('opContainer', !isCatalogOP); + this.nodes.post.classList.toggle('catalog-post', isCatalogOP); + this.nodes.post.classList.toggle('op', !isCatalogOP); + return this.nodes.post.style.left = this.nodes.post.style.right = null; + }; + return Post; })(); @@ -5813,60 +7287,83 @@ Post = (function() { _Class.prototype.isClone = true; - function _Class(origin, context, contractThumb) { - var base, file, i, inline, inlined, j, k, key, l, len, len1, len2, len3, node, nodes, ref, ref1, ref2, ref3, ref4, ref5, root, val; + function _Class() { + var that; + that = Object.create(Post.Clone.prototype); + that.construct.apply(that, arguments); + return that; + } + + _Class.prototype.construct = function(origin, context, contractThumb) { + var base, file, fileRoot, fileRoots, i, inline, inlined, j, k, key, l, len, len1, len2, len3, len4, m, node, nodes, originFile, ref, ref1, ref2, ref3, ref4, ref5, ref6, root, selector, val; this.origin = origin; this.context = context; - ref = ['ID', 'fullID', 'board', 'thread', 'info', 'quotes', 'isReply']; + ref = ['ID', 'postID', 'threadID', 'boardID', 'siteID', 'fullID', 'board', 'thread', 'info', 'quotes', 'isReply']; for (i = 0, len = ref.length; i < len; i++) { key = ref[i]; this[key] = this.origin[key]; } nodes = this.origin.nodes; root = contractThumb ? this.cloneWithoutVideo(nodes.root) : nodes.root.cloneNode(true); - (base = Post.Clone).prefix || (base.prefix = 0); + (base = Post.Clone).suffix || (base.suffix = 0); ref1 = [root].concat(slice.call($$('[id]', root))); for (j = 0, len1 = ref1.length; j < len1; j++) { node = ref1[j]; - node.id = Post.Clone.prefix + node.id; + node.id += "_" + Post.Clone.suffix; } - Post.Clone.prefix++; - this.nodes = this.parseNodes(root); - ref2 = $$('.inline', this.nodes.post); + Post.Clone.suffix++; + ref2 = $$('.inline', root); for (k = 0, len2 = ref2.length; k < len2; k++) { inline = ref2[k]; $.rm(inline); } - ref3 = $$('.inlined', this.nodes.post); + ref3 = $$('.inlined', root); for (l = 0, len3 = ref3.length; l < len3; l++) { inlined = ref3[l]; $.rmClass(inlined, 'inlined'); } + this.nodes = this.parseNodes(root); root.hidden = false; $.rmClass(root, 'forwarded'); $.rmClass(this.nodes.post, 'highlight'); + if (!this.isReply) { + this.setCatalogOP(false); + $.rm($('.catalog-link', this.nodes.post)); + $.rm($('.catalog-stats', this.nodes.post)); + $.rm($('.catalog-replies', this.nodes.post)); + } this.parseQuotes(); this.quotes = slice.call(this.origin.quotes); - if (this.origin.file) { - this.file = {}; - ref4 = this.origin.file; - for (key in ref4) { - val = ref4[key]; - this.file[key] = val; - } - file = $('.file', this.nodes.post); - this.file.text = file.firstElementChild; - this.file.link = $('.fileText > a, .fileText-original', file); - this.file.thumb = $('.fileThumb > [data-md5]', file); - this.file.fullImage = $('.full-image', file); - this.file.videoControls = $('.video-controls', this.file.text); - if (this.file.videoThumb) { - this.file.thumb.muted = true; - } - if ((ref5 = this.file.thumb) != null ? ref5.dataset.src : void 0) { - this.file.thumb.src = this.file.thumb.dataset.src; - this.file.thumb.removeAttribute('data-src'); - } + this.files = []; + if (this.origin.files.length) { + fileRoots = this.fileRoots(); + } + ref4 = this.origin.files; + for (m = 0, len4 = ref4.length; m < len4; m++) { + originFile = ref4[m]; + file = {}; + for (key in originFile) { + val = originFile[key]; + file[key] = val; + } + fileRoot = fileRoots[file.docIndex]; + ref5 = g.SITE.selectors.file; + for (key in ref5) { + selector = ref5[key]; + file[key] = $(selector, fileRoot); + } + file.thumbLink = (ref6 = file.thumb) != null ? ref6.parentNode : void 0; + if (file.thumbLink) { + file.fullImage = $('.full-image', file.thumbLink); + } + file.videoControls = $('.video-controls', file.text); + if (file.videoThumb) { + file.thumb.muted = true; + } + this.files.push(file); + } + if (this.files.length) { + this.file = this.files[0]; if (this.file.thumb && contractThumb) { ImageExpand.contract(this); } @@ -5874,8 +7371,8 @@ Post = (function() { if (this.origin.isDead) { this.isDead = true; } - root.dataset.clone = this.origin.clones.push(this) - 1; - } + return root.dataset.clone = this.origin.clones.push(this) - 1; + }; _Class.prototype.cloneWithoutVideo = function(node) { var child, clone, i, len, ref; @@ -6034,6 +7531,47 @@ RandomAccessList = (function() { }).call(this); +ShimSet = (function() { + var ShimSet; + + ShimSet = (function() { + function ShimSet() { + this.elements = $.dict(); + this.size = 0; + } + + ShimSet.prototype.has = function(value) { + return value in this.elements; + }; + + ShimSet.prototype.add = function(value) { + if (this.elements[value]) { + return; + } + this.elements[value] = true; + return this.size++; + }; + + ShimSet.prototype["delete"] = function(value) { + if (!this.elements[value]) { + return; + } + delete this.elements[value]; + return this.size--; + }; + + return ShimSet; + + })(); + + if (!('Set' in window)) { + window.Set = ShimSet; + } + + return ShimSet; + +}).call(this); + SimpleDict = (function() { var SimpleDict, slice = [].slice; @@ -6069,6 +7607,14 @@ SimpleDict = (function() { } }; + SimpleDict.prototype.get = function(key) { + if (key === 'keys') { + return void 0; + } else { + return $.getOwn(this, key); + } + }; + return SimpleDict; })(); @@ -6086,21 +7632,28 @@ Thread = (function() { }; function Thread(ID, board) { - this.ID = ID; this.board = board; + this.ID = +ID; + this.threadID = this.ID; + this.boardID = this.board.ID; + this.siteID = g.SITE.ID; this.fullID = this.board + "." + this.ID; this.posts = new SimpleDict(); this.isDead = false; this.isHidden = false; - this.isOnTop = false; this.isSticky = false; this.isClosed = false; this.isArchived = false; this.postLimit = false; this.fileLimit = false; + this.lastPost = 0; this.ipCount = void 0; + this.json = null; this.OP = null; this.catalogView = null; + this.nodes = { + root: null + }; this.board.threads.push(this.ID, this); g.threads.push(this.fullID, this); } @@ -6162,11 +7715,14 @@ Thread = (function() { return; } icon = $.el('img', { - src: "" + Build.staticPath + typeLC + Build.gifIcon, + src: "" + g.SITE.Build.staticPath + typeLC + g.SITE.Build.gifIcon, alt: type, title: type, className: typeLC + "Icon retina" }); + if (g.BOARD.ID === 'f') { + icon.style.cssText = 'height: 18px; width: 18px;'; + } root = type !== 'Sticky' && this.isSticky ? $('.stickyIcon', this.OP.nodes.info) : $('.page-num', this.OP.nodes.info) || this.OP.nodes.quote; $.after(root, [$.tn(' '), icon]); if (!this.catalogView) { @@ -6180,11 +7736,19 @@ Thread = (function() { }; Thread.prototype.collect = function() { + var n; + n = 0; this.posts.forEach(function(post) { - return post.collect(); + if (post.clones.length) { + return n++; + } else { + return post.collect(); + } }); - g.threads.rm(this.fullID); - return this.board.threads.rm(this); + if (!n) { + g.threads.rm(this.fullID); + return this.board.threads.rm(this); + } }; return Thread; @@ -6195,158 +7759,1317 @@ Thread = (function() { }).call(this); -Redirect = (function() { - var Redirect, - indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; +SW = {}; - Redirect = { - archives: [ - { "uid": 3, "name": "4plebs", "domain": "archive.4plebs.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "adv", "f", "hr", "o", "pol", "s4s", "sp", "tg", "trv", "tv", "x" ], "files": [ "adv", "f", "hr", "o", "pol", "s4s", "sp", "tg", "trv", "tv", "x" ] }, - { "uid": 4, "name": "Nyafuu Archive", "domain": "archive.nyafuu.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "c", "e", "news", "w", "wg", "wsr" ], "files": [ "c", "e", "news", "w", "wg", "wsr" ] }, - { "uid": 8, "name": "Rebecca Black Tech", "domain": "archive.rebeccablacktech.com", "http": false, "https": true, "software": "fuuka", "boards": [ "cgl", "g", "mu" ], "files": [ "cgl", "g", "mu" ] }, - { "uid": 10, "name": "warosu", "domain": "warosu.org", "http": false, "https": true, "software": "fuuka", "boards": [ "3", "biz", "cgl", "ck", "diy", "fa", "g", "ic", "jp", "lit", "sci", "tg", "vr" ], "files": [ "3", "biz", "cgl", "ck", "diy", "fa", "g", "ic", "jp", "lit", "sci", "tg", "vr" ] }, - { "uid": 23, "name": "Desustorage", "domain": "desustorage.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "a", "aco", "an", "c", "co", "d", "fit", "gif", "his", "int", "k", "m", "mlp", "qa", "r9k", "tg", "trash", "vr", "wsg" ], "files": [ "a", "aco", "an", "c", "co", "d", "fit", "gif", "his", "int", "k", "m", "mlp", "qa", "r9k", "tg", "trash", "vr", "wsg" ] }, - { "uid": 24, "name": "fireden.net", "domain": "boards.fireden.net", "http": false, "https": true, "software": "foolfuuka", "boards": [ "a", "cm", "ic", "sci", "tg", "v", "vg", "y" ], "files": [ "a", "cm", "ic", "sci", "tg", "v", "vg", "y" ] }, - { "uid": 25, "name": "arch.b4k.co", "domain": "arch.b4k.co", "http": true, "https": true, "software": "foolfuuka", "boards": [ "g", "jp", "mlp", "v" ], "files": [] }, - { "uid": 5, "name": "Love is Over", "domain": "archive.loveisover.me", "http": true, "https": false, "software": "foolfuuka", "boards": [ "c", "d", "e", "i", "lgbt", "t", "u" ], "files": [ "c", "d", "e", "i", "lgbt", "t", "u" ] }, - { "uid": 28, "name": "bstats", "domain": "archive.b-stats.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "f", "cm", "hm", "lgbt", "news", "qst", "trash", "y" ], "files": [] }, - { "uid": 29, "name": "Archived.Moe", "domain": "archived.moe", "http": true, "https": false, "software": "foolfuuka", "boards": [ "3", "a", "aco", "adv", "an", "asp", "b", "biz", "c", "cgl", "ck", "cm", "co", "d", "diy", "e", "f", "fa", "fit", "g", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "int", "jp", "k", "lgbt", "lit", "m", "mlp", "mu", "n", "news", "o", "out", "p", "po", "pol", "qa", "qst", "r", "r9k", "s", "s4s", "sci", "soc", "sp", "t", "tg", "toy", "trash", "trv", "tv", "u", "v", "vg", "vp", "vr", "w", "wg", "wsg", "wsr", "x", "y" ], "files": [ "gd", "po", "qst" ] }, - { "uid": 30, "name": "TheBArchive.com", "domain": "thebarchive.com", "http": true, "https": false, "software": "foolfuuka", "boards": [ "b" ], "files": [ "b" ] } - ], - init: function() { - this.selectArchives(); - if (Conf['archiveAutoUpdate'] && Conf['lastarchivecheck'] < Date.now() - 2 * $.DAY) { - return this.update(); +(function() { + var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + SW.tinyboard = { + isOPContainerThread: true, + mayLackJSON: true, + threadModTimeIgnoresSage: true, + disabledFeatures: ['Resurrect Quotes', 'Quick Reply Personas', 'Quick Reply', 'Cooldown', 'Report Link', 'Delete Link', 'Edit Link', 'Quote Inlining', 'Quote Previewing', 'Quote Backlinks', 'File Info Formatting', 'Image Expansion', 'Image Expansion (Menu)', 'Comment Expansion', 'Thread Expansion', 'Favicon', 'Quote Threading', 'Thread Updater', 'Banner', 'Flash Features', 'Reply Pruning'], + detect: function() { + var j, len, m, properties, ref, root, script; + ref = $$('script:not([src])', d.head); + for (j = 0, len = ref.length; j < len; j++) { + script = ref[j]; + if ((m = script.textContent.match(/\bvar configRoot=(".*?")/))) { + properties = $.dict(); + try { + root = JSON.parse(m[1]); + if (root[0] === '/') { + properties.root = location.origin + root; + } else if (/^https?:/.test(root)) { + properties.root = root; + } + } catch (error) {} + return properties; + } } + return false; }, - selectArchives: function() { - var archive, archives, boardID, boards, data, files, id, j, k, key, l, len, len1, len2, name, o, record, ref, ref1, ref2, software, type, uid; - o = { - thread: {}, - post: {}, - file: {} - }; - archives = {}; - ref = Conf['archives']; - for (j = 0, len = ref.length; j < len; j++) { - data = ref[j]; - ref1 = ['boards', 'files']; - for (k = 0, len1 = ref1.length; k < len1; k++) { - key = ref1[k]; - if (!(data[key] instanceof Array)) { - data[key] = []; - } + awaitBoard: function(cb) { + var reactUI, s; + if ((reactUI = $.id('react-ui'))) { + s = this.selectors = Object.create(this.selectors); + s.boardFor = { + index: '.page-container' + }; + s.thread = 'div[id^="thread_"]'; + return Main.mounted(cb); + } else { + return cb(); + } + }, + urls: { + thread: function(arg, isArchived) { + var boardID, ref, siteID, threadID; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID; + return "" + (((ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0) || ("http://" + siteID + "/")) + boardID + "/" + (isArchived ? 'archive/' : '') + "res/" + threadID + ".html"; + }, + post: function(arg) { + var postID; + postID = arg.postID; + return "#" + postID; + }, + index: function(arg) { + var boardID, ref, siteID; + siteID = arg.siteID, boardID = arg.boardID; + return "" + (((ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0) || ("http://" + siteID + "/")) + boardID + "/"; + }, + catalog: function(arg) { + var boardID, ref, siteID; + siteID = arg.siteID, boardID = arg.boardID; + return "" + (((ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0) || ("http://" + siteID + "/")) + boardID + "/catalog.html"; + }, + threadJSON: function(arg, isArchived) { + var boardID, ref, root, siteID, threadID; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID; + root = (ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0; + if (root) { + return "" + root + boardID + "/" + (isArchived ? 'archive/' : '') + "res/" + threadID + ".json"; + } else { + return ''; } - uid = data.uid, name = data.name, boards = data.boards, files = data.files, software = data.software; - if (software !== 'fuuka' && software !== 'foolfuuka') { - continue; + }, + archivedThreadJSON: function(thread) { + return SW.tinyboard.urls.threadJSON(thread, true); + }, + threadsListJSON: function(arg) { + var boardID, ref, root, siteID; + siteID = arg.siteID, boardID = arg.boardID; + root = (ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0; + if (root) { + return "" + root + boardID + "/threads.json"; + } else { + return ''; } - archives[JSON.stringify(uid != null ? uid : name)] = data; - for (l = 0, len2 = boards.length; l < len2; l++) { - boardID = boards[l]; - if (!(boardID in o.thread)) { - o.thread[boardID] = data; - } - if (!(boardID in o.post || software !== 'foolfuuka')) { - o.post[boardID] = data; - } - if (!(boardID in o.file || indexOf.call(files, boardID) < 0)) { - o.file[boardID] = data; - } + }, + archiveListJSON: function(arg) { + var boardID, ref, root, siteID; + siteID = arg.siteID, boardID = arg.boardID; + root = (ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0; + if (root) { + return "" + root + boardID + "/archive/archive.json"; + } else { + return ''; } - } - ref2 = Conf['selectedArchives']; - for (boardID in ref2) { - record = ref2[boardID]; - for (type in record) { - id = record[type]; - if (!((archive = archives[JSON.stringify(id)]))) { - continue; + }, + catalogJSON: function(arg) { + var boardID, ref, root, siteID; + siteID = arg.siteID, boardID = arg.boardID; + root = (ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0; + if (root) { + return "" + root + boardID + "/catalog.json"; + } else { + return ''; + } + }, + file: function(arg, filename) { + var boardID, ref, siteID; + siteID = arg.siteID, boardID = arg.boardID; + return "" + (((ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0) || ("http://" + siteID + "/")) + boardID + "/" + filename; + }, + thumb: function(board, filename) { + return SW.tinyboard.urls.file(board, filename); + } + }, + selectors: { + board: 'form[name="postcontrols"]', + thread: 'input[name="board"] ~ div[id^="thread_"]', + threadDivider: 'div[id^="thread_"] > hr:last-child', + summary: '.omitted', + postContainer: 'div[id^="reply_"]:not(.hidden)', + opBottom: '.op', + replyOriginal: 'div[id^="reply_"]:not(.hidden)', + infoRoot: '.intro', + info: { + subject: '.subject', + name: '.name', + email: '.email', + tripcode: '.trip', + uniqueID: '.poster_id', + capcode: '.capcode', + flag: '.flag', + date: 'time', + nameBlock: 'label', + quote: 'a[href*="#q"]', + reply: 'a[href*="/res/"]:not([href*="#"])' + }, + icons: { + isSticky: '.fa-thumb-tack', + isClosed: '.fa-lock' + }, + file: { + text: '.fileinfo', + link: '.fileinfo > a', + thumb: 'a > .post-image' + }, + thumbLink: '.file > a', + multifile: '.files > .file', + highlightable: { + op: ' > .op', + reply: '.reply', + catalog: ' > .thread' + }, + comment: '.body', + spoiler: '.spoiler', + quotelink: 'a[onclick*="highlightReply("]', + catalog: { + board: '#Grid', + thread: '.mix', + thumb: '.thread-image' + }, + boardList: '.boardlist', + boardListBottom: '.boardlist.bottom', + styleSheet: '#stylesheet', + psa: '.blotter', + nav: { + prev: '.pages > form > [value=Previous]', + next: '.pages > form > [value=Next]' + } + }, + classes: { + highlight: 'highlighted' + }, + xpath: { + thread: 'div[starts-with(@id,"thread_")]', + postContainer: 'div[starts-with(@id,"reply_") or starts-with(@id,"thread_")]', + replyContainer: 'div[starts-with(@id,"reply_")]' + }, + regexp: { + quotelink: /\/([^\/]+)\/res\/(\d+)(?:\.\w+)?#(\d+)$/, + quotelinkHTML: /]*\bhref="[^"]*\/([^\/]+)\/res\/(\d+)(?:\.\w+)?#(\d+)"/g + }, + Build: { + parseJSON: function(data, board) { + var extra_file, file, i, j, len, o, ref; + o = SW.yotsuba.Build.parseJSON(data, board); + if (data.ext === 'deleted') { + delete o.file; + $.extend(o, { + files: [], + fileDeleted: true, + filesDeleted: [0] + }); + } + if (data.extra_files) { + ref = data.extra_files; + for (i = j = 0, len = ref.length; j < len; i = ++j) { + extra_file = ref[i]; + if (extra_file.ext === 'deleted') { + o.filesDeleted.push(i); + } else { + file = SW.yotsuba.Build.parseJSONFile(data, board); + o.files.push(file); + } } - boards = type === 'file' ? archive.files : archive.boards; - if (indexOf.call(boards, boardID) >= 0) { - o[type][boardID] = archive; + if (o.files.length) { + o.file = o.files[0]; } } + return o; + }, + parseComment: function(html) { + html = html.replace(//gi, '\n').replace(/<[^>]*>/g, ''); + return $.unescape(html); } - return Redirect.data = o; }, - update: function(cb) { - var i, j, k, len, len1, load, nloaded, ref, ref1, responses, url, urls; - urls = []; - responses = []; - nloaded = 0; - ref = Conf['archiveLists'].split('\n'); - for (j = 0, len = ref.length; j < len; j++) { - url = ref[j]; - if (!(url[0] !== '#')) { - continue; - } - url = url.trim(); - if (url) { - urls.push(url); - } + bgColoredEl: function() { + return $.el('div', { + className: 'post reply' + }); + }, + isFileURL: function(url) { + return /\/src\/[^\/]+/.test(url.pathname); + }, + preParsingFixes: function(board) { + var broken; + if ((broken = $('a > input[name="board"]', board))) { + return $.before(broken.parentNode, broken); } - load = function(i) { - return function() { - var err, fail, response; - fail = function(action, msg) { - return new Notice('warning', "Error " + action + " archive data from\n" + urls[i] + "\n" + msg, 20); - }; - if (this.status !== 200) { - return fail('fetching', (this.status ? "Error " + this.statusText + " (" + this.status + ")" : 'Connection Error')); - } - try { - response = JSON.parse(this.response); - } catch (_error) { - err = _error; - return fail('parsing', err.message); - } - if (!(response instanceof Array)) { - response = [response]; - } - responses[i] = response; - nloaded++; - if (nloaded === urls.length) { - return Redirect.parse(responses, cb); - } - }; - }; - if (urls.length) { - for (i = k = 0, len1 = urls.length; k < len1; i = ++k) { - url = urls[i]; - if ((ref1 = url[0]) === '[' || ref1 === '{') { - load(i).call({ - status: 200, - response: url - }); - } else { - $.ajax(url, { - responseType: 'text', - onloadend: load(i) - }); - } - } - } else { - Redirect.parse([], cb); + }, + parseNodes: function(post, nodes) { + var m, nextSibling, node, text, uniqueID; + if (nodes.uniqueID) { + return; + } + text = ''; + node = nodes.nameBlock.nextSibling; + while (node && node.nodeType === 3) { + text += node.textContent; + node = node.nextSibling; + } + if ((m = text.match(/(\s*ID:\s*)(\S+)/))) { + nodes.info.normalize(); + nextSibling = nodes.nameBlock.nextSibling; + nextSibling = nextSibling.splitText(m[1].length); + nextSibling.splitText(m[2].length); + nodes.uniqueID = uniqueID = $.el('span', { + className: 'poster_id' + }); + $.replace(nextSibling, uniqueID); + return $.add(uniqueID, nextSibling); } }, - parse: function(responses, cb) { - var archiveUIDs, archives, data, items, j, k, len, len1, ref, response, uid; - archives = []; - archiveUIDs = {}; - for (j = 0, len = responses.length; j < len; j++) { - response = responses[j]; - for (k = 0, len1 = response.length; k < len1; k++) { - data = response[k]; - uid = JSON.stringify((ref = data.uid) != null ? ref : data.name); - if (uid in archiveUIDs) { - $.extend(archiveUIDs[uid], data); - } else { - archiveUIDs[uid] = data; - archives.push(data); - } - } + parseDate: function(node) { + var date, ref; + date = Date.parse((ref = node.getAttribute('datetime')) != null ? ref.trim() : void 0); + if (!isNaN(date)) { + return new Date(date); + } + date = Date.parse(node.textContent.trim() + ' UTC'); + if (!isNaN(date)) { + return new Date(date); + } + return void 0; + }, + parseFile: function(post, file) { + var info, infoNode, link, nameNode, ref, ref1, text, thumb; + text = file.text, link = file.link, thumb = file.thumb; + if ($.x("ancestor::" + this.xpath.postContainer + "[1]", text) !== post.nodes.root) { + return false; + } + if (!(infoNode = indexOf.call((ref = link.nextSibling) != null ? ref.textContent : void 0, '(') >= 0 ? link.nextSibling : link.nextElementSibling)) { + return false; + } + if (!(info = infoNode.textContent.match(/\((.*,\s*)?([\d.]+ ?[KMG]?B).*\)/))) { + return false; + } + nameNode = $('.postfilename', text); + $.extend(file, { + name: nameNode ? nameNode.title || nameNode.textContent : link.pathname.match(/[^\/]*$/)[0], + size: info[2], + dimensions: (ref1 = info[0].match(/\d+x\d+/)) != null ? ref1[0] : void 0 + }); + if (thumb) { + $.extend(file, { + thumbURL: /\/static\//.test(thumb.src) && $.isImage(link.href) ? link.href : thumb.src, + isSpoiler: /^Spoiler/i.test(info[1] || '') || link.textContent === 'Spoiler Image' + }); + } + return true; + }, + isThumbExpanded: function(file) { + return $.hasClass(file.thumb.parentNode, 'expanded') || file.thumb.parentNode.dataset.expanded === 'true'; + }, + isLinkified: function(link) { + return /\bnofollow\b/.test(link.rel); + }, + catalogPin: function(threadRoot) { + return threadRoot.dataset.sticky = 'true'; + } + }; + +}).call(this); + +(function() { + var slice = [].slice; + + SW.yotsuba = { + isOPContainerThread: false, + hasIPCount: true, + archivedBoardsKnown: true, + urls: { + thread: function(arg) { + var boardID, threadID; + boardID = arg.boardID, threadID = arg.threadID; + return location.protocol + "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/thread/" + threadID; + }, + post: function(arg) { + var postID; + postID = arg.postID; + return "#p" + postID; + }, + index: function(arg) { + var boardID; + boardID = arg.boardID; + return location.protocol + "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/"; + }, + catalog: function(arg) { + var boardID; + boardID = arg.boardID; + if (boardID === 'f') { + return void 0; + } else { + return location.protocol + "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/catalog"; + } + }, + archive: function(arg) { + var boardID; + boardID = arg.boardID; + if (BoardConfig.isArchived(boardID)) { + return location.protocol + "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/archive"; + } else { + return void 0; + } + }, + threadJSON: function(arg) { + var boardID, threadID; + boardID = arg.boardID, threadID = arg.threadID; + return location.protocol + "//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json"; + }, + threadsListJSON: function(arg) { + var boardID; + boardID = arg.boardID; + return location.protocol + "//a.4cdn.org/" + boardID + "/threads.json"; + }, + archiveListJSON: function(arg) { + var boardID; + boardID = arg.boardID; + if (BoardConfig.isArchived(boardID)) { + return location.protocol + "//a.4cdn.org/" + boardID + "/archive.json"; + } else { + return ''; + } + }, + catalogJSON: function(arg) { + var boardID; + boardID = arg.boardID; + return location.protocol + "//a.4cdn.org/" + boardID + "/catalog.json"; + }, + file: function(arg, filename) { + var boardID, hostname; + boardID = arg.boardID; + hostname = boardID === 'f' ? ImageHost.flashHost() : ImageHost.host(); + return location.protocol + "//" + hostname + "/" + boardID + "/" + filename; + }, + thumb: function(arg, filename) { + var boardID; + boardID = arg.boardID; + return location.protocol + "//" + (ImageHost.thumbHost()) + "/" + boardID + "/" + filename; + } + }, + isPrunedByAge: function(arg) { + var boardID; + boardID = arg.boardID; + return boardID === 'f'; + }, + areMD5sDeferred: function(arg) { + var boardID; + boardID = arg.boardID; + return boardID === 'f'; + }, + isOnePage: function(arg) { + var boardID; + boardID = arg.boardID; + return boardID === 'f'; + }, + noAudio: function(arg) { + var boardID; + boardID = arg.boardID; + return BoardConfig.noAudio(boardID); + }, + selectors: { + board: '.board', + thread: '.thread', + threadDivider: '.board > hr', + summary: '.summary', + postContainer: '.postContainer', + replyOriginal: '.replyContainer:not([data-clone])', + sideArrows: 'div.sideArrows', + post: '.post', + infoRoot: '.postInfo', + info: { + subject: '.subject', + name: '.name', + email: '.useremail', + tripcode: '.postertrip', + uniqueIDRoot: '.posteruid', + uniqueID: '.posteruid > .hand', + capcode: '.capcode.hand', + pass: '.n-pu', + flag: '.flag, .bfl', + date: '.dateTime', + nameBlock: '.nameBlock', + quote: '.postNum > a:nth-of-type(2)', + reply: '.replylink' + }, + icons: { + isSticky: '.stickyIcon', + isClosed: '.closedIcon', + isArchived: '.archivedIcon' + }, + file: { + text: '.file > :first-child', + link: '.fileText > a', + thumb: 'a.fileThumb > [data-md5]' + }, + thumbLink: 'a.fileThumb', + highlightable: { + op: '.opContainer', + reply: ' > .reply', + catalog: '' + }, + comment: '.postMessage', + spoiler: 's', + quotelink: ':not(pre) > .quotelink', + catalog: { + board: '#threads', + thread: '.thread', + thumb: '.thumb' + }, + boardList: '#boardNavDesktop > .boardList', + boardListBottom: '#boardNavDesktopFoot > .boardList', + styleSheet: 'link[title=switch]', + psa: '#globalMessage', + psaTop: '#globalToggle', + searchBox: '#search-box', + nav: { + prev: '.prev > form > [type=submit]', + next: '.next > form > [type=submit]' + } + }, + classes: { + highlight: 'highlight' + }, + xpath: { + thread: 'div[contains(concat(" ",@class," ")," thread ")]', + postContainer: 'div[contains(@class,"postContainer")]', + replyContainer: 'div[contains(@class,"replyContainer")]' + }, + regexp: { + quotelink: /^https?:\/\/boards\.4chan(?:nel)?\.org\/+([^\/]+)\/+thread\/+(\d+)(?:[\/?][^#]*)?(?:#p(\d+))?$/, + quotelinkHTML: /]*\bhref="(?:(?:\/\/boards\.4chan(?:nel)?\.org)?\/([^\/]+)\/thread\/)?(\d+)?(?:#p(\d+))?"/g, + pass: /^https?:\/\/www\.4chan(?:nel)?\.org\/+pass(?:$|[?#])/, + captcha: /^https?:\/\/sys\.4chan(?:nel)?\.org\/+captcha(?:$|[?#])/ + }, + bgColoredEl: function() { + return $.el('div', { + className: 'reply' + }); + }, + isThisPageLegit: function() { + var ref, ref1; + return ((ref = location.hostname) === 'boards.4chan.org' || ref === 'boards.4channel.org') && d.doctype && !$('link[href*="favicon-status.ico"]', d.head) && ((ref1 = d.title) !== '4chan - Temporarily Offline' && ref1 !== '4chan - Error' && ref1 !== '504 Gateway Time-out' && ref1 !== 'MathJax Equation Source'); + }, + is404: function() { + var ref; + return ((ref = d.title) === '4chan - Temporarily Offline' || ref === '4chan - 404 Not Found') || (g.VIEW === 'thread' && $('.board') && !$('.opContainer')); + }, + isIncomplete: function() { + var ref; + return ((ref = g.VIEW) === 'index' || ref === 'thread') && !$('.board + *'); + }, + isBoardlessPage: function(url) { + var ref; + return (ref = url.hostname) === 'www.4chan.org' || ref === 'www.4channel.org'; + }, + isAuxiliaryPage: function(url) { + var ref; + return (ref = url.hostname) !== 'boards.4chan.org' && ref !== 'boards.4channel.org'; + }, + isFileURL: function(url) { + return ImageHost.test(url.hostname); + }, + initAuxiliary: function() { + var match, pathname; + switch (location.hostname) { + case 'www.4chan.org': + case 'www.4channel.org': + if (SW.yotsuba.regexp.pass.test(location.href)) { + PassMessage.init(); + } else { + $.onExists(doc, 'body', function() { + return $.addStyle(CSS.www); + }); + Captcha.replace.init(); + } + break; + case 'sys.4chan.org': + case 'sys.4channel.org': + pathname = location.pathname.split(/\/+/); + if (pathname[2] === 'imgboard.php') { + if (/\bmode=report\b/.test(location.search)) { + Report.init(); + } else if ((match = location.search.match(/\bres=(\d+)/))) { + $.ready(function() { + var ref; + if (Conf['404 Redirect'] && ((ref = $.id('errmsg')) != null ? ref.textContent : void 0) === 'Error: Specified thread does not exist.') { + return Redirect.navigate('thread', { + boardID: g.BOARD.ID, + postID: +match[1] + }); + } + }); + } + } else if (pathname[2] === 'post') { + PostSuccessful.init(); + } + } + }, + scriptData: function() { + var j, len, ref, script; + ref = $$('script:not([src])', d.head); + for (j = 0, len = ref.length; j < len; j++) { + script = ref[j]; + if (/\bcooldowns *=/.test(script.textContent)) { + return script.textContent; + } + } + return ''; + }, + parseThreadMetadata: function(thread) { + var file, m, scriptData; + scriptData = this.scriptData(); + thread.postLimit = /\bbumplimit *= *1\b/.test(scriptData); + thread.fileLimit = /\bimagelimit *= *1\b/.test(scriptData); + thread.ipCount = (m = scriptData.match(/\bunique_ips *= *(\d+)\b/)) ? +m[1] : void 0; + if (g.BOARD.ID === 'f' && thread.OP.file) { + file = thread.OP.file; + return $.ajax(this.urls.threadJSON({ + boardID: 'f', + threadID: thread.ID + }), { + timeout: $.MINUTE, + onloadend: function() { + if (this.response) { + return file.text.dataset.md5 = file.MD5 = this.response.posts[0].md5; + } + } + }); + } + }, + parseNodes: function(post, nodes) { + var icon, j, len, ref, results, type; + if (post.boardID === 'f') { + ref = ['Sticky', 'Closed']; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + type = ref[j]; + if ((icon = $("img[alt=" + type + "]", nodes.info))) { + results.push($.addClass(icon, (type.toLowerCase()) + "Icon", 'retina')); + } + } + return results; + } + }, + parseDate: function(node) { + return new Date(node.dataset.utc * 1000); + }, + parseFile: function(post, file) { + var info, link, m, ref, ref1, ref2, text, thumb; + text = file.text, link = file.link, thumb = file.thumb; + if (!(info = (ref = link.nextSibling) != null ? ref.textContent.match(/\(([\d.]+ [KMG]?B).*\)/) : void 0)) { + return false; + } + $.extend(file, { + name: text.title || link.title || link.textContent, + size: info[1], + dimensions: (ref1 = info[0].match(/\d+x\d+/)) != null ? ref1[0] : void 0, + tag: (ref2 = info[0].match(/,[^,]*, ([a-z]+)\)/i)) != null ? ref2[1] : void 0, + MD5: text.dataset.md5 + }); + if (thumb) { + $.extend(file, { + thumbURL: thumb.src, + MD5: thumb.dataset.md5, + isSpoiler: $.hasClass(thumb.parentNode, 'imgspoiler') + }); + if (file.isSpoiler) { + file.thumbURL = (m = link.href.match(/\d+(?=\.\w+$)/)) ? location.protocol + "//" + (ImageHost.thumbHost()) + "/" + post.board + "/" + m[0] + "s.jpg" : void 0; + } + } + return true; + }, + cleanComment: function(bq) { + var abbr, br, i, j, k, len, node, ref; + if ((abbr = $('.abbr', bq))) { + ref = $$('.abbr + br, .exif', bq); + for (j = 0, len = ref.length; j < len; j++) { + node = ref[j]; + $.rm(node); + } + for (i = k = 0; k < 2; i = ++k) { + if ((br = abbr.previousSibling) && br.nodeName === 'BR') { + $.rm(br); + } + } + return $.rm(abbr); + } + }, + cleanCommentDisplay: function(bq) { + var b; + if ((b = $('b', bq)) && /^Rolled /.test(b.textContent)) { + $.rm(b); + } + return $.rm($('.fortune', bq)); + }, + insertTags: function(bq) { + var j, k, len, len1, node, ref, ref1; + ref = $$('s, .removed-spoiler', bq); + for (j = 0, len = ref.length; j < len; j++) { + node = ref[j]; + $.replace(node, [$.tn('[spoiler]')].concat(slice.call(node.childNodes), [$.tn('[/spoiler]')])); + } + ref1 = $$('.prettyprint', bq); + for (k = 0, len1 = ref1.length; k < len1; k++) { + node = ref1[k]; + $.replace(node, [$.tn('[code]')].concat(slice.call(node.childNodes), [$.tn('[/code]')])); + } + }, + hasCORS: function(url) { + return url.split('/').slice(0, 3).join('/') === location.protocol + '//a.4cdn.org'; + }, + sfwBoards: function(sfw) { + return BoardConfig.sfwBoards(sfw); + }, + uidColor: function(uid) { + var i, msg; + msg = 0; + i = 0; + while (i < 8) { + msg = (msg << 5) - msg + uid.charCodeAt(i++); + } + return (msg >> 8) & 0xFFFFFF; + }, + isLinkified: function(link) { + return ImageHost.test(link.hostname); + }, + testNativeExtension: function() { + return $.global(function() { + if (window.Parser.postMenuIcon) { + return this.enabled = 'true'; + } + }); + }, + transformBoardList: function() { + var a, chr, i, items, j, len, node, nodes, ref, spacer, span; + nodes = []; + spacer = function() { + return $.el('span', { + className: 'spacer' + }); + }; + items = $.X('.//a|.//text()[not(ancestor::a)]', $(SW.yotsuba.selectors.boardList)); + i = 0; + while (node = items.snapshotItem(i++)) { + switch (node.nodeName) { + case '#text': + ref = node.nodeValue; + for (j = 0, len = ref.length; j < len; j++) { + chr = ref[j]; + span = $.el('span', { + textContent: chr + }); + if (chr === ' ') { + span.className = 'space'; + } + if (chr === ']') { + nodes.push(spacer()); + } + nodes.push(span); + if (chr === '[') { + nodes.push(spacer()); + } + } + break; + case 'A': + a = node.cloneNode(true); + nodes.push(a); + } + } + return nodes; + } + }; + +}).call(this); + +(function() { + var Build, + slice = [].slice; + + Build = { + staticPath: '//s.4cdn.org/image/', + gifIcon: window.devicePixelRatio >= 2 ? '@2x.gif' : '.gif', + spoilerRange: $.dict(), + shortFilename: function(filename) { + var ext; + ext = filename.match(/\.?[^\.]*$/)[0]; + if (filename.length - ext.length > 30) { + return (filename.match(/(?:[\uD800-\uDBFF][\uDC00-\uDFFF]|[^]){0,25}/)[0]) + "(...)" + ext; + } else { + return filename; + } + }, + spoilerThumb: function(boardID) { + var spoilerRange; + if (spoilerRange = Build.spoilerRange[boardID]) { + return Build.staticPath + "spoiler-" + boardID + (Math.floor(1 + spoilerRange * Math.random())) + ".png"; + } else { + return Build.staticPath + "spoiler.png"; + } + }, + sameThread: function(boardID, threadID) { + return g.VIEW === 'thread' && g.BOARD.ID === boardID && g.THREADID === +threadID; + }, + threadURL: function(boardID, threadID) { + if (boardID !== g.BOARD.ID) { + return "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/thread/" + threadID; + } else if (g.VIEW !== 'thread' || +threadID !== g.THREADID) { + return "/" + boardID + "/thread/" + threadID; + } else { + return ''; + } + }, + postURL: function(boardID, threadID, postID) { + return (Build.threadURL(boardID, threadID)) + "#p" + postID; + }, + parseJSON: function(data, arg) { + var boardID, key, o, siteID; + siteID = arg.siteID, boardID = arg.boardID; + o = { + ID: data.no, + postID: data.no, + threadID: data.resto || data.no, + boardID: boardID, + siteID: siteID, + isReply: !!data.resto, + isSticky: !!data.sticky, + isClosed: !!data.closed, + isArchived: !!data.archived, + fileDeleted: !!data.filedeleted, + filesDeleted: data.filedeleted ? [0] : [] + }; + o.info = { + subject: $.unescape(data.sub), + email: $.unescape(data.email), + name: $.unescape(data.name) || '', + tripcode: data.trip, + pass: data.since4pass != null ? "" + data.since4pass : void 0, + uniqueID: data.id, + flagCode: data.country, + flagCodeTroll: data.board_flag, + flag: $.unescape(data.country_name || data.flag_name), + dateUTC: data.time, + dateText: data.now, + commentHTML: { + innerHTML: data.com || '' + } + }; + if (data.capcode) { + o.info.capcode = data.capcode.replace(/_highlight$/, '').replace(/_/g, ' ').replace(/\b\w/g, function(c) { + return c.toUpperCase(); + }); + o.capcodeHighlight = /_highlight$/.test(data.capcode); + delete o.info.uniqueID; + } + o.files = []; + if (data.ext) { + o.file = SW.yotsuba.Build.parseJSONFile(data, { + siteID: siteID, + boardID: boardID + }); + o.files.push(o.file); + } + o.extra = $.dict(); + for (key in data) { + if (key[0] === 'x') { + o.extra[key] = data[key]; + } + } + return o; + }, + parseJSONFile: function(data, arg) { + var boardID, filename, o, site, siteID; + siteID = arg.siteID, boardID = arg.boardID; + site = g.sites[siteID]; + filename = site.software === 'yotsuba' && boardID === 'f' ? "" + (encodeURIComponent(data.filename)) + data.ext : "" + data.tim + data.ext; + o = { + name: ($.unescape(data.filename)) + data.ext, + url: site.urls.file({ + siteID: siteID, + boardID: boardID + }, filename), + height: data.h, + width: data.w, + MD5: data.md5, + size: $.bytesToString(data.fsize), + thumbURL: site.urls.thumb({ + siteID: siteID, + boardID: boardID + }, data.tim + "s.jpg"), + theight: data.tn_h, + twidth: data.tn_w, + isSpoiler: !!data.spoiler, + tag: data.tag, + hasDownscale: !!data.m_img + }; + if ((data.h != null) && !/\.pdf$/.test(o.url)) { + o.dimensions = o.width + "x" + o.height; + } + return o; + }, + parseComment: function(html) { + html = html.replace(//gi, '\n').replace(/\n\n]*>/g, ''); + return $.unescape(html); + }, + parseCommentDisplay: function(html) { + var html2; + if (!(Conf['Remove Spoilers'] || Conf['Reveal Spoilers'])) { + while ((html2 = html.replace(/(?:(?!<\/?s>).)*<\/s>/g, '[spoiler]')) !== html) { + html = html2; + } + } + html = html.replace(/^Rolled [^<]*<\/b>/i, '').replace(/ " + ((!o.isReply || boardID === "f" || subject) ? "" + E(subject || "") + " " : "") + "" + ((email) ? "" : "") + "" + E(name) + "" + ((tripcode) ? " " + E(tripcode) + "" : "") + ((pass) ? " " : "") + ((capcode) ? " ## " + E(capcode) + "" : "") + ((email) ? "" : "") + ((boardID === "f" && !o.isReply || capcodeDescription) ? "" : " ") + ((capcodeDescription) ? " \""" : "") + ((uniqueID && !capcode) ? " (ID: " + E(uniqueID) + ")" : "") + ((flagCode) ? " " : "") + ((flagCodeTroll) ? " " : "") + "
" + E(dateText) + " No." + E(ID) + "" + ((o.isSticky) ? " \"Sticky\"" : "") + ((o.isClosed && !o.isArchived) ? " \"Closed\"" : "") + ((o.isArchived) ? " \"Archived\"" : "") + ((!o.isReply && g.VIEW === "index") ? "   [Reply]" : "") + ""}; + + /* File Info */ + if (file) { + protocol = /^https?:(?=\/\/i\.4cdn\.org\/)/; + fileURL = file.url.replace(protocol, ''); + shortFilename = Build.shortFilename(file.name); + fileThumb = file.isSpoiler ? Build.spoilerThumb(boardID) : file.thumbURL.replace(protocol, ''); + } + fileBlock = {innerHTML: ((file) ? "
" + ((boardID === "f") ? "
File: " + E(file.name) + "-(" + E(file.size) + ", " + E(file.dimensions) + ((file.tag) ? ", " + E(file.tag) : "") + ")
" : "
File: " + ((file.isSpoiler) ? "Spoiler Image" : E(shortFilename)) + " (" + E(file.size) + ", " + E(file.dimensions || "PDF") + ")
\""") + "
" : ((o.fileDeleted) ? "
\"File
" : ""))}; + + /* Whole Post */ + postClass = o.isReply ? 'reply' : 'op'; + wholePost = {innerHTML: ((o.isReply) ? "
>>
" : "") + "
" + ((o.isReply) ? (postInfo).innerHTML + (fileBlock).innerHTML : (fileBlock).innerHTML + (postInfo).innerHTML) + "
" + (commentHTML).innerHTML + "
"}; + container = $.el('div', { + className: "postContainer " + postClass + "Container", + id: "pc" + ID + }); + $.extend(container, wholePost); + ref1 = $$('.quotelink', container); + for (i = 0, len = ref1.length; i < len; i++) { + quote = ref1[i]; + href = quote.getAttribute('href'); + if (href[0] === '#') { + if (!Build.sameThread(boardID, threadID)) { + quote.href = Build.threadURL(boardID, threadID) + href; + } + } else { + if ((match = quote.href.match(SW.yotsuba.regexp.quotelink)) && (Build.sameThread(match[1], match[2]))) { + quote.href = href.match(/(#[^#]*)?$/)[0] || '#'; + } + } + } + return container; + }, + summaryText: function(status, posts, files) { + var text; + text = ''; + if (status) { + text += status + " "; + } + text += posts + " post" + (posts > 1 ? 's' : ''); + if (+files) { + text += " and " + files + " image repl" + (files > 1 ? 'ies' : 'y'); + } + return text += " " + (status === '-' ? 'shown' : 'omitted') + "."; + }, + summary: function(boardID, threadID, posts, files) { + return $.el('a', { + className: 'summary', + textContent: Build.summaryText('', posts, files), + href: "/" + boardID + "/thread/" + threadID + }); + }, + thread: function(thread, data, withReplies) { + var files, posts, ref, root, summary; + if ((root = thread.nodes.root)) { + $.rmAll(root); + } else { + thread.nodes.root = root = $.el('div', { + className: 'thread', + id: "t" + data.no + }); + } + if (Build.hat) { + $.add(root, Build.hat.cloneNode(false)); + } + $.add(root, thread.OP.nodes.root); + if (data.omitted_posts || !withReplies && data.replies) { + ref = withReplies ? [ + data.omitted_posts, data.images - data.last_replies.filter(function(data) { + return !!data.ext; + }).length + ] : [data.replies, data.images], posts = ref[0], files = ref[1]; + summary = Build.summary(thread.board.ID, data.no, posts, files); + $.add(root, summary); + } + return root; + }, + catalogThread: function(thread, data, pageCount) { + var br, container, cssText, fileCount, gifIcon, i, imgClass, len, postCount, ratio, ref, root, spoilerRange, src, staticPath, tn_h, tn_w; + staticPath = Build.staticPath, gifIcon = Build.gifIcon; + tn_w = data.tn_w, tn_h = data.tn_h; + if (data.spoiler && !Conf['Reveal Spoiler Thumbnails']) { + src = staticPath + "spoiler"; + if (spoilerRange = Build.spoilerRange[thread.board]) { + src += ("-" + thread.board) + Math.floor(1 + spoilerRange * Math.random()); + } + src += '.png'; + imgClass = 'spoiler-file'; + cssText = "--tn-w: 100; --tn-h: 100;"; + } else if (data.filedeleted) { + src = staticPath + "filedeleted-res" + gifIcon; + imgClass = 'deleted-file'; + } else if (thread.OP.file) { + src = thread.OP.file.thumbURL; + ratio = 250 / Math.max(tn_w, tn_h); + cssText = "--tn-w: " + (tn_w * ratio) + "; --tn-h: " + (tn_h * ratio) + ";"; + } else { + src = staticPath + "nofile.png"; + imgClass = 'no-file'; + } + postCount = data.replies + 1; + fileCount = data.images + !!data.ext; + container = $.el('div', {innerHTML: "
" + E(postCount) + " / " + E(fileCount) + " / " + E(pageCount) + "" + ((thread.isSticky) ? "" : "") + ((thread.isClosed) ? "" : "") + "
"}); + $.before(thread.OP.nodes.info, slice.call(container.childNodes)); + ref = $$('br', thread.OP.nodes.comment); + for (i = 0, len = ref.length; i < len; i++) { + br = ref[i]; + if (br.previousSibling && br.previousSibling.nodeName === 'BR') { + $.addClass(br, 'extra-linebreak'); + } + } + root = $.el('div', { + className: 'thread catalog-thread', + id: "t" + thread + }); + if (thread.OP.highlights) { + $.addClass.apply($, [root].concat(slice.call(thread.OP.highlights))); + } + if (!thread.OP.file) { + $.addClass(root, 'noFile'); + } + root.style.cssText = cssText || ''; + return root; + }, + catalogReply: function(thread, data) { + var excerpt, link; + excerpt = ''; + if (data.com) { + excerpt = Build.parseCommentDisplay(data.com).replace(/>>\d+/g, '').trim().replace(/\n+/g, ' // '); + } + if (data.ext) { + excerpt || (excerpt = "" + ($.unescape(data.filename)) + data.ext); + } + if (data.com) { + excerpt || (excerpt = $.unescape(data.com.replace(//gi, ' // '))); + } + excerpt || (excerpt = '\xA0'); + if (excerpt.length > 73) { + excerpt = excerpt.slice(0, 70) + "..."; + } + link = Build.postURL(thread.board.ID, thread.ID, data.no); + return $.el('div', { + className: 'catalog-reply' + }, {innerHTML: ": " + E(excerpt) + "..."}); + } + }; + + SW.yotsuba.Build = Build; + +}).call(this); + +Site = (function() { + var Site; + + Site = { + defaultProperties: { + '4chan.org': { + software: 'yotsuba' + }, + '4channel.org': { + canonical: '4chan.org' + }, + '4cdn.org': { + canonical: '4chan.org' + }, + 'notso.smuglo.li': { + canonical: 'smuglo.li' + }, + 'smugloli.net': { + canonical: 'smuglo.li' + }, + 'smug.nepu.moe': { + canonical: 'smuglo.li' + } + }, + init: function(cb) { + var hostname; + $.extend(Conf['siteProperties'], Site.defaultProperties); + hostname = Site.resolve(); + if (hostname && $.hasOwn(SW, Conf['siteProperties'][hostname].software)) { + this.set(hostname); + cb(); + } + return $.onExists(doc, 'body', (function(_this) { + return function() { + var base, base1, changed, changes, key, properties, software; + for (software in SW) { + if (!((changes = typeof (base = SW[software]).detect === "function" ? base.detect() : void 0))) { + continue; + } + changes.software = software; + hostname = location.hostname.replace(/^www\./, ''); + properties = ((base1 = Conf['siteProperties'])[hostname] || (base1[hostname] = $.dict())); + changed = 0; + for (key in changes) { + if (!(properties[key] !== changes[key])) { + continue; + } + properties[key] = changes[key]; + changed++; + } + if (changed) { + $.set('siteProperties', Conf['siteProperties']); + } + if (!g.SITE) { + _this.set(hostname); + cb(); + } + return; + } + }; + })(this)); + }, + resolve: function(url) { + var canonical, hostname; + if (url == null) { + url = location; + } + hostname = url.hostname; + while (hostname && !$.hasOwn(Conf['siteProperties'], hostname)) { + hostname = hostname.replace(/^[^.]*\.?/, ''); + } + if (hostname) { + if ((canonical = Conf['siteProperties'][hostname].canonical)) { + hostname = canonical; + } + } + return hostname; + }, + parseURL: function(url) { + var siteID; + siteID = Site.resolve(url); + return Main.parseURL(g.sites[siteID], url); + }, + set: function(hostname) { + var ID, properties, ref, site, software; + ref = Conf['siteProperties']; + for (ID in ref) { + properties = ref[ID]; + if (properties.canonical) { + continue; + } + software = properties.software; + if (!(software && $.hasOwn(SW, software))) { + continue; + } + g.sites[ID] = site = Object.create(SW[software]); + $.extend(site, { + ID: ID, + siteID: ID, + properties: properties, + software: software + }); + } + return g.SITE = g.sites[hostname]; + } + }; + + return Site; + +}).call(this); + +Redirect = (function() { + var Redirect, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + Redirect = { + archives: [ + { "uid": 3, "name": "4plebs", "domain": "archive.4plebs.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "adv", "f", "hr", "mlpol", "mo", "o", "pol", "s4s", "sp", "tg", "trv", "tv", "x" ], "files": [ "adv", "f", "hr", "mlpol", "mo", "o", "pol", "s4s", "sp", "tg", "trv", "tv", "x" ], "reports": true }, + { "uid": 10, "name": "warosu", "domain": "warosu.org", "http": false, "https": true, "software": "fuuka", "boards": [ "3", "biz", "cgl", "ck", "diy", "fa", "ic", "jp", "lit", "sci", "vr", "vt" ], "files": [ "3", "biz", "cgl", "ck", "diy", "fa", "ic", "jp", "lit", "sci", "vr", "vt" ], "search": [ "biz", "cgl", "ck", "diy", "fa", "ic", "jp", "lit", "sci", "vr", "vt" ] }, + { "uid": 23, "name": "Desuarchive", "domain": "desuarchive.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "a", "aco", "an", "c", "cgl", "co", "d", "fit", "g", "his", "int", "k", "m", "mlp", "mu", "q", "qa", "r9k", "tg", "trash", "vr", "wsg" ], "files": [ "a", "aco", "an", "c", "cgl", "co", "d", "fit", "g", "his", "int", "k", "m", "mlp", "mu", "q", "qa", "r9k", "tg", "trash", "vr" ], "reports": true }, + { "uid": 24, "name": "fireden.net", "domain": "boards.fireden.net", "http": false, "https": true, "software": "foolfuuka", "boards": [ "cm", "co", "ic", "sci", "vip", "y" ], "files": [ "cm", "co", "ic", "sci", "vip", "y" ], "search": [ "cm", "co", "ic", "sci", "y" ] }, + { "uid": 25, "name": "arch.b4k.co", "domain": "arch.b4k.co", "http": true, "https": true, "software": "foolfuuka", "boards": [ "g", "mlp", "qb", "v", "vg", "vm", "vmg", "vp", "vrpg", "vst" ], "files": [ "qb", "v", "vg", "vm", "vmg", "vp", "vrpg", "vst" ], "search": [ "qb", "v", "vg", "vm", "vmg", "vp", "vrpg", "vst" ] }, + { "uid": 29, "name": "Archived.Moe", "domain": "archived.moe", "http": true, "https": true, "software": "foolfuuka", "boards": [ "3", "a", "aco", "adv", "an", "asp", "b", "bant", "biz", "c", "can", "cgl", "ck", "cm", "co", "cock", "con", "d", "diy", "e", "f", "fa", "fap", "fit", "fitlit", "g", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "int", "jp", "k", "lgbt", "lit", "m", "mlp", "mlpol", "mo", "mtv", "mu", "n", "news", "o", "out", "outsoc", "p", "po", "pol", "pw", "q", "qa", "qb", "qst", "r", "r9k", "s", "s4s", "sci", "soc", "sp", "spa", "t", "tg", "toy", "trash", "trv", "tv", "u", "v", "vg", "vint", "vip", "vm", "vmg", "vp", "vr", "vrpg", "vst", "vt", "w", "wg", "wsg", "wsr", "x", "xs", "y" ], "files": [ "can", "cock", "con", "fap", "fitlit", "gd", "mlpol", "mo", "mtv", "outsoc", "po", "q", "qb", "qst", "spa", "vint", "vip" ], "search": [ "aco", "adv", "an", "asp", "b", "bant", "biz", "c", "can", "cgl", "ck", "cm", "cock", "con", "d", "diy", "e", "f", "fap", "fitlit", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "lgbt", "lit", "mlpol", "mo", "mtv", "n", "news", "o", "out", "outsoc", "p", "po", "pw", "q", "qa", "qst", "r", "s", "soc", "spa", "trv", "u", "vint", "vip", "vrpg", "w", "wg", "wsg", "wsr", "x", "y" ], "reports": true }, + { "uid": 30, "name": "TheBArchive.com", "domain": "thebarchive.com", "http": true, "https": true, "software": "foolfuuka", "boards": [ "b", "bant" ], "files": [ "b", "bant" ], "reports": true }, + { "uid": 31, "name": "Archive Of Sins", "domain": "archiveofsins.com", "http": true, "https": true, "software": "foolfuuka", "boards": [ "h", "hc", "hm", "i", "lgbt", "r", "s", "soc", "t", "u" ], "files": [ "h", "hc", "hm", "i", "lgbt", "r", "s", "soc", "t", "u" ], "reports": true }, + { "uid": 36, "name": "palanq.win", "domain": "archive.palanq.win", "http": false, "https": true, "software": "foolfuuka", "boards": [ "bant", "c", "con", "e", "i", "n", "news", "out", "p", "pw", "qst", "toy", "vip", "vp", "vt", "w", "wg", "wsr" ], "files": [ "bant", "c", "e", "i", "n", "news", "out", "p", "pw", "qst", "toy", "vip", "vp", "vt", "w", "wg", "wsr" ], "reports": true }, + { "uid": 37, "name": "Eientei", "domain": "eientei.xyz", "http": false, "https": true, "software": "Eientei", "boards": [ "3", "i", "sci", "xs" ], "files": [ "3", "i", "sci", "xs" ], "reports": true } + ], + init: function() { + var now, ref; + this.selectArchives(); + if (Conf['archiveAutoUpdate']) { + now = Date.now(); + if (!((now - 2 * $.DAY < (ref = Conf['lastarchivecheck']) && ref <= now))) { + return this.update(); + } + } + }, + selectArchives: function() { + var archive, archives, boardID, boards, data, files, id, j, k, key, l, len, len1, len2, name, o, record, ref, ref1, ref2, software, type, uid; + o = { + thread: $.dict(), + post: $.dict(), + file: $.dict() + }; + archives = $.dict(); + ref = Conf['archives']; + for (j = 0, len = ref.length; j < len; j++) { + data = ref[j]; + ref1 = ['boards', 'files']; + for (k = 0, len1 = ref1.length; k < len1; k++) { + key = ref1[k]; + if (!(data[key] instanceof Array)) { + data[key] = []; + } + } + uid = data.uid, name = data.name, boards = data.boards, files = data.files, software = data.software; + if (software !== 'fuuka' && software !== 'foolfuuka') { + continue; + } + archives[JSON.stringify(uid != null ? uid : name)] = data; + for (l = 0, len2 = boards.length; l < len2; l++) { + boardID = boards[l]; + if (!(boardID in o.thread)) { + o.thread[boardID] = data; + } + if (!(boardID in o.post || software !== 'foolfuuka')) { + o.post[boardID] = data; + } + if (!(boardID in o.file || indexOf.call(files, boardID) < 0)) { + o.file[boardID] = data; + } + } + } + ref2 = Conf['selectedArchives']; + for (boardID in ref2) { + record = ref2[boardID]; + for (type in record) { + id = record[type]; + if (!((archive = archives[JSON.stringify(id)]) && $.hasOwn(o, type))) { + continue; + } + boards = type === 'file' ? archive.files : archive.boards; + if (indexOf.call(boards, boardID) >= 0) { + o[type][boardID] = archive; + } + } + } + return Redirect.data = o; + }, + update: function(cb) { + var err, fail, i, j, k, len, len1, load, nloaded, ref, ref1, response, responses, url, urls; + urls = []; + responses = []; + nloaded = 0; + ref = Conf['archiveLists'].split('\n'); + for (j = 0, len = ref.length; j < len; j++) { + url = ref[j]; + if (!(url[0] !== '#')) { + continue; + } + url = url.trim(); + if (url) { + urls.push(url); + } + } + fail = function(url, action, msg) { + return new Notice('warning', "Error " + action + " archive data from\n" + url + "\n" + msg, 20); + }; + load = function(i) { + return function() { + var response; + if (this.status !== 200) { + return fail(urls[i], 'fetching', (this.status ? "Error " + this.statusText + " (" + this.status + ")" : 'Connection Error')); + } + response = this.response; + if (!(response instanceof Array)) { + response = [response]; + } + responses[i] = response; + nloaded++; + if (nloaded === urls.length) { + return Redirect.parse(responses, cb); + } + }; + }; + if (urls.length) { + for (i = k = 0, len1 = urls.length; k < len1; i = ++k) { + url = urls[i]; + if ((ref1 = url[0]) === '[' || ref1 === '{') { + try { + response = JSON.parse(url); + } catch (error) { + err = error; + fail(url, 'parsing', err.message); + continue; + } + load(i).call({ + status: 200, + response: response + }); + } else { + CrossOrigin.ajax(url, { + onloadend: load(i) + }); + } + } + } else { + Redirect.parse([], cb); + } + }, + parse: function(responses, cb) { + var archiveUIDs, archives, data, items, j, k, len, len1, ref, response, uid; + archives = []; + archiveUIDs = $.dict(); + for (j = 0, len = responses.length; j < len; j++) { + response = responses[j]; + for (k = 0, len1 = response.length; k < len1; k++) { + data = response[k]; + uid = JSON.stringify((ref = data.uid) != null ? ref : data.name); + if (uid in archiveUIDs) { + $.extend(archiveUIDs[uid], data); + } else { + archiveUIDs[uid] = $.dict.clone(data); + archives.push(data); + } + } } items = { archives: archives, @@ -6368,7 +9091,7 @@ Redirect = (function() { protocol: function(archive) { var protocol; protocol = location.protocol; - if (!archive[protocol.slice(0, -1)]) { + if (!$.getOwn(archive, protocol.slice(0, -1))) { protocol = protocol === 'https:' ? 'http:' : 'https:'; } return protocol + "//"; @@ -6398,6 +9121,16 @@ Redirect = (function() { file: function(archive, arg) { var boardID, filename; boardID = arg.boardID, filename = arg.filename; + if (!filename) { + return ''; + } + if (boardID === 'f') { + filename = encodeURIComponent($.unescape(decodeURIComponent(filename))); + } else { + if (/[sm]\.jpg$/.test(filename)) { + return ''; + } + } return "" + (Redirect.protocol(archive)) + archive.domain + "/" + boardID + "/full_image/" + filename; }, board: function(archive, arg) { @@ -6410,9 +9143,10 @@ Redirect = (function() { boardID = arg.boardID, type = arg.type, value = arg.value; type = type === 'name' ? 'username' : type === 'MD5' ? 'image' : type; if (type === 'capcode') { - value = { - 'Developer': 'dev' - }[value] || value.toLowerCase(); + value = $.getOwn({ + 'Developer': 'dev', + 'Verified': 'ver' + }, value) || value.toLowerCase(); } else if (type === 'image') { value = value.replace(/[+\/=]/g, function(c) { return { @@ -6426,6 +9160,19 @@ Redirect = (function() { path = archive.software === 'foolfuuka' ? boardID + "/search/" + type + "/" + value + "/" : type === 'image' ? boardID + "/image/" + value : boardID + "/?task=search2&search_" + type + "=" + value; return "" + (Redirect.protocol(archive)) + archive.domain + "/" + path; }, + report: function(boardID) { + var archive, boards, domain, https, j, len, name, ref, reports, software, urls; + urls = []; + ref = Conf['archives']; + for (j = 0, len = ref.length; j < len; j++) { + archive = ref[j]; + software = archive.software, https = archive.https, reports = archive.reports, boards = archive.boards, name = archive.name, domain = archive.domain; + if (software === 'foolfuuka' && https && reports && boards instanceof Array && indexOf.call(boards, boardID) >= 0) { + urls.push([name, "https://" + domain + "/_/api/chan/offsite_report/"]); + } + } + return urls; + }, securityCheck: function(url) { return /^https:\/\//.test(url) || location.protocol === 'http:' || Conf['Exempt Archives from Encryption']; }, @@ -6452,50 +9199,10 @@ Anonymize = (function() { Anonymize = { init: function() { - var ref; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Anonymize'])) { - return; - } - if (g.VIEW === 'archive') { - return this.archive(); - } - return Callbacks.Post.push({ - name: 'Anonymize', - cb: this.node - }); - }, - node: function() { - var email, name, ref, tripcode; - if (this.info.capcode || this.isClone) { + if (!Conf['Anonymize']) { return; } - ref = this.nodes, name = ref.name, tripcode = ref.tripcode, email = ref.email; - if (this.info.name !== 'Anonymous') { - name.textContent = 'Anonymous'; - } - if (tripcode) { - $.rm(tripcode); - delete this.nodes.tripcode; - } - if (email) { - $.replace(email, name); - return delete this.nodes.email; - } - }, - archive: function() { - return $.ready(function() { - var i, j, len, len1, name, ref, ref1, trip; - ref = $$('.name'); - for (i = 0, len = ref.length; i < len; i++) { - name = ref[i]; - name.textContent = 'Anonymous'; - } - ref1 = $$('.postertrip'); - for (j = 0, len1 = ref1.length; j < len1; j++) { - trip = ref1[j]; - $.rm(trip); - } - }); + return $.addClass(doc, 'anonymize'); } }; @@ -6505,48 +9212,59 @@ Anonymize = (function() { Filter = (function() { var Filter, - indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, + slice = [].slice; Filter = { - filters: {}, + filters: $.dict(), init: function() { - var boards, err, excludes, filter, hl, i, key, len, line, op, ref, ref1, ref2, ref3, ref4, ref5, ref6, regexp, stub, top; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Filter'])) { + var base, base1, boards, err, excludes, file, filter, hide, hl, i, isstring, j, key, len, len1, line, mask, noti, op, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, regexp, stub, top, type, types; + if (!(((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'catalog') && Conf['Filter'])) { + return; + } + if (g.VIEW === 'catalog' && !Conf['Filter in Native Catalog']) { return; } if (!Conf['Filtered Backlinks']) { $.addClass(doc, 'hide-backlinks'); } for (key in Config.filter) { - this.filters[key] = []; ref1 = Conf[key].split('\n'); for (i = 0, len = ref1.length; i < len; i++) { line = ref1[i]; if (line[0] === '#') { continue; } - if (!(regexp = line.match(/\/(.+)\/(\w*)/))) { + if (!(regexp = line.match(/\/(.*)\/(\w*)/))) { continue; } filter = line.replace(regexp[0], ''); - boards = ((ref2 = filter.match(/boards:([^;]+)/)) != null ? ref2[1].toLowerCase() : void 0) || 'global'; - boards = boards === 'global' ? null : boards.split(','); - excludes = boards === null ? ((ref3 = filter.match(/exclude:([^;]+)/)) != null ? ref3[1].toLowerCase().split(',') : void 0) || null : null; - if (key === 'uniqueID' || key === 'MD5') { + boards = this.parseBoards((ref2 = filter.match(/(?:^|;)\s*boards:([^;]+)/)) != null ? ref2[1] : void 0); + excludes = this.parseBoards((ref3 = filter.match(/(?:^|;)\s*exclude:([^;]+)/)) != null ? ref3[1] : void 0); + if ((isstring = (key === 'uniqueID' || key === 'MD5'))) { regexp = regexp[1]; } else { try { regexp = RegExp(regexp[1], regexp[2]); - } catch (_error) { - err = _error; + } catch (error) { + err = error; new Notice('warning', [$.tn("Invalid " + key + " filter:"), $.el('br'), $.tn(line), $.el('br'), $.tn(err.message)], 60); continue; } } - op = ((ref4 = filter.match(/[^t]op:(yes|no|only)/)) != null ? ref4[1] : void 0) || 'yes'; + op = ((ref4 = filter.match(/(?:^|;)\s*op:(no|only)/)) != null ? ref4[1] : void 0) || ''; + mask = $.getOwn({ + 'no': 1, + 'only': 2 + }, op) || 0; + file = ((ref5 = filter.match(/(?:^|;)\s*file:(no|only)/)) != null ? ref5[1] : void 0) || ''; + mask = mask | ($.getOwn({ + 'no': 4, + 'only': 8 + }, file) || 0); stub = (function() { - var ref5; - switch ((ref5 = filter.match(/stub:(yes|no)/)) != null ? ref5[1] : void 0) { + var ref6; + switch ((ref6 = filter.match(/(?:^|;)\s*stub:(yes|no)/)) != null ? ref6[1] : void 0) { case 'yes': return true; case 'no': @@ -6555,146 +9273,416 @@ Filter = (function() { return Conf['Stubs']; } })(); - if (hl = /highlight/.test(filter)) { - hl = ((ref5 = filter.match(/highlight:([\w-]+)/)) != null ? ref5[1] : void 0) || 'filter-highlight'; - top = ((ref6 = filter.match(/top:(yes|no)/)) != null ? ref6[1] : void 0) || 'yes'; + noti = /(?:^|;)\s*notify/.test(filter); + if ((hl = /(?:^|;)\s*highlight/.test(filter))) { + hl = ((ref6 = filter.match(/(?:^|;)\s*highlight:([\w-]+)/)) != null ? ref6[1] : void 0) || 'filter-highlight'; + top = ((ref7 = filter.match(/(?:^|;)\s*top:(yes|no)/)) != null ? ref7[1] : void 0) || 'yes'; top = top === 'yes'; } - this.filters[key].push(this.createFilter(regexp, boards, excludes, op, stub, hl, top)); - } - if (!this.filters[key].length) { - delete this.filters[key]; + if (key === 'general') { + if ((types = filter.match(/(?:^|;)\s*type:([^;]*)/))) { + types = types[1].split(','); + } else { + types = ['subject', 'name', 'filename', 'comment']; + } + } + hide = !(hl || noti); + filter = { + isstring: isstring, + regexp: regexp, + boards: boards, + excludes: excludes, + mask: mask, + hide: hide, + stub: stub, + hl: hl, + top: top, + noti: noti + }; + if (key === 'general') { + for (j = 0, len1 = types.length; j < len1; j++) { + type = types[j]; + ((base = this.filters)[type] || (base[type] = [])).push(filter); + } + } else { + ((base1 = this.filters)[key] || (base1[key] = [])).push(filter); + } } } if (!Object.keys(this.filters).length) { return; } - return Callbacks.Post.push({ - name: 'Filter', - cb: this.node - }); + if (g.VIEW === 'catalog') { + return Filter.catalog(); + } else { + return Callbacks.Post.push({ + name: 'Filter', + cb: this.node + }); + } }, - createFilter: function(regexp, boards, excludes, op, stub, hl, top) { - var settings, test; - test = typeof regexp === 'string' ? function(value) { - return regexp === value; - } : function(value) { - return regexp.test(value); - }; - settings = { - hide: !hl, - stub: stub, - "class": hl, - top: top - }; - return function(value, boardID, isReply) { - if (boards && indexOf.call(boards, boardID) < 0) { - return false; - } - if (excludes && indexOf.call(excludes, boardID) >= 0) { - return false; - } - if (isReply && op === 'only' || !isReply && op === 'no') { - return false; - } - if (!test(value)) { - return false; + parseBoards: function(boardsRaw) { + var boardID, boardID2, boards, i, j, len, len1, ref, ref1, ref2, ref3, site, siteFilter, siteID; + if (!boardsRaw) { + return false; + } + if ((boards = Filter.parseBoardsMemo[boardsRaw])) { + return boards; + } + boards = $.dict(); + siteFilter = ''; + ref = boardsRaw.split(','); + for (i = 0, len = ref.length; i < len; i++) { + boardID = ref[i]; + if (indexOf.call(boardID, ':') >= 0) { + ref1 = boardID.split(':').slice(-2), siteFilter = ref1[0], boardID = ref1[1]; + } + ref2 = g.sites; + for (siteID in ref2) { + site = ref2[siteID]; + if (siteID.slice(0, siteFilter.length) === siteFilter) { + if (boardID === 'nsfw' || boardID === 'sfw') { + ref3 = (typeof site.sfwBoards === "function" ? site.sfwBoards(boardID === 'sfw') : void 0) || []; + for (j = 0, len1 = ref3.length; j < len1; j++) { + boardID2 = ref3[j]; + boards[siteID + "/" + boardID2] = true; + } + } else { + boards[siteID + "/" + (encodeURIComponent(boardID))] = true; + } + } } - return settings; - }; + } + Filter.parseBoardsMemo[boardsRaw] = boards; + return boards; }, - node: function() { - var filter, i, key, len, ref, ref1, result, value; - if (this.isClone) { - return; + parseBoardsMemo: $.dict(), + test: function(post, hideable) { + var board, filter, hide, hl, i, j, key, len, len1, mask, noti, ref, ref1, ref2, site, stub, top, value; + if (hideable == null) { + hideable = true; } + if (post.filterResults) { + return post.filterResults; + } + hide = false; + stub = true; + hl = void 0; + top = false; + noti = false; + if (QuoteYou.isYou(post)) { + hideable = false; + } + mask = (post.isReply ? 2 : 1); + mask = mask | (post.file ? 4 : 8); + board = post.siteID + "/" + post.boardID; + site = post.siteID + "/*"; for (key in Filter.filters) { - if ((value = Filter[key](this)) != null) { - ref = Filter.filters[key]; - for (i = 0, len = ref.length; i < len; i++) { - filter = ref[i]; - if (!(result = filter(value, this.board.ID, this.isReply))) { + ref = Filter.values(key, post); + for (i = 0, len = ref.length; i < len; i++) { + value = ref[i]; + ref1 = Filter.filters[key]; + for (j = 0, len1 = ref1.length; j < len1; j++) { + filter = ref1[j]; + if ((filter.boards && !(filter.boards[board] || filter.boards[site])) || (filter.excludes && (filter.excludes[board] || filter.excludes[site])) || (filter.mask & mask) || (filter.isstring ? filter.regexp !== value : !filter.regexp.test(value))) { continue; } - if (result.hide && !this.isFetchedQuote) { - if (this.isReply) { - PostHiding.hide(this, result.stub); - } else if (g.VIEW === 'index') { - ThreadHiding.hide(this.thread, result.stub); - } else { - continue; + if (filter.hide) { + if (hideable) { + hide = true; + stub && (stub = filter.stub); + } + } else { + if (!(hl && (ref2 = filter.hl, indexOf.call(hl, ref2) >= 0))) { + (hl || (hl = [])).push(filter.hl); + } + top || (top = filter.top); + if (filter.noti) { + noti = true; } - return; - } - $.addClass(this.nodes.root, result["class"]); - if (!(this.highlights && (ref1 = result["class"], indexOf.call(this.highlights, ref1) >= 0))) { - (this.highlights || (this.highlights = [])).push(result["class"]); - } - if (!this.isReply && result.top) { - this.thread.isOnTop = true; } } } } + if (hide) { + return { + hide: hide, + stub: stub + }; + } else { + return { + hl: hl, + top: top, + noti: noti + }; + } }, - isHidden: function(post) { - var filter, i, key, len, ref, result, value; - for (key in Filter.filters) { - if ((value = Filter[key](post)) != null) { - ref = Filter.filters[key]; - for (i = 0, len = ref.length; i < len; i++) { - filter = ref[i]; - if (result = filter(value, post.boardID, post.isReply)) { - if (result.hide) { - return true; - } - } - } + node: function() { + var hide, hl, noti, ref, stub, top; + if (this.isClone) { + return; + } + ref = Filter.test(this, !this.isFetchedQuote && (this.isReply || g.VIEW === 'index')), hide = ref.hide, stub = ref.stub, hl = ref.hl, top = ref.top, noti = ref.noti; + if (hide) { + if (this.isReply) { + PostHiding.hide(this, stub); + } else { + ThreadHiding.hide(this.thread, stub); + } + } else { + if (hl) { + this.highlights = hl; + $.addClass.apply($, [this.nodes.root].concat(slice.call(hl))); } } - return false; + if (noti && Unread.posts && (this.ID > Unread.lastReadPost) && !QuoteYou.isYou(this)) { + return Unread.openNotification(this, ' triggered a notification filter'); + } }, - postID: function(post) { - var ref; - return "" + ((ref = post.ID) != null ? ref : post.postID); + catalog: function() { + var base, url; + if (!(url = typeof (base = g.SITE.urls).catalogJSON === "function" ? base.catalogJSON(g.BOARD) : void 0)) { + return; + } + Filter.catalogData = $.dict(); + $.ajax(url, { + onloadend: Filter.catalogParse + }); + return Callbacks.CatalogThreadNative.push({ + name: 'Filter', + cb: this.catalogNode + }); }, - name: function(post) { - return post.info.name; + catalogParse: function() { + var i, item, j, len, len1, page, ref, ref1, ref2; + if ((ref = this.status) !== 200 && ref !== 404) { + new Notice('warning', "Failed to fetch catalog JSON data. " + (this.status ? "Error " + this.statusText + " (" + this.status + ")" : 'Connection Error'), 1); + return; + } + ref1 = this.response; + for (i = 0, len = ref1.length; i < len; i++) { + page = ref1[i]; + ref2 = page.threads; + for (j = 0, len1 = ref2.length; j < len1; j++) { + item = ref2[j]; + Filter.catalogData[item.no] = item; + } + } + g.BOARD.threads.forEach(function(thread) { + if (thread.catalogViewNative) { + return Filter.catalogNode.call(thread.catalogViewNative); + } + }); }, - uniqueID: function(post) { - return post.info.uniqueID; + catalogNode: function() { + var base, hide, hl, ref, ref1, top; + if (!(this.boardID === g.BOARD.ID && Filter.catalogData[this.ID])) { + return; + } + if ((ref = QuoteYou.db) != null ? ref.get({ + siteID: g.SITE.ID, + boardID: this.boardID, + threadID: this.ID, + postID: this.ID + }) : void 0) { + return; + } + ref1 = Filter.test(g.SITE.Build.parseJSON(Filter.catalogData[this.ID], this)), hide = ref1.hide, hl = ref1.hl, top = ref1.top; + if (hide) { + return this.nodes.root.hidden = true; + } else { + if (hl) { + this.highlights = hl; + $.addClass.apply($, [this.nodes.root].concat(slice.call(hl))); + } + if (top) { + $.prepend(this.nodes.root.parentNode, this.nodes.root); + return typeof (base = g.SITE).catalogPin === "function" ? base.catalogPin(this.nodes.root) : void 0; + } + } }, - tripcode: function(post) { - return post.info.tripcode; + isHidden: function(post) { + return !!Filter.test(post).hide; }, - capcode: function(post) { - return post.info.capcode; + valueF: { + postID: function(post) { + return ["" + post.ID]; + }, + name: function(post) { + return [post.info.name]; + }, + uniqueID: function(post) { + return [post.info.uniqueID || '']; + }, + tripcode: function(post) { + return [post.info.tripcode]; + }, + capcode: function(post) { + return [post.info.capcode]; + }, + pass: function(post) { + return [post.info.pass]; + }, + email: function(post) { + return [post.info.email]; + }, + subject: function(post) { + return [post.info.subject || (post.isReply ? void 0 : '')]; + }, + comment: function(post) { + var base, ref, ref1; + return [((base = post.info).comment != null ? base.comment : base.comment = (ref = g.sites[post.siteID]) != null ? (ref1 = ref.Build) != null ? typeof ref1.parseComment === "function" ? ref1.parseComment(post.info.commentHTML.innerHTML) : void 0 : void 0 : void 0)]; + }, + flag: function(post) { + return [post.info.flag]; + }, + filename: function(post) { + return post.files.map(function(f) { + return f.name; + }); + }, + dimensions: function(post) { + return post.files.map(function(f) { + return f.dimensions; + }); + }, + filesize: function(post) { + return post.files.map(function(f) { + return f.size; + }); + }, + MD5: function(post) { + return post.files.map(function(f) { + return f.MD5; + }); + } }, - subject: function(post) { - return post.info.subject; + values: function(key, post) { + if ($.hasOwn(Filter.valueF, key)) { + return Filter.valueF[key](post).filter(function(v) { + return v != null; + }); + } else { + return [ + key.split('+').map(function(k) { + var f; + if ((f = $.getOwn(Filter.valueF, k))) { + return f(post).map(function(v) { + return v || ''; + }).join('\n'); + } else { + return ''; + } + }).join('\n') + ]; + } }, - comment: function(post) { - var base; - return (base = post.info).comment != null ? base.comment : base.comment = Build.parseComment(post.info.commentHTML.innerHTML); + addFilter: function(type, re, cb) { + if (!$.hasOwn(Config.filter, type)) { + return; + } + return $.get(type, Conf[type], function(item) { + var save; + save = item[type]; + save = save ? save + "\n" + re : re; + return $.set(type, save, cb); + }); }, - flag: function(post) { - return post.info.flag; + removeFilters: function(type, res, cb) { + return $.get(type, Conf[type], function(item) { + var save; + save = item[type]; + res = res.map(Filter.escape).join('|'); + save = save.replace(RegExp("(?:$\n|^)(?:" + res + ")$", 'mg'), ''); + return $.set(type, save, cb); + }); }, - filename: function(post) { - var ref; - return (ref = post.file) != null ? ref.name : void 0; + showFilters: function(type) { + var section, select; + Settings.open('Filter'); + section = $('.section-container'); + select = $('select[name=filter]', section); + select.value = type; + Settings.selectFilter.call(select); + return $.onExists(section, 'textarea', function(ta) { + var tl; + tl = ta.textLength; + ta.setSelectionRange(tl, tl); + return ta.focus(); + }); }, - dimensions: function(post) { - var ref; - return (ref = post.file) != null ? ref.dimensions : void 0; + quickFilterMD5: function() { + var files, filter, links, msg, notice, origin, post; + post = Get.postFromNode(this); + files = post.files.filter(function(f) { + return f.MD5; + }); + if (!files.length) { + return; + } + filter = files.map(function(f) { + return "/" + f.MD5 + "/"; + }).join('\n'); + Filter.addFilter('MD5', filter); + origin = post.origin || post; + if (origin.isReply) { + PostHiding.hide(origin); + } else if (g.VIEW === 'index') { + ThreadHiding.hide(origin.thread); + } + if (!Conf['MD5 Quick Filter Notifications']) { + if (post.nodes.post.getBoundingClientRect().height) { + new Notice('info', 'MD5 filtered.', 2); + } + return; + } + notice = Filter.quickFilterMD5.notice; + if (notice) { + notice.filters.push(filter); + notice.posts.push(origin); + return $('span', notice.el).textContent = notice.filters.length + " MD5s filtered."; + } else { + msg = $.el('div', {innerHTML: "MD5 filtered. [show] [undo]"}); + notice = Filter.quickFilterMD5.notice = new Notice('info', msg, void 0, function() { + return delete Filter.quickFilterMD5.notice; + }); + notice.filters = [filter]; + notice.posts = [origin]; + links = $$('a', msg); + $.on(links[0], 'click', Filter.quickFilterCB.show.bind(notice)); + return $.on(links[1], 'click', Filter.quickFilterCB.undo.bind(notice)); + } }, - filesize: function(post) { - var ref; - return (ref = post.file) != null ? ref.size : void 0; + quickFilterCB: { + show: function() { + Filter.showFilters('MD5'); + return this.close(); + }, + undo: function() { + var i, len, post, ref; + Filter.removeFilters('MD5', this.filters); + ref = this.posts; + for (i = 0, len = ref.length; i < len; i++) { + post = ref[i]; + if (post.isReply) { + PostHiding.show(post); + } else if (g.VIEW === 'index') { + ThreadHiding.show(post.thread); + } + } + return this.close(); + } }, - MD5: function(post) { - var ref; - return (ref = post.file) != null ? ref.MD5 : void 0; + escape: function(value) { + return value.replace(/\/|\\|\^|\$|\n|\.|\(|\)|\{|\}|\[|\]|\?|\*|\+|\|/g, function(c) { + if (c === '\n') { + return '\\n'; + } else if (c === '\\') { + return '\\\\'; + } else { + return "\\" + c; + } + }); }, menu: { init: function() { @@ -6714,7 +9702,7 @@ Filter = (function() { }, subEntries: [] }; - ref1 = [['Name', 'name'], ['Unique ID', 'uniqueID'], ['Tripcode', 'tripcode'], ['Capcode', 'capcode'], ['Subject', 'subject'], ['Comment', 'comment'], ['Flag', 'flag'], ['Filename', 'filename'], ['Image dimensions', 'dimensions'], ['Filesize', 'filesize'], ['Image MD5', 'MD5']]; + ref1 = [['Name', 'name'], ['Unique ID', 'uniqueID'], ['Tripcode', 'tripcode'], ['Capcode', 'capcode'], ['Pass Date', 'pass'], ['Email', 'email'], ['Subject', 'subject'], ['Comment', 'comment'], ['Flag', 'flag'], ['Filename', 'filename'], ['Image dimensions', 'dimensions'], ['Filesize', 'filesize'], ['Image MD5', 'MD5']]; for (i = 0, len = ref1.length; i < len; i++) { type = ref1[i]; entry.subEntries.push(Filter.menu.createSubEntry(type[0], type[1])); @@ -6732,40 +9720,25 @@ Filter = (function() { return { el: el, open: function(post) { - var value; - value = Filter[type](post); - return (value != null) && !(g.BOARD.ID === 'f' && type === 'MD5'); + return Filter.values(type, post).length; } }; }, makeFilter: function() { - var re, type, value; + var res, type, values; type = this.dataset.type; - value = Filter[type](Filter.menu.post); - re = type === 'uniqueID' || type === 'MD5' ? value : value.replace(/\/|\\|\^|\$|\n|\.|\(|\)|\{|\}|\[|\]|\?|\*|\+|\|/g, function(c) { - if (c === '\n') { - return '\\n'; - } else if (c === '\\') { - return '\\\\'; + values = Filter.values(type, Filter.menu.post); + res = values.map(function(value) { + var re; + re = type === 'uniqueID' || type === 'MD5' ? value : Filter.escape(value); + if (type === 'uniqueID' || type === 'MD5') { + return "/" + re + "/"; } else { - return "\\" + c; + return "/^" + re + "$/"; } - }); - re = type === 'uniqueID' || type === 'MD5' ? "/" + re + "/" : "/^" + re + "$/"; - return $.get(type, Conf[type], function(item) { - var save, section, select, ta, tl; - save = item[type]; - save = save ? save + "\n" + re : re; - $.set(type, save); - Settings.open('Filter'); - section = $('.section-container'); - select = $('select[name=filter]', section); - select.value = type; - Settings.selectFilter.call(select); - ta = $('textarea', section); - tl = ta.textLength; - ta.setSelectionRange(tl, tl); - return ta.focus(); + }).join('\n'); + return Filter.addFilter(type, res, function() { + return Filter.showFilters(type); }); } } @@ -6793,8 +9766,15 @@ PostHiding = (function() { cb: this.node }); }, + isHidden: function(boardID, threadID, postID) { + return !!(PostHiding.db && PostHiding.db.get({ + boardID: boardID, + threadID: threadID, + postID: postID + })); + }, node: function() { - var data, sideArrows; + var button, data, sa, sideArrows; if (!this.isReply || this.isClone || this.isFetchedQuote) { return; } @@ -6813,9 +9793,14 @@ PostHiding = (function() { if (!Conf['Reply Hiding Buttons']) { return; } - sideArrows = $('.sideArrows', this.nodes.root); - $.replace(sideArrows.firstChild, PostHiding.makeButton(this, 'hide')); - return sideArrows.removeAttribute('class'); + button = PostHiding.makeButton(this, 'hide'); + if ((sa = g.SITE.selectors.sideArrows)) { + sideArrows = $(sa, this.nodes.root); + $.replace(sideArrows.firstChild, button); + return sideArrows.className = 'replacedSideArrows'; + } else { + return $.prepend(this.nodes.info, button); + } }, menu: { init: function() { @@ -7086,7 +10071,7 @@ Recursive = (function() { indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; Recursive = { - recursives: {}, + recursives: $.dict(), init: function() { var ref; if ((ref = g.VIEW) !== 'index' && ref !== 'thread') { @@ -7169,6 +10154,10 @@ ThreadHiding = (function() { return this.catalogWatch(); } this.catalogSet(g.BOARD); + $.on(d, 'IndexRefreshInternal', this.onIndexRefresh); + if (Conf['Thread Hiding Buttons']) { + $.addClass(doc, 'thread-hide'); + } return Callbacks.Post.push({ name: 'Thread Hiding', cb: this.node @@ -7176,12 +10165,12 @@ ThreadHiding = (function() { }, catalogSet: function(board) { var hiddenThreads, threadID; - if (!$.hasStorage) { + if (!($.hasStorage && g.SITE.software === 'yotsuba')) { return; } hiddenThreads = ThreadHiding.db.get({ boardID: board.ID, - defaultValue: {} + defaultValue: $.dict() }); for (threadID in hiddenThreads) { hiddenThreads[threadID] = true; @@ -7189,7 +10178,7 @@ ThreadHiding = (function() { return localStorage.setItem("4chan-hide-t-" + board, JSON.stringify(hiddenThreads)); }, catalogWatch: function() { - if (!$.hasStorage) { + if (!($.hasStorage && g.SITE.software === 'yotsuba')) { return; } this.hiddenThreads = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; @@ -7205,7 +10194,7 @@ ThreadHiding = (function() { var hiddenThreads2, threadID; hiddenThreads2 = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; for (threadID in hiddenThreads2) { - if (!(threadID in ThreadHiding.hiddenThreads)) { + if (!$.hasOwn(ThreadHiding.hiddenThreads, threadID)) { ThreadHiding.db.set({ boardID: g.BOARD.ID, threadID: threadID, @@ -7216,7 +10205,7 @@ ThreadHiding = (function() { } } for (threadID in ThreadHiding.hiddenThreads) { - if (!(threadID in hiddenThreads2)) { + if (!$.hasOwn(hiddenThreads2, threadID)) { ThreadHiding.db["delete"]({ boardID: g.BOARD.ID, threadID: threadID @@ -7225,31 +10214,35 @@ ThreadHiding = (function() { } return ThreadHiding.hiddenThreads = hiddenThreads2; }, + isHidden: function(boardID, threadID) { + return !!(ThreadHiding.db && ThreadHiding.db.get({ + boardID: boardID, + threadID: threadID + })); + }, node: function() { var data; if (this.isReply || this.isClone || this.isFetchedQuote) { return; } + if (Conf['Thread Hiding Buttons']) { + $.prepend(this.nodes.root, ThreadHiding.makeButton(this.thread, 'hide')); + } if (data = ThreadHiding.db.get({ boardID: this.board.ID, threadID: this.ID })) { - ThreadHiding.hide(this.thread, data.makeStub); + return ThreadHiding.hide(this.thread, data.makeStub); } - if (!Conf['Thread Hiding Buttons']) { - return; - } - return $.prepend(this.nodes.root, ThreadHiding.makeButton(this.thread, 'hide')); }, - onIndexBuild: function(nodes) { - var i, len, root, thread; - for (i = 0, len = nodes.length; i < len; i++) { - root = nodes[i]; - thread = Get.threadFromRoot(root); + onIndexRefresh: function() { + return g.BOARD.threads.forEach(function(thread) { + var root; + root = thread.nodes.root; if (thread.isHidden && thread.stub && !root.contains(thread.stub)) { - ThreadHiding.makeStub(thread, root); + return ThreadHiding.makeStub(thread, root); } - } + }); }, menu: { init: function() { @@ -7354,17 +10347,15 @@ ThreadHiding = (function() { className: type + "-thread-button", href: 'javascript:;' }); - $.extend(a, { - innerHTML: "" - }); + $.extend(a, {innerHTML: ""}); a.dataset.fullID = thread.fullID; $.on(a, 'click', ThreadHiding.toggle); return a; }, makeStub: function(thread, root) { - var a, numReplies, summary; - numReplies = $$('.thread > .replyContainer', root).length; - if (summary = $('.summary', root)) { + var a, numReplies, summary, threadDivider; + numReplies = $$(g.SITE.selectors.replyOriginal, root).length; + if (summary = $(g.SITE.selectors.summary, root)) { numReplies += +summary.textContent.match(/\d+/); } a = ThreadHiding.makeButton(thread, 'show'); @@ -7377,7 +10368,10 @@ ThreadHiding = (function() { } else { $.add(thread.stub, a); } - return $.prepend(root, thread.stub); + $.prepend(root, thread.stub); + if ((threadDivider = $(g.SITE.selectors.threadDivider, root))) { + return $.addClass(threadDivider, 'threadDivider'); + } }, saveHiddenState: function(thread, makeStub) { if (thread.isHidden) { @@ -7398,7 +10392,7 @@ ThreadHiding = (function() { }, toggle: function(thread) { if (!(thread instanceof Thread)) { - thread = g.threads[this.dataset.fullID]; + thread = g.threads.get(this.dataset.fullID); } if (thread.isHidden) { ThreadHiding.show(thread); @@ -7415,10 +10409,12 @@ ThreadHiding = (function() { if (thread.isHidden) { return; } - threadRoot = thread.OP.nodes.root.parentNode; + threadRoot = thread.nodes.root; thread.isHidden = true; - if (Conf['JSON Index']) { - Index.updateHideLabel(); + Index.updateHideLabel(); + if (thread.catalogView && !Index.showHiddenThreads) { + $.rm(thread.catalogView.nodes.root); + $.event('PostsRemoved', null, Index.root); } if (!makeStub) { return threadRoot.hidden = true; @@ -7431,10 +10427,12 @@ ThreadHiding = (function() { $.rm(thread.stub); delete thread.stub; } - threadRoot = thread.OP.nodes.root.parentNode; + threadRoot = thread.nodes.root; threadRoot.hidden = thread.isHidden = false; - if (Conf['JSON Index']) { - return Index.updateHideLabel(); + Index.updateHideLabel(); + if (thread.catalogView && Index.showHiddenThreads) { + $.rm(thread.catalogView.nodes.root); + return $.event('PostsRemoved', null, Index.root); } } }; @@ -7443,375 +10441,178 @@ ThreadHiding = (function() { }).call(this); -Build = (function() { - var Build, - slice = [].slice; +BoardConfig = (function() { + var BoardConfig; - Build = { - staticPath: '//s.4cdn.org/image/', - gifIcon: window.devicePixelRatio >= 2 ? '@2x.gif' : '.gif', - spoilerRange: {}, - unescape: function(text) { - if (text == null) { - return text; - } - return text.replace(/<[^>]*>/g, '').replace(/&(amp|#039|quot|lt|gt|#44);/g, function(c) { - return { - '&': '&', - ''': "'", - '"': '"', - '<': '<', - '>': '>', - ',': ',' - }[c]; - }); - }, - shortFilename: function(filename) { - var ext; - ext = filename.match(/\.?[^\.]*$/)[0]; - if (filename.length - ext.length > 30) { - return (filename.match(/(?:[\uD800-\uDBFF][\uDC00-\uDFFF]|[^]){0,25}/)[0]) + "(...)" + ext; - } else { - return filename; - } - }, - spoilerThumb: function(boardID) { - var spoilerRange; - if (spoilerRange = Build.spoilerRange[boardID]) { - return Build.staticPath + "spoiler-" + boardID + (Math.floor(1 + spoilerRange * Math.random())) + ".png"; - } else { - return Build.staticPath + "spoiler.png"; + BoardConfig = { + cbs: [], + init: function() { + var boards, now, ref; + if (g.SITE.software !== 'yotsuba') { + return; } - }, - sameThread: function(boardID, threadID) { - return g.VIEW === 'thread' && g.BOARD.ID === boardID && g.THREADID === +threadID; - }, - postURL: function(boardID, threadID, postID) { - if (Build.sameThread(boardID, threadID)) { - return "#p" + postID; + now = Date.now(); + if (!((now - 2 * $.HOUR < (ref = Conf['boardConfig'].lastChecked || 0) && ref <= now))) { + return $.ajax(location.protocol + "//a.4cdn.org/boards.json", { + onloadend: this.load + }); } else { - return "/" + boardID + "/thread/" + threadID + "#p" + postID; + boards = Conf['boardConfig'].boards; + return this.set(boards); } }, - parseJSON: function(data, boardID) { - var o; - o = { - postID: data.no, - threadID: data.resto || data.no, - boardID: boardID, - isReply: !!data.resto, - isSticky: !!data.sticky, - isClosed: !!data.closed, - isArchived: !!data.archived, - fileDeleted: !!data.filedeleted - }; - o.info = { - subject: Build.unescape(data.sub), - email: Build.unescape(data.email), - name: Build.unescape(data.name) || '', - tripcode: data.trip, - uniqueID: data.id, - flagCode: data.country, - flag: Build.unescape(data.country_name), - dateUTC: data.time, - dateText: data.now, - commentHTML: { - innerHTML: data.com || '' + load: function() { + var board, boards, err, i, len, ref; + if (this.status === 200 && this.response && this.response.boards) { + boards = $.dict(); + ref = this.response.boards; + for (i = 0, len = ref.length; i < len; i++) { + board = ref[i]; + boards[board.board] = board; } - }; - if (data.capcode) { - o.info.capcode = data.capcode.replace(/_highlight$/, '').replace(/_/g, ' ').replace(/\b\w/g, function(c) { - return c.toUpperCase(); + $.set('boardConfig', { + boards: boards, + lastChecked: Date.now() }); - o.capcodeHighlight = /_highlight$/.test(data.capcode); - delete o.info.uniqueID; - } - if (data.ext) { - o.file = { - name: (Build.unescape(data.filename)) + data.ext, - url: boardID === 'f' ? location.protocol + "//i.4cdn.org/" + boardID + "/" + (encodeURIComponent(data.filename)) + data.ext : location.protocol + "//i.4cdn.org/" + boardID + "/" + data.tim + data.ext, - height: data.h, - width: data.w, - MD5: data.md5, - size: $.bytesToString(data.fsize), - thumbURL: location.protocol + "//i.4cdn.org/" + boardID + "/" + data.tim + "s.jpg", - theight: data.tn_h, - twidth: data.tn_w, - isSpoiler: !!data.spoiler, - tag: data.tag - }; - if (!/\.pdf$/.test(o.file.url)) { - o.file.dimensions = o.file.width + "x" + o.file.height; - } + } else { + boards = Conf['boardConfig'].boards; + err = (function() { + switch (this.status) { + case 0: + return 'Connection Error'; + case 200: + return 'Invalid Data'; + default: + return "Error " + this.statusText + " (" + this.status + ")"; + } + }).call(this); + new Notice('warning', "Failed to load board configuration. " + err, 20); } - return o; + return BoardConfig.set(boards); }, - parseComment: function(html) { - html = html.replace(//gi, '\n').replace(/\n\nRolled [^<]*<\/b>/i, '').replace(/]*>/g, ''); - return Build.unescape(html); - }, - postFromObject: function(data, boardID, suppressThumb) { - var o; - o = Build.parseJSON(data, boardID); - return Build.post(o, suppressThumb); - }, - post: function(o, suppressThumb) { - var boardID, capcode, capcodeDescription, capcodeLC, capcodeLong, capcodePlural, commentHTML, container, dateText, dateUTC, email, file, fileBlock, fileThumb, fileURL, flag, flagCode, gifIcon, href, i, len, match, name, postClass, postID, postInfo, postLink, protocol, quote, quoteLink, ref, ref1, shortFilename, staticPath, subject, threadID, tripcode, uniqueID, wholePost; - postID = o.postID, threadID = o.threadID, boardID = o.boardID, file = o.file; - ref = o.info, subject = ref.subject, email = ref.email, name = ref.name, tripcode = ref.tripcode, capcode = ref.capcode, uniqueID = ref.uniqueID, flagCode = ref.flagCode, flag = ref.flag, dateUTC = ref.dateUTC, dateText = ref.dateText, commentHTML = ref.commentHTML; - staticPath = Build.staticPath, gifIcon = Build.gifIcon; - - /* Post Info */ - if (capcode) { - capcodeLC = capcode.toLowerCase(); - if (capcode === 'Founder') { - capcodePlural = 'the Founder'; - capcodeDescription = "4chan's Founder"; - } else { - capcodeLong = { - 'Admin': 'Administrator', - 'Mod': 'Moderator' - }[capcode] || capcode; - capcodePlural = capcodeLong + "s"; - capcodeDescription = "a 4chan " + capcodeLong; - } - } - postLink = Build.postURL(boardID, threadID, postID); - quoteLink = Build.sameThread(boardID, threadID) ? "javascript:quote('" + (+postID) + "');" : "/" + boardID + "/thread/" + threadID + "#q" + postID; - postInfo = { - innerHTML: "
" + ((!o.isReply || boardID === "f" || subject) ? "" + E(subject || "") + " " : "") + "" + ((email) ? "" : "") + "" + E(name) + "" + ((tripcode) ? " " + E(tripcode) + "" : "") + ((capcode) ? " ## " + E(capcode) + "" : "") + ((email) ? "" : "") + ((boardID === "f" && !o.isReply || capcode) ? "" : " ") + ((capcode) ? " \""" : "") + ((uniqueID && !capcode) ? " (ID: " + E(uniqueID) + ")" : "") + ((flagCode) ? " " : "") + " " + E(dateText) + " No." + E(postID) + "" + ((o.isSticky) ? " \"Sticky\"" : "") + ((o.isClosed && !o.isArchived) ? " \"Closed\"" : "") + ((o.isArchived) ? " \"Archived\"" : "") + ((!o.isReply && g.VIEW === "index") ? "   [Reply]" : "") + "
" - }; - - /* File Info */ - if (file) { - protocol = /^https?:(?=\/\/i\.4cdn\.org\/)/; - fileURL = file.url.replace(protocol, ''); - shortFilename = Build.shortFilename(file.name); - fileThumb = file.isSpoiler ? Build.spoilerThumb(boardID) : file.thumbURL.replace(protocol, ''); + set: function(boards1) { + var ID, board, cb, i, len, ref, ref1; + this.boards = boards1; + ref = g.boards; + for (ID in ref) { + board = ref[ID]; + board.config = this.boards[ID] || {}; } - fileBlock = { - innerHTML: ((file) ? "
" + ((boardID === "f") ? "
File: " + E(file.name) + "-(" + E(file.size) + ", " + E(file.dimensions) + ((file.tag) ? ", " + E(file.tag) : "") + ")
" : "
File: " + ((file.isSpoiler) ? "Spoiler Image" : E(shortFilename)) + " (" + E(file.size) + ", " + E(file.dimensions || "PDF") + ")
") + "
" : ((o.fileDeleted) ? "
\"File
" : "")) - }; - - /* Whole Post */ - postClass = o.isReply ? 'reply' : 'op'; - wholePost = { - innerHTML: ((o.isReply) ? "
>>
" : "") + "
" + ((o.isReply) ? (postInfo).innerHTML + (fileBlock).innerHTML : (fileBlock).innerHTML + (postInfo).innerHTML) + "
" + (commentHTML).innerHTML + "
" - }; - container = $.el('div', { - className: "postContainer " + postClass + "Container", - id: "pc" + postID - }); - $.extend(container, wholePost); - ref1 = $$('.quotelink', container); + ref1 = this.cbs; for (i = 0, len = ref1.length; i < len; i++) { - quote = ref1[i]; - href = quote.getAttribute('href'); - if ((href[0] === '#') && !(Build.sameThread(boardID, threadID))) { - quote.href = ("/" + boardID + "/thread/" + threadID) + href; - } else if ((match = href.match(/^\/([^\/]+)\/thread\/(\d+)/)) && (Build.sameThread(match[1], match[2]))) { - quote.href = href.match(/(#[^#]*)?$/)[0] || '#'; - } else if (/^\d+(#|$)/.test(href) && !(g.VIEW === 'thread' && g.BOARD.ID === boardID)) { - quote.href = "/" + boardID + "/thread/" + href; - } + cb = ref1[i]; + $.queueTask(cb); } - return container; }, - summaryText: function(status, posts, files) { - var text; - text = ''; - if (status) { - text += status + " "; - } - text += posts + " post" + (posts > 1 ? 's' : ''); - if (+files) { - text += " and " + files + " image repl" + (files > 1 ? 'ies' : 'y'); + ready: function(cb) { + if (this.boards) { + return cb(); + } else { + return this.cbs.push(cb); } - return text += " " + (status === '-' ? 'shown' : 'omitted') + "."; - }, - summary: function(boardID, threadID, posts, files) { - return $.el('a', { - className: 'summary', - textContent: Build.summaryText('', posts, files), - href: "/" + boardID + "/thread/" + threadID - }); }, - thread: function(board, data) { - var OP, root; - Build.spoilerRange[board] = data.custom_spoiler; - if (OP = board.posts[data.no]) { - if (OP.isFetchedQuote) { - OP = null; + sfwBoards: function(sfw) { + var board, data, ref, results; + ref = this.boards || Conf['boardConfig'].boards; + results = []; + for (board in ref) { + data = ref[board]; + if (!!data.ws_board === sfw) { + results.push(board); } } - if (OP && (root = OP.nodes.root.parentNode)) { - $.rmAll(root); - } else { - root = $.el('div', { - className: 'thread', - id: "t" + data.no - }); - } - $.add(root, Build.excerptThread(board, data, OP)); - return root; + return results; }, - excerptThread: function(board, data, OP) { - var files, nodes, posts, ref; - nodes = [OP ? OP.nodes.root : Build.postFromObject(data, board.ID, true)]; - if (data.omitted_posts || !Conf['Show Replies'] && data.replies) { - ref = Conf['Show Replies'] ? [ - data.omitted_posts, data.images - data.last_replies.filter(function(data) { - return !!data.ext; - }).length - ] : [data.replies, data.images], posts = ref[0], files = ref[1]; - nodes.push(Build.summary(board.ID, data.no, posts, files)); - } - return nodes; + isSFW: function(board) { + var ref; + return !!((ref = (this.boards || Conf['boardConfig'].boards)[board]) != null ? ref.ws_board : void 0); }, - catalogThread: function(thread) { - var br, cc, comment, data, exif, fileCount, gifIcon, href, i, imgClass, j, k, l, len, len1, len2, len3, pageCount, postCount, pp, quote, ref, ref1, ref2, ref3, ref4, root, spoilerRange, src, staticPath; - staticPath = Build.staticPath, gifIcon = Build.gifIcon; - data = Index.liveThreadData[Index.liveThreadIDs.indexOf(thread.ID)]; - if (data.spoiler && !Conf['Reveal Spoiler Thumbnails']) { - src = staticPath + "spoiler"; - if (spoilerRange = Build.spoilerRange[thread.board]) { - src += ("-" + thread.board) + Math.floor(1 + spoilerRange * Math.random()); - } - src += '.png'; - imgClass = 'spoiler-file'; - } else if (data.filedeleted) { - src = staticPath + "filedeleted-res" + gifIcon; - imgClass = 'deleted-file'; - } else if (thread.OP.file) { - src = thread.OP.file.thumbURL; - } else { - src = staticPath + "nofile.png"; - imgClass = 'no-file'; - } - postCount = data.replies + 1; - fileCount = data.images + !!data.ext; - pageCount = Math.floor(Index.liveThreadIDs.indexOf(thread.ID) / Index.threadsNumPerPage) + 1; - comment = { - innerHTML: data.com || '' - }; - root = $.el('div', { - className: 'catalog-thread' - }); - $.extend(root, { - innerHTML: "
" + E(postCount) + " / " + E(fileCount) + " / " + E(pageCount) + "
" + ((thread.OP.info.subject) ? "
" + E(thread.OP.info.subject) + "
" : "") + "
" + (comment).innerHTML + "
" - }); - root.dataset.fullID = thread.fullID; - if (thread.OP.highlights) { - $.addClass.apply($, [root].concat(slice.call(thread.OP.highlights))); - } - ref = $$('.quotelink', root.lastElementChild); - for (i = 0, len = ref.length; i < len; i++) { - quote = ref[i]; - href = quote.getAttribute('href'); - if (href[0] === '#') { - quote.href = ("/" + thread.board + "/thread/" + thread.ID) + href; - } - } - ref1 = $$('.abbr, .exif', root.lastElementChild); - for (j = 0, len1 = ref1.length; j < len1; j++) { - exif = ref1[j]; - $.rm(exif); - } - ref2 = $$('.prettyprint', root.lastElementChild); - for (k = 0, len2 = ref2.length; k < len2; k++) { - pp = ref2[k]; - cc = $.el('span', { - className: 'catalog-code' - }); - $.add(cc, slice.call(pp.childNodes)); - $.replace(pp, cc); - } - ref3 = $$('br', root.lastElementChild); - for (l = 0, len3 = ref3.length; l < len3; l++) { - br = ref3[l]; - if (((ref4 = br.previousSibling) != null ? ref4.nodeName : void 0) === 'BR') { - $.rm(br); - } - } - if (thread.isSticky) { - $.add($('.catalog-icons', root), $.el('img', { - src: staticPath + "sticky" + gifIcon, - className: 'stickyIcon', - title: 'Sticky' - })); - } - if (thread.isClosed) { - $.add($('.catalog-icons', root), $.el('img', { - src: staticPath + "closed" + gifIcon, - className: 'closedIcon', - title: 'Closed' - })); - } - if (data.bumplimit) { - $.addClass($('.post-count', root), 'warning'); - } - if (data.imagelimit) { - $.addClass($('.file-count', root), 'warning'); + domain: function() { + return 'boards.4chan.org'; + }, + isArchived: function(board) { + var data; + data = (this.boards || Conf['boardConfig'].boards)[board]; + return !data || data.is_archived; + }, + noAudio: function(boardID) { + var boards; + if (g.SITE.software !== 'yotsuba') { + return false; } - return root; + boards = this.boards || Conf['boardConfig'].boards; + return boards && boards[boardID] && !boards[boardID].webm_audio; + }, + title: function(boardID) { + var ref, ref1; + return ((ref = this.boards || Conf['boardConfig'].boards) != null ? (ref1 = ref[boardID]) != null ? ref1.title : void 0 : void 0) || ''; } }; - return Build; - -}).call(this); - -(function() { - + return BoardConfig; }).call(this); Get = (function() { var Get, + slice = [].slice, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; Get = { + url: function() { + var IDs, args, f, site, type; + type = arguments[0], IDs = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; + if ((site = g.sites[IDs.siteID]) && (f = $.getOwn(site.urls, type))) { + return f.apply(null, [IDs].concat(slice.call(args))); + } else { + return void 0; + } + }, threadExcerpt: function(thread) { var OP, excerpt, ref, ref1; OP = thread.OP; - excerpt = ("/" + thread.board + "/ - ") + (((ref = OP.info.subject) != null ? ref.trim() : void 0) || OP.info.commentDisplay.replace(/\n+/g, ' // ') || ((ref1 = OP.file) != null ? ref1.name : void 0) || OP.info.nameBlock); + excerpt = ("/" + (decodeURIComponent(thread.board.ID)) + "/ - ") + (((ref = OP.info.subject) != null ? ref.trim() : void 0) || OP.commentDisplay().replace(/\n+/g, ' // ') || ((ref1 = OP.file) != null ? ref1.name : void 0) || ("No." + OP)); if (excerpt.length > 73) { return excerpt.slice(0, 70) + "..."; } return excerpt; }, threadFromRoot: function(root) { - return g.threads[g.BOARD + "." + root.id.slice(1)]; + var board; + if (root == null) { + return null; + } + board = root.dataset.board; + return g.threads.get((board ? encodeURIComponent(board) : g.BOARD.ID) + "." + (root.id.match(/\d*$/)[0])); }, threadFromNode: function(node) { - return Get.threadFromRoot($.x('ancestor::div[@class="thread"]', node)); + return Get.threadFromRoot($.x("ancestor-or-self::" + g.SITE.xpath.thread, node)); }, postFromRoot: function(root) { var index, post; if (root == null) { return null; } - post = g.posts[root.dataset.fullID]; + post = g.posts.get(root.dataset.fullID); index = root.dataset.clone; if (index) { - return post.clones[index]; + return post.clones[+index]; } else { return post; } }, postFromNode: function(root) { - return Get.postFromRoot($.x('(ancestor::div[contains(@class,"postContainer")][1]|following::div[contains(@class,"postContainer")][1])', root)); + return Get.postFromRoot($.x("ancestor-or-self::" + g.SITE.xpath.postContainer + "[1]", root)); }, postDataFromLink: function(link) { - var boardID, path, postID, ref, threadID; - if (link.hostname === 'boards.4chan.org') { - path = link.pathname.split(/\/+/); - boardID = path[1]; - threadID = path[3]; - postID = link.hash.slice(2); - } else { + var boardID, match, postID, ref, ref1, threadID; + if (link.dataset.postID) { ref = link.dataset, boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID; threadID || (threadID = 0); + } else { + match = link.href.match(g.SITE.regexp.quotelink); + ref1 = match.slice(1), boardID = ref1[0], threadID = ref1[1], postID = ref1[2]; + postID || (postID = threadID); } return { boardID: boardID, @@ -7842,7 +10643,7 @@ Get = (function() { ref = post.quotes; for (i = 0, len = ref.length; i < len; i++) { quote = ref[i]; - if (qPost = posts[quote]) { + if (qPost = posts.get(quote)) { handleQuotes(qPost, 'backlinks'); } } @@ -7852,17 +10653,6 @@ Get = (function() { ref1 = Get.postDataFromLink(quotelink), boardID = ref1.boardID, postID = ref1.postID; return boardID === post.board.ID && postID === post.ID; }); - }, - scriptData: function() { - var i, len, ref, script; - ref = $$('script:not([src])', d.head); - for (i = 0, len = ref.length; i < len; i++) { - script = ref[i]; - if (/\bcooldowns *=/.test(script.textContent)) { - return script.textContent; - } - } - return ''; } }; @@ -7871,18 +10661,28 @@ Get = (function() { }).call(this); Header = (function() { - var Header; + var Header, + slice = [].slice; Header = { init: function() { - var barFixedToggler, barPositionToggler, box, customNavToggler, editCustomNav, footerToggler, headerToggler, linkJustifyToggler, menuButton, scrollHeaderToggler, shortcutToggler; + var barFixedToggler, barPositionToggler, box, cs, customNavToggler, editCustomNav, footerToggler, headerToggler, linkJustifyToggler, menuButton, scrollHeaderToggler, shortcutToggler; + $.onExists(doc, 'body', (function(_this) { + return function() { + if (!Main.isThisPageLegit()) { + return; + } + $.add(_this.bar, [_this.noticesRoot, _this.toggle]); + $.prepend(d.body, _this.bar); + $.add(d.body, Header.hover); + return _this.setBarPosition(Conf['Bottom Header']); + }; + })(this)); this.menu = new UI.Menu('header'); menuButton = $.el('span', { className: 'menu-button' }); - $.extend(menuButton, { - innerHTML: "" - }); + $.extend(menuButton, {innerHTML: ""}); box = UI.checkbox; barFixedToggler = box('Fixed Header', 'Fixed Header'); headerToggler = box('Header auto-hide', 'Auto-hide header'); @@ -7955,65 +10755,52 @@ Header = (function() { } ] }); - $.on(window, 'load popstate', Header.hashScroll); - $.on(d, 'CreateNotification', this.createNotification); - $.asap((function() { - return d.body; - }), (function(_this) { - return function() { - if (!Main.isThisPageLegit()) { + $.on(window, 'load popstate', Header.hashScroll); + $.on(d, 'CreateNotification', this.createNotification); + this.setBoardList(); + $.onExists(doc, g.SITE.selectors.boardList + " + *", Header.generateFullBoardList); + Main.ready(function() { + var a, absbot, footer, i, len, ref; + if (g.SITE.software === 'yotsuba' && !(footer = $.id('boardNavDesktopFoot'))) { + if (!(absbot = $.id('absbot'))) { return; } - $.asap((function() { - return $.id('boardNavMobile') || d.readyState !== 'loading'; - }), function() { - var a, footer; - footer = $.id('boardNavDesktop').cloneNode(true); - footer.id = 'boardNavDesktopFoot'; - $('#navtopright', footer).id = 'navbotright'; - $('#settingsWindowLink', footer).id = 'settingsWindowLinkBot'; - Header.bottomBoardList = $('.boardList', footer); - if (a = $("a[href*='/" + g.BOARD + "/']", footer)) { - a.className = 'current'; - } - Main.ready(function() { - var absbot, oldFooter; - if ((oldFooter = $.id('boardNavDesktopFoot'))) { - return $.replace($('.boardList', oldFooter), Header.bottomBoardList); - } else if ((absbot = $.id('absbot'))) { - $.before(absbot, footer); - return $.globalEval('window.cloneTopNav = function() {};'); - } - }); - return Header.setBoardList(); + footer = $.id('boardNavDesktop').cloneNode(true); + footer.id = 'boardNavDesktopFoot'; + $('#navtopright', footer).id = 'navbotright'; + $('#settingsWindowLink', footer).id = 'settingsWindowLinkBot'; + $.before(absbot, footer); + $.global(function() { + return window.cloneTopNav = function() {}; }); - $.prepend(d.body, _this.bar); - $.add(d.body, Header.hover); - _this.setBarPosition(Conf['Bottom Header']); - return _this; - }; - })(this)); - Main.ready((function(_this) { - return function() { - var cs; - if (g.VIEW === 'catalog' || !Conf['Disable Native Extension']) { - cs = $.el('a', { - href: 'javascript:;' - }); - if (g.VIEW === 'catalog') { - cs.title = cs.textContent = 'Catalog Settings'; - cs.className = 'fa fa-book'; - } else { - cs.title = cs.textContent = '4chan Settings'; - cs.className = 'native-settings'; + } + if ((Header.bottomBoardList = $(g.SITE.selectors.boardListBottom))) { + ref = $$('a', Header.bottomBoardList); + for (i = 0, len = ref.length; i < len; i++) { + a = ref[i]; + if (a.hostname === location.hostname && a.pathname.split('/')[1] === g.BOARD.ID) { + a.className = 'current'; } - $.on(cs, 'click', function() { - return $.id('settingsWindowLink').click(); - }); - return _this.addShortcut('native', cs, 810); } - }; - })(this)); + return CatalogLinks.setLinks(Header.bottomBoardList); + } + }); + if (g.SITE.software === 'yotsuba' && (g.VIEW === 'catalog' || !Conf['Disable Native Extension'])) { + cs = $.el('a', { + href: 'javascript:;' + }); + if (g.VIEW === 'catalog') { + cs.title = cs.textContent = 'Catalog Settings'; + cs.className = 'fa fa-book'; + } else { + cs.title = cs.textContent = '4chan Settings'; + cs.className = 'native-settings'; + } + $.on(cs, 'click', function() { + return $.id('settingsWindowLink').click(); + }); + this.addShortcut('native', cs, 810); + } return this.enableDesktopNotifications(); }, bar: $.el('div', { @@ -8032,84 +10819,61 @@ Header = (function() { id: 'scroll-marker' }), setBoardList: function() { - var a, boardList, btn, chr, i, j, len, len1, node, nodes, ref, ref1, spacer, span; + var boardList, btn; Header.boardList = boardList = $.el('span', { id: 'board-list' }); - $.extend(boardList, { - innerHTML: "" - }); + $.extend(boardList, {innerHTML: ""}); btn = $('.hide-board-list-button', boardList); $.on(btn, 'click', Header.toggleBoardList); - nodes = []; - spacer = function() { - return $.el('span', { - className: 'spacer' - }); - }; - ref = $('#boardNavDesktop > .boardList').childNodes; - for (i = 0, len = ref.length; i < len; i++) { - node = ref[i]; - switch (node.nodeName) { - case '#text': - ref1 = node.nodeValue; - for (j = 0, len1 = ref1.length; j < len1; j++) { - chr = ref1[j]; - span = $.el('span', { - textContent: chr - }); - if (chr === ' ') { - span.className = 'space'; - } - if (chr === ']') { - nodes.push(spacer()); - } - nodes.push(span); - if (chr === '[') { - nodes.push(spacer()); - } - } - break; - case 'A': - a = node.cloneNode(true); - if (a.pathname.split('/')[1] === g.BOARD.ID) { - a.className = 'current'; - } - nodes.push(a); - } - } - $.add($('.boardList', boardList), nodes); - $.add(Header.bar, [Header.boardList, Header.shortcuts, Header.noticesRoot, Header.toggle]); + $.prepend(Header.bar, [Header.boardList, Header.shortcuts]); Header.setCustomNav(Conf['Custom Board Navigation']); Header.generateBoardList(Conf['boardnav']); $.sync('Custom Board Navigation', Header.setCustomNav); return $.sync('boardnav', Header.generateBoardList); }, + generateFullBoardList: function() { + var a, fullBoardList, i, len, nodes, ref; + if (g.SITE.transformBoardList) { + nodes = g.SITE.transformBoardList(); + } else { + nodes = slice.call($(g.SITE.selectors.boardList).cloneNode(true).childNodes); + } + fullBoardList = $('.boardList', Header.boardList); + $.add(fullBoardList, nodes); + ref = $$('a', fullBoardList); + for (i = 0, len = ref.length; i < len; i++) { + a = ref[i]; + if (a.hostname === location.hostname && a.pathname.split('/')[1] === g.BOARD.ID) { + a.className = 'current'; + } + } + return CatalogLinks.setLinks(fullBoardList); + }, generateBoardList: function(boardnav) { - var as, list, nodes, re, t; + var list, nodes, re, t; list = $('#custom-board-list', Header.boardList); $.rmAll(list); if (!boardnav) { return; } boardnav = boardnav.replace(/(\r\n|\n|\r)/g, ' '); - as = $$('#full-board-list a[title]', Header.boardList); - re = /[\w@]+(-(all|title|replace|full|index|catalog|archive|expired|(mode|sort|text):"[^"]+"(,"[^"]+")?))*|[^\w@]+/g; + re = /[\w@]+(-(all|title|replace|full|index|catalog|archive|expired|nt|(mode|sort|text):"[^"]+"(,"[^"]+")?))*|[^\w@]+/g; nodes = (function() { var i, len, ref, results; ref = boardnav.match(re); results = []; for (i = 0, len = ref.length; i < len; i++) { t = ref[i]; - results.push(Header.mapCustomNavigation(t, as)); + results.push(Header.mapCustomNavigation(t)); } return results; })(); $.add(list, nodes); - return $.ready(CatalogLinks.initBoardList); + return CatalogLinks.setLinks(list); }, - mapCustomNavigation: function(t, as) { - var a, boardID, href, indexOptions, m, text, url; + mapCustomNavigation: function(t) { + var a, boardID, href, indexOptions, m, ref, ref1, text, url, urlIC; if (/^[^\w@]/.test(t)) { return $.tn(t); } @@ -8140,14 +10904,39 @@ Header = (function() { textContent: text || '+', className: 'external' }); + if (/-nt/.test(t)) { + a.target = '_blank'; + a.rel = 'noopener'; + } return a; } boardID = t.split('-')[0]; if (boardID === 'current') { - boardID = g.BOARD.ID; + if ((ref = location.hostname) === 'boards.4chan.org' || ref === 'boards.4channel.org') { + boardID = g.BOARD.ID; + } else { + a = $.el('a', { + href: "/" + g.BOARD.ID + "/", + textContent: text || decodeURIComponent(g.BOARD.ID), + className: 'current' + }); + if (/-nt/.test(t)) { + a.target = '_blank'; + a.rel = 'noopener'; + } + if (/-index/.test(t)) { + a.dataset.only = 'index'; + } else if (/-catalog/.test(t)) { + a.dataset.only = 'catalog'; + a.href += 'catalog.html'; + } else if (/-(archive|expired)/.test(t)) { + a = a.firstChild; + } + return a; + } } a = (function() { - var i, len, ref; + var ref1, urlV; if (boardID === '@') { return $.el('a', { href: 'https://twitter.com/4chan', @@ -8155,29 +10944,31 @@ Header = (function() { textContent: '@' }); } - for (i = 0, len = as.length; i < len; i++) { - a = as[i]; - if (a.textContent === boardID) { - return a.cloneNode(true); - } - } a = $.el('a', { - href: "/" + boardID + "/", - textContent: boardID + href: "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/", + textContent: boardID, + title: BoardConfig.title(boardID) }); - if ((ref = g.VIEW) === 'catalog' || ref === 'archive') { - a.href += g.VIEW; + if (((ref1 = g.VIEW) === 'catalog' || ref1 === 'archive') && (urlV = Get.url(g.VIEW, { + siteID: '4chan.org', + boardID: boardID + }))) { + a.href = urlV; } - if (boardID === g.BOARD.ID) { + if (a.hostname === location.hostname && boardID === g.BOARD.ID) { a.className = 'current'; } return a; })(); - a.textContent = /-title/.test(t) || /-replace/.test(t) && boardID === g.BOARD.ID ? a.title || a.textContent : /-full/.test(t) ? ("/" + boardID + "/") + (a.title ? " - " + a.title : '') : text || boardID; + a.textContent = /-title/.test(t) || /-replace/.test(t) && a.hostname === location.hostname && boardID === g.BOARD.ID ? a.title || a.textContent : /-full/.test(t) ? ("/" + boardID + "/") + (a.title ? " - " + a.title : '') : text || boardID; if (m = t.match(/-(index|catalog)/)) { - if (!(boardID === 'f' && m[1] === 'catalog')) { + urlIC = CatalogLinks[m[1]]({ + siteID: '4chan.org', + boardID: boardID + }); + if (urlIC) { a.dataset.only = m[1]; - a.href = CatalogLinks[m[1]](boardID); + a.href = urlIC; if (m[1] === 'catalog') { $.addClass(a, 'catalog'); } @@ -8187,7 +10978,7 @@ Header = (function() { } if (Conf['JSON Index'] && indexOptions) { a.dataset.indexOptions = indexOptions; - if (a.hostname === 'boards.4chan.org' && a.pathname.split('/')[2] === '') { + if (((ref1 = a.hostname) === 'boards.4chan.org' || ref1 === 'boards.4channel.org') && a.pathname.split('/')[2] === '') { a.href += (a.hash ? '/' : '#') + indexOptions; } } @@ -8201,12 +10992,16 @@ Header = (function() { } } if (/-expired/.test(t)) { - if (boardID !== 'b' && boardID !== 'f' && boardID !== 'trash') { - a.href = "/" + boardID + "/archive"; + if (BoardConfig.isArchived(boardID)) { + a.href = "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/archive"; } else { return a.firstChild; } } + if (/-nt/.test(t)) { + a.target = '_blank'; + a.rel = 'noopener'; + } if (boardID === '@') { $.addClass(a, 'navSmall'); } @@ -8289,9 +11084,7 @@ Header = (function() { } $.off(window, 'scroll', Header.hideBarOnScroll); $.rmClass(Header.bar, 'scroll'); - if (!Conf['Header auto-hide']) { - return $.rmClass(Header.bar, 'autohide'); - } + return Header.bar.classList.toggle('autohide', Conf['Header auto-hide']); }, toggleHideBarOnScroll: function() { var hide; @@ -8310,8 +11103,10 @@ Header = (function() { return Header.previousOffset = offsetY; }, setBarPosition: function(bottom) { - var args; - Header.barPositionToggler.checked = bottom; + var args, ref; + if ((ref = Header.barPositionToggler) != null) { + ref.checked = bottom; + } $.event('CloseMenu'); args = bottom ? ['bottom-header', 'top-header', 'after'] : ['top-header', 'bottom-header', 'add']; $.addClass(doc, args[0]); @@ -8495,9 +11290,7 @@ Header = (function() { case 'denied': return; } - el = $.el('span', { - innerHTML: "4chan X needs your permission to show desktop notifications. [FAQ]
or " - }); + el = $.el('span', {innerHTML: "4chan X needs your permission to show desktop notifications. [FAQ]
or "}); ref = $$('button', el), authorize = ref[0], disable = ref[1]; $.on(authorize, 'click', function() { return Notification.requestPermission(function(status) { @@ -8528,11 +11321,26 @@ Index = (function() { Index = { showHiddenThreads: false, changed: {}, + enabledOn: function(arg) { + var boardID, siteID; + siteID = arg.siteID, boardID = arg.boardID; + return Conf['JSON Index'] && g.sites[siteID].software === 'yotsuba' && boardID !== 'f'; + }, init: function() { - var anchorEntry, input, j, k, label, len, len1, name, pinEntry, ref, ref1, ref2, ref3, ref4, ref5, ref6, refNavEntry, repliesEntry, select, sortEntry; - if (g.BOARD.ID === 'f' || !Conf['JSON Index'] || g.VIEW !== 'index') { + var arr, entries, i, input, inputs, k, l, label, len1, len2, name, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, select, sortEntry, tRaw, watchSettings; + if (g.VIEW !== 'index') { return; } + $.one(d, '4chanXInitFinished', this.cb.initFinished); + $.on(d, 'PostsInserted', this.cb.postsInserted); + if (!this.enabledOn(g.BOARD)) { + return; + } + this.enabled = true; + Callbacks.Post.push({ + name: 'Index Page Numbers', + cb: this.node + }); Callbacks.CatalogThread.push({ name: 'Catalog Features', cb: this.catalogNode @@ -8547,7 +11355,8 @@ Index = (function() { this.processHash(); $.addClass(doc, 'index-loading', (Conf['Index Mode'].replace(/\ /g, '-')) + "-mode"); $.on(window, 'popstate', this.cb.popstate); - $.on(d, 'scroll', Index.scroll); + $.on(d, 'scroll', this.scroll); + $.on(d, 'SortIndex', this.cb.resort); this.button = $.el('a', { className: 'fa fa-refresh', title: 'Refresh', @@ -8558,56 +11367,55 @@ Index = (function() { return Index.update(); }); Header.addShortcut('index-refresh', this.button, 590); - repliesEntry = { - el: UI.checkbox('Show Replies', 'Show replies') - }; - sortEntry = { - el: UI.checkbox('Per-Board Sort Type', 'Per-board sort type', typeof Conf['Index Sort'] === 'object') - }; - pinEntry = { - el: UI.checkbox('Pin Watched Threads', 'Pin watched threads') - }; - anchorEntry = { - el: UI.checkbox('Anchor Hidden Threads', 'Anchor hidden threads') - }; - refNavEntry = { - el: UI.checkbox('Refreshed Navigation', 'Refreshed navigation') - }; - sortEntry.el.title = 'Set the sorting order of each board independently.'; - pinEntry.el.title = 'Move watched threads to the start of the index.'; - anchorEntry.el.title = 'Move hidden threads to the end of the index.'; - refNavEntry.el.title = 'Refresh index when navigating through pages.'; - ref4 = [repliesEntry, pinEntry, anchorEntry, refNavEntry]; - for (j = 0, len = ref4.length; j < len; j++) { - label = ref4[j]; - input = label.el.firstChild; - name = input.name; - $.on(input, 'change', $.cb.checked); - switch (name) { - case 'Show Replies': - $.on(input, 'change', this.cb.replies); - break; - case 'Pin Watched Threads': - case 'Anchor Hidden Threads': - $.on(input, 'change', this.cb.resort); + entries = []; + this.inputs = inputs = $.dict(); + ref4 = Config.Index; + for (name in ref4) { + arr = ref4[name]; + if (!(arr instanceof Array)) { + continue; } + label = UI.checkbox(name, "" + name[0] + (name.slice(1).toLowerCase())); + label.title = arr[1]; + entries.push({ + el: label + }); + input = label.firstChild; + $.on(input, 'change', $.cb.checked); + inputs[name] = input; } - $.on(sortEntry.el.firstChild, 'change', this.cb.perBoardSort); + $.on(inputs['Show Replies'], 'change', this.cb.replies); + $.on(inputs['Catalog Hover Expand'], 'change', this.cb.hover); + $.on(inputs['Pin Watched Threads'], 'change', this.cb.resort); + $.on(inputs['Anchor Hidden Threads'], 'change', this.cb.resort); + watchSettings = function(e) { + if ((input = $.getOwn(inputs, e.target.name))) { + input.checked = e.target.checked; + return $.event('change', null, input); + } + }; + $.on(d, 'OpenSettings', function() { + return $.on($.id('fourchanx-settings'), 'change', watchSettings); + }); + sortEntry = UI.checkbox('Per-Board Sort Type', 'Per-board sort type', typeof Conf['Index Sort'] === 'object'); + sortEntry.title = 'Set the sorting order of each board independently.'; + $.on(sortEntry.firstChild, 'change', this.cb.perBoardSort); + entries.splice(3, 0, { + el: sortEntry + }); Header.menu.addEntry({ el: $.el('span', { textContent: 'Index Navigation' }), order: 100, - subEntries: [repliesEntry, sortEntry, pinEntry, anchorEntry, refNavEntry] + subEntries: entries }); this.navLinks = $.el('div', { className: 'navLinks json-index' }); - $.extend(this.navLinks, { - innerHTML: "Index Catalog Archive Bottom ×" - }); + $.extend(this.navLinks, {innerHTML: "Index Catalog Archive Bottom ×"}); $('.cataloglink a', this.navLinks).href = CatalogLinks.catalog(); - if ((ref5 = g.BOARD.ID) === 'b' || ref5 === 'trash') { + if (!BoardConfig.isArchived(g.BOARD.ID)) { $('.archlistlink', this.navLinks).hidden = true; } $.on($('#index-last-refresh a', this.navLinks), 'click', this.cb.refreshFront); @@ -8617,29 +11425,43 @@ Index = (function() { $.on($('#index-search-clear', this.navLinks), 'click', this.clearSearch); this.hideLabel = $('#hidden-label', this.navLinks); $.on($('#hidden-toggle a', this.navLinks), 'click', this.cb.toggleHiddenThreads); + this.selectRev = $('#index-rev', this.navLinks); this.selectMode = $('#index-mode', this.navLinks); this.selectSort = $('#index-sort', this.navLinks); this.selectSize = $('#index-size', this.navLinks); + $.on(this.selectRev, 'change', this.cb.sort); $.on(this.selectMode, 'change', this.cb.mode); $.on(this.selectSort, 'change', this.cb.sort); $.on(this.selectSize, 'change', $.cb.value); $.on(this.selectSize, 'change', this.cb.size); - ref6 = [this.selectMode, this.selectSize]; - for (k = 0, len1 = ref6.length; k < len1; k++) { - select = ref6[k]; + ref5 = [this.selectMode, this.selectSize]; + for (k = 0, len1 = ref5.length; k < len1; k++) { + select = ref5[k]; select.value = Conf[select.name]; } - this.selectSort.value = Index.currentSort; + this.selectRev.checked = /-rev$/.test(Index.currentSort); + this.selectSort.value = Index.currentSort.replace(/-rev$/, ''); + this.lastLongOptions = $('#lastlong-options', this.navLinks); + this.lastLongInputs = $$('input', this.lastLongOptions); + this.lastLongThresholds = [0, 0]; + this.lastLongOptions.hidden = this.selectSort.value !== 'lastlong'; + ref6 = this.lastLongInputs; + for (i = l = 0, len2 = ref6.length; l < len2; i = ++l) { + input = ref6[i]; + $.on(input, 'change', this.cb.lastLongThresholds); + tRaw = Conf["Last Long Reply Thresholds " + i]; + input.value = this.lastLongThresholds[i] = typeof tRaw === 'object' ? (ref7 = tRaw[g.BOARD.ID]) != null ? ref7 : 100 : tRaw; + } this.root = $.el('div', { className: 'board json-index' }); + $.on(this.root, 'click', this.cb.hoverToggle); this.cb.size(); + this.cb.hover(); this.pagelist = $.el('div', { className: 'pagelist json-index' }); - $.extend(this.pagelist, { - innerHTML: "
" - }); + $.extend(this.pagelist, {innerHTML: "
"}); $('.cataloglink a', this.pagelist).href = CatalogLinks.catalog(); $.on(this.pagelist, 'click', this.cb.pageNav); this.update(true); @@ -8647,25 +11469,25 @@ Index = (function() { return d.title = d.title.replace(/\ -\ Page\ \d+/, ''); }); $.onExists(doc, '.board > .thread > .postContainer, .board + *', function() { - var board, el, l, len2, len3, m, ref7, ref8, threadRoot, topNavPos; - Index.hat = $('.board > .thread > img:first-child'); - if (Index.hat) { - if (Index.nodes) { - ref7 = Index.nodes; - for (l = 0, len2 = ref7.length; l < len2; l++) { - threadRoot = ref7[l]; - $.prepend(threadRoot, Index.hat.cloneNode(false)); + var board, el, len3, m, ref8, timeEl, topNavPos; + g.SITE.Build.hat = $('.board > .thread > img:first-child'); + if (g.SITE.Build.hat) { + g.BOARD.threads.forEach(function(thread) { + if (thread.nodes.root) { + return $.prepend(thread.nodes.root, g.SITE.Build.hat.cloneNode(false)); } - } + }); $.addClass(doc, 'hats-enabled'); - $.addStyle(".catalog-thread::after {background-image: url(" + Index.hat.src + ");}"); + $.addStyle(".catalog-thread::after {background-image: url(" + g.SITE.Build.hat.src + ");}"); } board = $('.board'); $.replace(board, Index.root); - $.event('PostsInserted'); + if (Index.loaded) { + $.event('PostsInserted', null, Index.root); + } try { d.implementation.createDocument(null, null, null).appendChild(board); - } catch (_error) {} + } catch (error) {} ref8 = $$('.navLinks'); for (m = 0, len3 = ref8.length; m < len3; m++) { el = ref8[m]; @@ -8674,7 +11496,11 @@ Index = (function() { $.rm($.id('ctrl-top')); topNavPos = $.id('delform').previousElementSibling; $.before(topNavPos, $.el('hr')); - return $.before(topNavPos, Index.navLinks); + $.before(topNavPos, Index.navLinks); + timeEl = $('#index-last-refresh time', Index.navLinks); + if (timeEl.dataset.utc) { + return RelativeDates.update(timeEl); + } }); return Main.ready(function() { var pagelist; @@ -8685,7 +11511,7 @@ Index = (function() { }); }, scroll: function() { - var nodes, pageNum; + var pageNum, threadIDs; if (Index.req || !Index.liveThreadData || Conf['Index Mode'] !== 'infinite' || (window.scrollY <= doc.scrollHeight - (300 + window.innerHeight))) { return; } @@ -8696,11 +11522,8 @@ Index = (function() { if (pageNum > Index.pagesNum) { return Index.endNotice(); } - nodes = Index.buildSinglePage(pageNum); - if (Conf['Show Replies']) { - Index.buildReplies(nodes); - } - return Index.buildStructure(nodes); + threadIDs = Index.threadsOnPage(pageNum); + return Index.buildStructure(threadIDs); }, endNotice: (function() { var notify, reset; @@ -8719,16 +11542,14 @@ Index = (function() { })(), menu: { init: function() { - if (g.VIEW !== 'index' || !Conf['JSON Index'] || !Conf['Menu'] || !Conf['Thread Hiding Link'] || g.BOARD.ID === 'f') { + if (!(g.VIEW === 'index' && Conf['Menu'] && Conf['Thread Hiding Link'] && Index.enabledOn(g.BOARD))) { return; } return Menu.menu.addEntry({ el: $.el('a', { href: 'javascript:;', className: 'has-shortcut-text' - }, { - innerHTML: "Shift+click" - }), + }, {innerHTML: "Shift+click"}), order: 20, open: function(arg) { var thread; @@ -8750,24 +11571,26 @@ Index = (function() { }); } }, - catalogNode: function() { - return $.on(this.nodes.thumb.parentNode, 'click', Index.onClick); - }, - onClick: function(e) { - var thread; - if (e.button !== 0) { - return; - } - thread = g.threads[this.parentNode.dataset.fullID]; - if (e.shiftKey) { - Index.toggleHide(thread); - } else { + node: function() { + if (this.isReply || this.isClone || !(Index.threadPosition[this.ID] != null)) { return; } - return e.preventDefault(); + return this.thread.setPage(Math.floor(Index.threadPosition[this.ID] / Index.threadsNumPerPage) + 1); + }, + catalogNode: function() { + return $.on(this.nodes.root, 'mousedown click', (function(_this) { + return function(e) { + if (!(e.button === 0 && e.shiftKey)) { + return; + } + if (e.type === 'click') { + Index.toggleHide(_this.thread); + } + return e.preventDefault(); + }; + })(this)); }, toggleHide: function(thread) { - $.rm(thread.catalogView.nodes.root); if (Index.showHiddenThreads) { ThreadHiding.show(thread); if (!ThreadHiding.db.get({ @@ -8782,11 +11605,11 @@ Index = (function() { return ThreadHiding.saveHiddenState(thread); }, cycleSortType: function() { - var i, j, len, type, types; + var i, k, len1, type, types; types = slice.call(Index.selectSort.options).filter(function(option) { return !option.disabled; }); - for (i = j = 0, len = types.length; j < len; i = ++j) { + for (i = k = 0, len1 = types.length; k < len1; i = ++k) { type = types[i]; if (type.selected) { break; @@ -8796,6 +11619,28 @@ Index = (function() { return $.event('change', null, Index.selectSort); }, cb: { + initFinished: function() { + Index.initFinishedFired = true; + return $.queueTask(function() { + return Index.cb.postsInserted(); + }); + }, + postsInserted: function() { + var n; + if (!Index.initFinishedFired) { + return; + } + n = 0; + g.posts.forEach(function(post) { + if (!post.isFetchedQuote && !post.indexRefreshSeen && doc.contains(post.nodes.root)) { + post.indexRefreshSeen = true; + return n++; + } + }); + if (n) { + return $.event('IndexRefresh'); + } + }, toggleHiddenThreads: function() { $('#hidden-toggle a', Index.navLinks).textContent = (Index.showHiddenThreads = !Index.showHiddenThreads) ? 'Hide' : 'Show'; Index.sort(); @@ -8808,18 +11653,41 @@ Index = (function() { return Index.pageLoad(false); }, sort: function() { + var value; + value = Index.selectRev.checked ? Index.selectSort.value + "-rev" : Index.selectSort.value; Index.pushState({ - sort: this.value + sort: value }); return Index.pageLoad(false); }, - resort: function() { - Index.sort(); - return Index.buildIndex(); + resort: function(e) { + var ref; + Index.changed.order = true; + if (!(e != null ? (ref = e.detail) != null ? ref.deferred : void 0 : void 0)) { + return Index.pageLoad(false); + } }, perBoardSort: function() { - Conf['Index Sort'] = this.checked ? {} : ''; - return Index.saveSort(); + var i, k; + Conf['Index Sort'] = this.checked ? $.dict() : ''; + Index.saveSort(); + for (i = k = 0; k < 2; i = ++k) { + Conf["Last Long Reply Thresholds " + i] = this.checked ? $.dict() : ''; + Index.saveLastLongThresholds(i); + } + }, + lastLongThresholds: function() { + var i, value; + i = slice.call(this.parentNode.children).indexOf(this); + value = +this.value; + if (!Number.isFinite(value)) { + this.value = Index.lastLongThresholds[i]; + return; + } + Index.lastLongThresholds[i] = value; + Index.saveLastLongThresholds(i); + Index.changed.order = true; + return Index.pageLoad(false); }, size: function(e) { if (Conf['Index Mode'] !== 'catalog') { @@ -8837,10 +11705,23 @@ Index = (function() { } }, replies: function() { - Index.buildThreads(); - Index.sort(); return Index.buildIndex(); }, + hover: function() { + return doc.classList.toggle('catalog-hover-expand', Conf['Catalog Hover Expand']); + }, + hoverToggle: function(e) { + var input, thread; + if (Conf['Catalog Hover Toggle'] && $.hasClass(doc, 'catalog-mode') && !$.modifiedClick(e) && !$.x('ancestor-or-self::a', e.target)) { + input = Index.inputs['Catalog Hover Expand']; + input.checked = !input.checked; + $.event('change', null, input); + if ((thread = Get.threadFromNode(e.target))) { + Index.cb.catalogReplies.call(thread); + return Index.cb.hoverAdjust.call(thread.OP.nodes); + } + } + }, popstate: function(e) { var mode, nCommands, page, ref, searched, sort; if (e != null ? e.state : void 0) { @@ -8864,7 +11745,7 @@ Index = (function() { }, pageNav: function(e) { var a; - if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { + if ($.modifiedClick(e)) { return; } switch (e.target.nodeName) { @@ -8889,6 +11770,26 @@ Index = (function() { page: 1 }); return Index.update(); + }, + catalogReplies: function() { + if (Conf['Show Replies'] && $.hasClass(doc, 'catalog-hover-expand') && !this.catalogView.nodes.replies) { + return Index.buildCatalogReplies(this); + } + }, + hoverAdjust: function() { + var rect, style, x; + if (!$.hasClass(doc, 'catalog-hover-expand')) { + return; + } + rect = this.post.getBoundingClientRect(); + if ((x = $.minmax(0, -rect.left, doc.clientWidth - rect.right))) { + style = this.post.style; + style.left = x + "px"; + style.right = (-x) + "px"; + return $.one(this.root, 'mouseleave', function() { + return style.left = style.right = null; + }); + } } }, scrollToIndex: function() { @@ -8922,26 +11823,30 @@ Index = (function() { 'last-long-reply': 'lastlong', 'creation-date': 'birth', 'reply-count': 'replycount', - 'file-count': 'filecount' + 'file-count': 'filecount', + 'posts-per-minute': 'activity' } }, processHash: function() { - var command, commands, hash, j, leftover, len, mode, ref, sort, state; + var command, commands, hash, k, leftover, len1, mode, ref, sort, state; hash = ((ref = location.href.match(/#.*/)) != null ? ref[0] : void 0) || ''; state = { replace: true }; commands = hash.slice(1).split('/'); leftover = []; - for (j = 0, len = commands.length; j < len; j++) { - command = commands[j]; - if ((mode = Index.hashCommands.mode[command])) { + for (k = 0, len1 = commands.length; k < len1; k++) { + command = commands[k]; + if ((mode = $.getOwn(Index.hashCommands.mode, command))) { state.mode = mode; } else if (command === 'index') { state.mode = Conf['Previous Index Mode']; state.page = 1; - } else if ((sort = Index.hashCommands.sort[command])) { + } else if ((sort = $.getOwn(Index.hashCommands.sort, command.replace(/-rev$/, '')))) { state.sort = sort; + if (/-rev$/.test(command)) { + state.sort += '-rev'; + } } else if (/^s=/.test(command)) { state.search = decodeURIComponent(command.slice(2)).replace(/\+/g, ' ').trim(); } else { @@ -9009,27 +11914,35 @@ Index = (function() { return Index.changed.hash = true; } }, - saveSort: function() { - if (typeof Conf['Index Sort'] === 'object') { - Conf['Index Sort'][g.BOARD.ID] = Index.currentSort; + savePerBoard: function(key, value) { + if (typeof Conf[key] === 'object') { + Conf[key][g.BOARD.ID] = value; } else { - Conf['Index Sort'] = Index.currentSort; + Conf[key] = value; } - return $.set('Index Sort', Conf['Index Sort']); + return $.set(key, Conf[key]); + }, + saveSort: function() { + return Index.savePerBoard('Index Sort', Index.currentSort); + }, + saveLastLongThresholds: function(i) { + return Index.savePerBoard("Last Long Reply Thresholds " + i, Index.lastLongThresholds[i]); }, pageLoad: function(scroll) { - var hash, mode, page, ref, search, sort, threads; + var hash, mode, order, page, ref, search, sort, threads; if (scroll == null) { scroll = true; } if (!Index.liveThreadData) { return; } - ref = Index.changed, threads = ref.threads, search = ref.search, mode = ref.mode, sort = ref.sort, page = ref.page, hash = ref.hash; - if (threads || search || sort) { + ref = Index.changed, threads = ref.threads, order = ref.order, search = ref.search, mode = ref.mode, sort = ref.sort, page = ref.page, hash = ref.hash; + threads || (threads = search); + order || (order = sort); + if (threads || order) { Index.sort(); } - if (threads || search) { + if (threads) { Index.buildPagelist(); } if (search) { @@ -9041,10 +11954,10 @@ Index = (function() { if (sort) { Index.setupSort(); } - if (threads || search || mode || page || sort) { + if (threads || mode || page || order) { Index.buildIndex(); } - if (threads || search || mode || page) { + if (threads || page) { Index.setPage(); } if (scroll && !hash) { @@ -9056,10 +11969,10 @@ Index = (function() { return Index.changed = {}; }, setupMode: function() { - var j, len, mode, ref; + var k, len1, mode, ref; ref = ['paged', 'infinite', 'all pages', 'catalog']; - for (j = 0, len = ref.length; j < len; j++) { - mode = ref[j]; + for (k = 0, len1 = ref.length; k < len1; k++) { + mode = ref[k]; $[mode === Conf['Index Mode'] ? 'addClass' : 'rmClass'](doc, (mode.replace(/\ /g, '-')) + "-mode"); } Index.selectMode.value = Conf['Index Mode']; @@ -9068,11 +11981,13 @@ Index = (function() { return $('#hidden-toggle a', Index.navLinks).textContent = 'Show'; }, setupSort: function() { - return Index.selectSort.value = Index.currentSort; + Index.selectRev.checked = /-rev$/.test(Index.currentSort); + Index.selectSort.value = Index.currentSort.replace(/-rev$/, ''); + return Index.lastLongOptions.hidden = Index.selectSort.value !== 'lastlong'; }, getPagesNum: function() { if (Index.search) { - return Math.ceil(Index.sortedNodes.length / Index.threadsNumPerPage); + return Math.ceil(Index.sortedThreadIDs.length / Index.threadsNumPerPage); } else { return Index.pagesNum; } @@ -9081,12 +11996,12 @@ Index = (function() { return Math.max(1, Index.getPagesNum()); }, buildPagelist: function() { - var a, i, j, maxPageNum, nodes, pagesRoot, ref; + var a, i, k, maxPageNum, nodes, pagesRoot, ref; pagesRoot = $('.pages', Index.pagelist); maxPageNum = Index.getMaxPageNum(); if (pagesRoot.childElementCount !== maxPageNum) { nodes = []; - for (i = j = 1, ref = maxPageNum; j <= ref; i = j += 1) { + for (i = k = 1, ref = maxPageNum; k <= ref; i = k += 1) { a = $.el('a', { textContent: i, href: i === 1 ? './' : i @@ -9118,20 +12033,22 @@ Index = (function() { } else { strong = $.el('strong'); } - a = pagesRoot.children[pageNum - 1]; - $.before(a, strong); - return $.add(strong, a); + if ((a = pagesRoot.children[pageNum - 1])) { + $.before(a, strong); + return $.add(strong, a); + } }, updateHideLabel: function() { - var hiddenCount, ref, ref1, thread, threadID; + var hiddenCount, k, len1, ref, threadID; + if (!Index.hideLabel) { + return; + } hiddenCount = 0; - ref = g.BOARD.threads; - for (threadID in ref) { - thread = ref[threadID]; - if (thread.isHidden) { - if (ref1 = thread.ID, indexOf.call(Index.liveThreadIDs, ref1) >= 0) { - hiddenCount++; - } + ref = Index.liveThreadIDs; + for (k = 0, len1 = ref.length; k < len1; k++) { + threadID = ref[k]; + if (Index.isHidden(threadID)) { + hiddenCount++; } } if (!hiddenCount) { @@ -9145,56 +12062,46 @@ Index = (function() { return $('#hidden-count', Index.navLinks).textContent = hiddenCount === 1 ? '1 hidden thread' : hiddenCount + " hidden threads"; }, update: function(firstTime) { - var now, ref, ref1; - if ((ref = Index.req) != null) { - ref.abort(); - } - if ((ref1 = Index.notice) != null) { - ref1.close(); - } - if (Conf['Index Refresh Notifications'] && d.readyState !== 'loading') { - Index.notice = new Notice('info', 'Refreshing index...'); + var oldReq; + if ((oldReq = Index.req)) { + delete Index.req; + oldReq.abort(); + } + if (Conf['Index Refresh Notifications']) { + Index.notice || (Index.notice = new Notice('info', 'Refreshing index...')); + Index.nTimeout || (Index.nTimeout = setTimeout(function() { + var ref; + return (ref = Index.notice) != null ? ref.el.lastElementChild.textContent += ' (disable JSON Index if this takes too long)' : void 0; + }, 3 * $.SECOND)); } else { - now = Date.now(); - $.ready(function() { - return Index.nTimeout = setTimeout((function() { - if (Index.req && !Index.notice) { - return Index.notice = new Notice('info', 'Refreshing index...'); - } - }), 3 * $.SECOND - (Date.now() - now)); - }); + Index.nTimeout || (Index.nTimeout = setTimeout(function() { + return Index.notice || (Index.notice = new Notice('info', 'Refreshing index... (disable JSON Index if this takes too long)')); + }, 3 * $.SECOND)); } if (!firstTime && d.readyState !== 'loading' && !$('.board + *')) { location.reload(); return; } - Index.req = $.ajax("//a.4cdn.org/" + g.BOARD + "/catalog.json", { - onabort: Index.load, - onloadend: Index.load - }, { - whenModified: 'Index' - }); + Index.req = $.whenModified(g.SITE.urls.catalogJSON({ + boardID: g.BOARD.ID + }), 'Index', Index.load); return $.addClass(Index.button, 'fa-spin'); }, - load: function(e) { - var err, nTimeout, notice, ref, req, timeEl; + load: function() { + var err, nTimeout, notice, ref, timeEl; + if (this !== Index.req) { + return; + } $.rmClass(Index.button, 'fa-spin'); - req = Index.req, notice = Index.notice, nTimeout = Index.nTimeout; + notice = Index.notice, nTimeout = Index.nTimeout; if (nTimeout) { clearTimeout(nTimeout); } delete Index.nTimeout; delete Index.req; delete Index.notice; - if (e.type === 'abort') { - req.onloadend = null; - if (notice != null) { - notice.close(); - } - return; - } - if ((ref = req.status) !== 200 && ref !== 304) { - err = "Index refresh failed. " + (req.status ? "Error " + req.statusText + " (" + req.status + ")" : 'Connection Error'); + if ((ref = this.status) !== 200 && ref !== 304) { + err = "Index refresh failed. " + (this.status ? "Error " + this.statusText + " (" + this.status + ")" : 'Connection Error'); if (notice) { notice.setType('warning'); notice.el.lastElementChild.textContent = err; @@ -9205,13 +12112,13 @@ Index = (function() { return; } try { - if (req.status === 200) { - Index.parse(req.response); - } else if (req.status === 304) { + if (this.status === 200) { + Index.parse(this.response); + } else if (this.status === 304) { Index.pageLoad(); } - } catch (_error) { - err = _error; + } catch (error) { + err = error; c.error("Index failure: " + err.message, err.stack); if (notice) { notice.setType('error'); @@ -9232,20 +12139,19 @@ Index = (function() { } } timeEl = $('#index-last-refresh time', Index.navLinks); - timeEl.dataset.utc = Date.parse(req.getResponseHeader('Last-Modified')); + timeEl.dataset.utc = Date.parse(this.getResponseHeader('Last-Modified')); return RelativeDates.update(timeEl); }, parse: function(pages) { $.cleanCache(function(url) { - return /^\/\/a\.4cdn\.org\//.test(url); + return /^https?:\/\/a\.4cdn\.org\//.test(url); }); Index.parseThreadList(pages); - Index.buildThreads(); Index.changed.threads = true; return Index.pageLoad(); }, parseThreadList: function(pages) { - var ref; + var ID, data, i, k, l, len1, len2, obj, ref, ref1, ref2, reply, results; Index.pagesNum = pages.length; Index.threadsNumPerPage = ((ref = pages[0]) != null ? ref.threads.length : void 0) || 1; Index.liveThreadData = pages.reduce((function(arr, next) { @@ -9254,126 +12160,197 @@ Index = (function() { Index.liveThreadIDs = Index.liveThreadData.map(function(data) { return data.no; }); + Index.liveThreadDict = $.dict(); + Index.threadPosition = $.dict(); + Index.parsedThreads = $.dict(); + Index.replyData = $.dict(); + ref1 = Index.liveThreadData; + for (i = k = 0, len1 = ref1.length; k < len1; i = ++k) { + data = ref1[i]; + Index.liveThreadDict[data.no] = data; + Index.threadPosition[data.no] = i; + Index.parsedThreads[data.no] = obj = g.SITE.Build.parseJSON(data, g.BOARD); + obj.filterResults = results = Filter.test(obj); + obj.isOnTop = results.top; + obj.isHidden = results.hide || ThreadHiding.isHidden(obj.boardID, obj.threadID); + if (data.last_replies) { + ref2 = data.last_replies; + for (l = 0, len2 = ref2.length; l < len2; l++) { + reply = ref2[l]; + Index.replyData[g.BOARD + "." + reply.no] = reply; + } + } + } + if (Index.liveThreadData[0]) { + g.SITE.Build.spoilerRange[g.BOARD.ID] = Index.liveThreadData[0].custom_spoiler; + } g.BOARD.threads.forEach(function(thread) { - var ref1; - if (ref1 = thread.ID, indexOf.call(Index.liveThreadIDs, ref1) < 0) { + var ref3; + if (ref3 = thread.ID, indexOf.call(Index.liveThreadIDs, ref3) < 0) { return thread.collect(); } }); + $.event('IndexUpdate', { + threads: (function() { + var len3, m, ref3, results1; + ref3 = Index.liveThreadIDs; + results1 = []; + for (m = 0, len3 = ref3.length; m < len3; m++) { + ID = ref3[m]; + results1.push(g.BOARD + "." + ID); + } + return results1; + })() + }); }, - buildThreads: function() { - var err, errors, i, j, len, posts, ref, thread, threadData, threadRoot, threads; - if (!Index.liveThreadData) { - return; + isHidden: function(threadID) { + var thread; + if ((thread = g.BOARD.threads.get(threadID)) && thread.OP && !thread.OP.isFetchedQuote) { + return thread.isHidden; + } else { + return Index.parsedThreads[threadID].isHidden; } - Index.nodes = []; + }, + isHiddenReply: function(threadID, replyData) { + return PostHiding.isHidden(g.BOARD.ID, threadID, replyData.no) || Filter.isHidden(g.SITE.Build.parseJSON(replyData, g.BOARD)); + }, + buildThreads: function(threadIDs, isCatalog, withReplies) { + var ID, OP, err, errors, isStale, k, lastPost, len1, newPosts, newThreads, obj, opRoot, t, thread, threadData, threads; threads = []; - posts = []; - ref = Index.liveThreadData; - for (i = j = 0, len = ref.length; j < len; i = ++j) { - threadData = ref[i]; + newThreads = []; + newPosts = []; + for (k = 0, len1 = threadIDs.length; k < len1; k++) { + ID = threadIDs[k]; try { - threadRoot = Build.thread(g.BOARD, threadData); - if (Index.hat) { - $.prepend(threadRoot, Index.hat.cloneNode(false)); - } - if (thread = g.BOARD.threads[threadData.no]) { - thread.setCount('post', threadData.replies + 1, threadData.bumplimit); - thread.setCount('file', threadData.images + !!threadData.ext, threadData.imagelimit); - thread.setStatus('Sticky', !!threadData.sticky); - thread.setStatus('Closed', !!threadData.closed); + threadData = Index.liveThreadDict[ID]; + if ((thread = g.BOARD.threads.get(ID))) { + isStale = (thread.json !== threadData) && (JSON.stringify(thread.json) !== JSON.stringify(threadData)); + if (isStale) { + thread.setCount('post', threadData.replies + 1, threadData.bumplimit); + thread.setCount('file', threadData.images + !!threadData.ext, threadData.imagelimit); + thread.setStatus('Sticky', !!threadData.sticky); + thread.setStatus('Closed', !!threadData.closed); + } + if (thread.catalogView) { + $.rm(thread.catalogView.nodes.replies); + thread.catalogView.nodes.replies = null; + } } else { - thread = new Thread(threadData.no, g.BOARD); - threads.push(thread); + thread = new Thread(ID, g.BOARD); + newThreads.push(thread); + } + lastPost = threadData.last_replies && threadData.last_replies.length ? threadData.last_replies[threadData.last_replies.length - 1].no : ID; + if (lastPost > thread.lastPost) { + thread.lastPost = lastPost; + } + thread.json = threadData; + threads.push(thread); + if ((OP = thread.OP) && !OP.isFetchedQuote) { + OP.setCatalogOP(isCatalog); + thread.setPage(Math.floor(Index.threadPosition[ID] / Index.threadsNumPerPage) + 1); + } else { + obj = Index.parsedThreads[ID]; + opRoot = g.SITE.Build.post(obj); + OP = new Post(opRoot, thread, g.BOARD); + OP.filterResults = obj.filterResults; + newPosts.push(OP); } - Index.nodes.push(threadRoot); - if (!(thread.OP && !thread.OP.isFetchedQuote)) { - posts.push(new Post($('.opContainer', threadRoot), thread, g.BOARD)); + if (!(isCatalog && thread.nodes.root)) { + g.SITE.Build.thread(thread, threadData, withReplies); } - thread.setPage(Math.floor(i / Index.threadsNumPerPage) + 1); - } catch (_error) { - err = _error; + } catch (error) { + err = error; if (!errors) { errors = []; } errors.push({ message: "Parsing of Thread No." + thread + " failed. Thread will be skipped.", - error: err + error: err, + html: opRoot != null ? opRoot.outerHTML : void 0 }); } } if (errors) { Main.handleErrors(errors); } - $.nodes(Index.nodes); - Main.callbackNodes('Thread', threads); - Main.callbackNodes('Post', posts); + if (withReplies) { + newPosts = newPosts.concat(Index.buildReplies(threads)); + } + Main.callbackNodes('Thread', newThreads); + Main.callbackNodes('Post', newPosts); Index.updateHideLabel(); - return $.event('IndexRefresh'); + $.event('IndexRefreshInternal', { + threadIDs: (function() { + var l, len2, results1; + results1 = []; + for (l = 0, len2 = threads.length; l < len2; l++) { + t = threads[l]; + results1.push(t.fullID); + } + return results1; + })(), + isCatalog: isCatalog + }); + return threads; }, - buildReplies: function(threadRoots) { - var data, err, errors, i, j, k, lastReplies, len, len1, node, nodes, post, posts, thread, threadRoot; + buildReplies: function(threads) { + var data, err, errors, k, l, lastReplies, len1, len2, node, nodes, post, posts, thread; posts = []; - for (j = 0, len = threadRoots.length; j < len; j++) { - threadRoot = threadRoots[j]; - thread = Get.threadFromRoot(threadRoot); - i = Index.liveThreadIDs.indexOf(thread.ID); - if (!(lastReplies = Index.liveThreadData[i].last_replies)) { + for (k = 0, len1 = threads.length; k < len1; k++) { + thread = threads[k]; + if (!(lastReplies = Index.liveThreadDict[thread.ID].last_replies)) { continue; } nodes = []; - for (k = 0, len1 = lastReplies.length; k < len1; k++) { - data = lastReplies[k]; - if ((post = thread.posts[data.no]) && !post.isFetchedQuote) { + for (l = 0, len2 = lastReplies.length; l < len2; l++) { + data = lastReplies[l]; + if ((post = thread.posts.get(data.no)) && !post.isFetchedQuote) { nodes.push(post.nodes.root); continue; } - nodes.push(node = Build.postFromObject(data, thread.board.ID)); + nodes.push(node = g.SITE.Build.postFromObject(data, thread.board.ID)); try { posts.push(new Post(node, thread, thread.board)); - } catch (_error) { - err = _error; + } catch (error) { + err = error; if (!errors) { errors = []; } errors.push({ message: "Parsing of Post No." + data.no + " failed. Post will be skipped.", - error: err + error: err, + html: node != null ? node.outerHTML : void 0 }); } } - $.add(threadRoot, nodes); + $.add(thread.nodes.root, nodes); } if (errors) { Main.handleErrors(errors); } - return Main.callbackNodes('Post', posts); + return posts; }, - buildCatalogViews: function() { - var catalogThreads, j, len, thread, threads; - threads = Index.sortedNodes.map(function(threadRoot) { - return Get.threadFromRoot(threadRoot); - }).filter(function(thread) { - return !thread.isHidden !== Index.showHiddenThreads; - }); + buildCatalogViews: function(threads) { + var ID, catalogThreads, k, len1, page, root, thread; catalogThreads = []; - for (j = 0, len = threads.length; j < len; j++) { - thread = threads[j]; - if (!thread.catalogView) { - catalogThreads.push(new CatalogThread(Build.catalogThread(thread), thread)); + for (k = 0, len1 = threads.length; k < len1; k++) { + thread = threads[k]; + if (!(!thread.catalogView)) { + continue; } + ID = thread.ID; + page = Math.floor(Index.threadPosition[ID] / Index.threadsNumPerPage) + 1; + root = g.SITE.Build.catalogThread(thread, Index.liveThreadDict[ID], page); + catalogThreads.push(new CatalogThread(root, thread)); } Main.callbackNodes('CatalogThread', catalogThreads); - return threads.map(function(thread) { - return thread.catalogView.nodes.root; - }); }, - sizeCatalogViews: function(nodes) { - var height, j, len, node, ratio, ref, size, thumb, width; + sizeCatalogViews: function(threads) { + var height, k, len1, ratio, ref, size, thread, thumb, width; size = Conf['Index Size'] === 'small' ? 150 : 250; - for (j = 0, len = nodes.length; j < len; j++) { - node = nodes[j]; - thumb = $('.catalog-thumb', node); + for (k = 0, len1 = threads.length; k < len1; k++) { + thread = threads[k]; + thumb = thread.catalogView.nodes.thumb; ref = thumb.dataset, width = ref.width, height = ref.height; if (!width) { continue; @@ -9383,41 +12360,78 @@ Index = (function() { thumb.style.height = height * ratio + 'px'; } }, + buildCatalogReplies: function(thread) { + var data, k, lastReplies, len1, nodes, replies, reply; + nodes = thread.catalogView.nodes; + if (!(lastReplies = Index.liveThreadDict[thread.ID].last_replies)) { + return; + } + replies = []; + for (k = 0, len1 = lastReplies.length; k < len1; k++) { + data = lastReplies[k]; + if (Index.isHiddenReply(thread.ID, data)) { + continue; + } + reply = g.SITE.Build.catalogReply(thread, data); + RelativeDates.update($('time', reply)); + $.on($('.catalog-reply-preview', reply), 'mouseover', QuotePreview.mouseover); + replies.push(reply); + } + nodes.replies = $.el('div', { + className: 'catalog-replies' + }); + $.add(nodes.replies, replies); + $.add(thread.OP.nodes.post, nodes.replies); + }, sort: function() { - var j, lastlong, len, liveThreadData, liveThreadIDs, nodes, sortedNodes, sortedThreadIDs, threadID; + var lastlong, lastlongD, liveThreadData, liveThreadIDs, repliesAvailable, sortType, thread, threadIDs, tmp_time; liveThreadIDs = Index.liveThreadIDs, liveThreadData = Index.liveThreadData; if (!liveThreadData) { return; } - sortedThreadIDs = (function() { - switch (Index.currentSort) { + tmp_time = new Date().getTime() / 1000; + sortType = Index.currentSort.replace(/-rev$/, ''); + Index.sortedThreadIDs = (function() { + var k, len1; + switch (sortType) { case 'lastreply': - return slice.call(liveThreadData).sort(function(a, b) { - var num; - if ((num = a.last_replies)) { - a = num[num.length - 1]; - } - if ((num = b.last_replies)) { - b = num[num.length - 1]; - } - return b.no - a.no; - }).map(function(post) { - return post.no; - }); case 'lastlong': + repliesAvailable = liveThreadData.some(function(thread) { + var ref; + return (ref = thread.last_replies) != null ? ref.length : void 0; + }); lastlong = function(thread) { - var i, j, r, ref; + var i, k, len, r, ref, ref1; + if (!repliesAvailable) { + return thread.last_modified; + } ref = thread.last_replies || []; - for (i = j = ref.length - 1; j >= 0; i = j += -1) { + for (i = k = ref.length - 1; k >= 0; i = k += -1) { r = ref[i]; - if (r.com && Build.parseComment(r.com).replace(/[^a-z]/ig, '').length >= 100) { + if (Index.isHiddenReply(thread.no, r)) { + continue; + } + if (sortType === 'lastreply') { return r; } + len = r.com ? g.SITE.Build.parseComment(r.com).replace(/[^a-z]/ig, '').length : 0; + if (len >= Index.lastLongThresholds[+(!!r.ext)]) { + return r; + } + } + if (thread.omitted_posts && ((ref1 = thread.last_replies) != null ? ref1.length : void 0)) { + return thread.last_replies[0]; + } else { + return thread; } - return thread; }; + lastlongD = $.dict(); + for (k = 0, len1 = liveThreadData.length; k < len1; k++) { + thread = liveThreadData[k]; + lastlongD[thread.no] = lastlong(thread).no; + } return slice.call(liveThreadData).sort(function(a, b) { - return lastlong(b).no - lastlong(a).no; + return lastlongD[b.no] - lastlongD[a.no]; }).map(function(post) { return post.no; }); @@ -9439,105 +12453,134 @@ Index = (function() { }).map(function(post) { return post.no; }); + case 'activity': + return slice.call(liveThreadData).sort(function(a, b) { + return (tmp_time - a.time) / (a.replies + 1) - (tmp_time - b.time) / (b.replies + 1); + }).map(function(post) { + return post.no; + }); + default: + return liveThreadIDs; } })(); - Index.sortedNodes = sortedNodes = []; - nodes = Index.nodes; - for (j = 0, len = sortedThreadIDs.length; j < len; j++) { - threadID = sortedThreadIDs[j]; - sortedNodes.push(nodes[Index.liveThreadIDs.indexOf(threadID)]); + if (/-rev$/.test(Index.currentSort)) { + Index.sortedThreadIDs = slice.call(Index.sortedThreadIDs).reverse(); } - if (Index.search && (nodes = Index.querySearch(Index.search))) { - Index.sortedNodes = nodes; + if (Index.search && (threadIDs = Index.querySearch(Index.search))) { + Index.sortedThreadIDs = threadIDs; } - Index.sortOnTop(function(thread) { - return thread.isSticky; + Index.sortOnTop(function(obj) { + return obj.isSticky; }); - Index.sortOnTop(function(thread) { - return thread.isOnTop || Conf['Pin Watched Threads'] && ThreadWatcher.isWatched(thread); + Index.sortOnTop(function(obj) { + return obj.isOnTop || Conf['Pin Watched Threads'] && ThreadWatcher.isWatchedRaw(obj.boardID, obj.threadID); }); if (Conf['Anchor Hidden Threads']) { - return Index.sortOnTop(function(thread) { - return !thread.isHidden; + return Index.sortOnTop(function(obj) { + return !Index.isHidden(obj.threadID); }); } }, sortOnTop: function(match) { - var bottomNodes, j, len, ref, threadRoot, topNodes; - topNodes = []; - bottomNodes = []; - ref = Index.sortedNodes; - for (j = 0, len = ref.length; j < len; j++) { - threadRoot = ref[j]; - (match(Get.threadFromRoot(threadRoot)) ? topNodes : bottomNodes).push(threadRoot); + var ID, bottomThreads, k, len1, ref, topThreads; + topThreads = []; + bottomThreads = []; + ref = Index.sortedThreadIDs; + for (k = 0, len1 = ref.length; k < len1; k++) { + ID = ref[k]; + (match(Index.parsedThreads[ID]) ? topThreads : bottomThreads).push(ID); } - return Index.sortedNodes = topNodes.concat(bottomNodes); + return Index.sortedThreadIDs = topThreads.concat(bottomThreads); }, buildIndex: function() { - var i, nodes, page, post; + var threadIDs; if (!Index.liveThreadData) { return; } switch (Conf['Index Mode']) { case 'all pages': - nodes = Index.sortedNodes; + threadIDs = Index.sortedThreadIDs; break; case 'catalog': - nodes = Index.buildCatalogViews(); - Index.sizeCatalogViews(nodes); + threadIDs = Index.sortedThreadIDs.filter(function(ID) { + return !Index.isHidden(ID) !== Index.showHiddenThreads; + }); break; default: - if (Index.followedThreadID != null) { - i = 0; - while (Index.followedThreadID !== Get.threadFromRoot(Index.sortedNodes[i]).ID) { - i++; - } - page = Math.floor(i / Index.threadsNumPerPage) + 1; - if (page !== Index.currentPage) { - Index.currentPage = page; - Index.pushState({ - page: page - }); - Index.setPage(); - } - } - nodes = Index.buildSinglePage(Index.currentPage); + threadIDs = Index.threadsOnPage(Index.currentPage); } delete Index.pageNum; $.rmAll(Index.root); $.rmAll(Header.hover); + if (Index.loaded && Index.root.parentNode) { + $.event('PostsRemoved', null, Index.root); + } if (Conf['Index Mode'] === 'catalog') { - return $.add(Index.root, nodes); + Index.buildCatalog(threadIDs); } else { - if (Conf['Show Replies']) { - Index.buildReplies(nodes); - } - Index.buildStructure(nodes); - if ((Index.followedThreadID != null) && (post = g.posts[g.BOARD + "." + Index.followedThreadID])) { - return Header.scrollTo(post.nodes.root); - } + Index.buildStructure(threadIDs); } }, - buildSinglePage: function(pageNum) { + threadsOnPage: function(pageNum) { var nodesPerPage, offset; nodesPerPage = Index.threadsNumPerPage; offset = nodesPerPage * (pageNum - 1); - return Index.sortedNodes.slice(offset, offset + nodesPerPage); + return Index.sortedThreadIDs.slice(offset, offset + nodesPerPage); }, - buildStructure: function(nodes) { - var j, len, node, thumb; - for (j = 0, len = nodes.length; j < len; j++) { - node = nodes[j]; - if (thumb = $('img[data-src]', node)) { - thumb.src = thumb.dataset.src; - thumb.removeAttribute('data-src'); - } - $.add(Index.root, [node, $.el('hr')]); + buildStructure: function(threadIDs) { + var k, len1, nodes, thread, threads; + threads = Index.buildThreads(threadIDs, false, Conf['Show Replies']); + nodes = []; + for (k = 0, len1 = threads.length; k < len1; k++) { + thread = threads[k]; + nodes.push(thread.nodes.root, $.el('hr')); } - if (doc.contains(Index.root)) { - $.event('PostsInserted'); + $.add(Index.root, nodes); + if (Index.root.parentNode) { + $.event('PostsInserted', null, Index.root); } - return ThreadHiding.onIndexBuild(nodes); + Index.loaded = true; + }, + buildCatalog: function(threadIDs) { + var fn, i, n, node0; + i = 0; + n = threadIDs.length; + node0 = null; + fn = function() { + var j; + if (node0 && !node0.parentNode) { + return; + } + j = i > 0 && Index.root.parentNode ? n : i + 30; + node0 = Index.buildCatalogPart(threadIDs.slice(i, j))[0]; + i = j; + if (i < n) { + return $.queueTask(fn); + } else { + if (Index.root.parentNode) { + $.event('PostsInserted', null, Index.root); + } + return Index.loaded = true; + } + }; + fn(); + }, + buildCatalogPart: function(threadIDs) { + var k, len1, nodes, thread, threads; + threads = Index.buildThreads(threadIDs, true); + Index.buildCatalogViews(threads); + Index.sizeCatalogViews(threads); + nodes = []; + for (k = 0, len1 = threads.length; k < len1; k++) { + thread = threads[k]; + thread.OP.setCatalogOP(true); + $.add(thread.catalogView.nodes.root, thread.OP.nodes.root); + nodes.push(thread.catalogView.nodes.root); + $.on(thread.catalogView.nodes.root, 'mouseenter', Index.cb.catalogReplies.bind(thread)); + $.on(thread.OP.nodes.root, 'mouseenter', Index.cb.hoverAdjust.bind(thread.OP.nodes)); + } + $.add(Index.root, nodes); + return nodes; }, clearSearch: function() { Index.searchInput.value = ''; @@ -9565,21 +12608,34 @@ Index = (function() { return Index.pageLoad(false); }, querySearch: function(query) { - var keywords; + var keywords, match, regexp; + if ((match = query.match(/^([\w+]+):\/(.*)\/(\w*)$/))) { + try { + regexp = RegExp(match[2], match[3]); + } catch (error) { + return []; + } + return Index.sortedThreadIDs.filter(function(ID) { + return regexp.test(Filter.values(match[1], Index.parsedThreads[ID]).join('\n')); + }); + } if (!(keywords = query.toLowerCase().match(/\S+/g))) { return; } - return Index.sortedNodes.filter(function(threadRoot) { - return Index.searchMatch(Get.threadFromRoot(threadRoot), keywords); + return Index.sortedThreadIDs.filter(function(ID) { + return Index.searchMatch(Index.parsedThreads[ID], keywords); }); }, - searchMatch: function(thread, keywords) { - var file, info, j, k, key, keyword, len, len1, ref, ref1, text; - ref = thread.OP, info = ref.info, file = ref.file; + searchMatch: function(obj, keywords) { + var file, info, k, key, keyword, l, len1, len2, ref, text; + info = obj.info, file = obj.file; + if (info.comment == null) { + info.comment = g.SITE.Build.parseComment(info.commentHTML.innerHTML); + } text = []; - ref1 = ['comment', 'subject', 'name', 'tripcode', 'email']; - for (j = 0, len = ref1.length; j < len; j++) { - key = ref1[j]; + ref = ['comment', 'subject', 'name', 'tripcode']; + for (k = 0, len1 = ref.length; k < len1; k++) { + key = ref[k]; if (key in info) { text.push(info[key]); } @@ -9588,8 +12644,8 @@ Index = (function() { text.push(file.name); } text = text.join(' ').toLowerCase(); - for (k = 0, len1 = keywords.length; k < len1; k++) { - keyword = keywords[k]; + for (l = 0, len2 = keywords.length; l < len2; l++) { + keyword = keywords[l]; if (-1 === text.indexOf(keyword)) { return false; } @@ -9607,7 +12663,10 @@ Polyfill = (function() { Polyfill = { init: function() { - return this.toBlob(); + var base; + this.toBlob(); + $.global(this.toBlob); + (base = Element.prototype).matches || (base.matches = Element.prototype.mozMatchesSelector || Element.prototype.webkitMatchesSelector); }, toBlob: function() { if (HTMLCanvasElement.prototype.toBlob) { @@ -9615,9 +12674,6 @@ Polyfill = (function() { } HTMLCanvasElement.prototype.toBlob = function(cb, type, encoderOptions) { var data, i, j, l, ref, ui8a, url; - if (type == null) { - type = 'image/png'; - } url = this.toDataURL(type, encoderOptions); data = atob(url.slice(url.indexOf(',') + 1)); l = data.length; @@ -9626,10 +12682,9 @@ Polyfill = (function() { ui8a[i] = data.charCodeAt(i); } return cb(new Blob([ui8a], { - type: type + type: type || 'image/png' })); }; - return $.globalEval("HTMLCanvasElement.prototype.toBlob = (" + HTMLCanvasElement.prototype.toBlob + ");"); } }; @@ -9644,7 +12699,7 @@ Settings = (function() { Settings = { init: function() { - var add, link, settings; + var add, link; link = $.el('a', { className: 'settings-link fa fa-wrench', textContent: 'Settings', @@ -9663,14 +12718,25 @@ Settings = (function() { $.on(d, 'OpenSettings', function(e) { return Settings.open(e.detail); }); - if (Conf['Disable Native Extension']) { + if (g.SITE.software === 'yotsuba' && Conf['Disable Native Extension']) { if ($.hasStorage) { - settings = JSON.parse(localStorage.getItem('4chan-settings')) || {}; - if (settings.disableAll) { - return; - } - settings.disableAll = true; - return localStorage.setItem('4chan-settings', JSON.stringify(settings)); + return $.global(function() { + var settings; + try { + settings = JSON.parse(localStorage.getItem('4chan-settings')) || {}; + if (settings.disableAll) { + return; + } + settings.disableAll = true; + return localStorage.setItem('4chan-settings', JSON.stringify(settings)); + } catch (error) { + return Object.defineProperty(window, 'Config', { + value: { + disableAll: true + } + }); + } + }); } else { return $.global(function() { return Object.defineProperty(window, 'Config', { @@ -9683,21 +12749,14 @@ Settings = (function() { } }, open: function(openSection) { - var dialog, j, len, link, links, overlay, ref, section, sectionToOpen; - if (Settings.overlay) { + var dialog, j, len, link, links, ref, section, sectionToOpen; + if (Settings.dialog) { return; } $.event('CloseMenu'); Settings.dialog = dialog = $.el('div', { - id: 'fourchanx-settings', - className: 'dialog' - }); - $.extend(dialog, { - innerHTML: "
" - }); - Settings.overlay = overlay = $.el('div', { id: 'overlay' - }); + }, {innerHTML: ""}); $.on($('.export', dialog), 'click', Settings["export"]); $.on($('.import', dialog), 'click', Settings["import"]); $.on($('.reset', dialog), 'click', Settings.reset); @@ -9723,9 +12782,12 @@ Settings = (function() { (sectionToOpen ? sectionToOpen : links[0]).click(); } $.on($('.close', dialog), 'click', Settings.close); - $.on(overlay, 'click', Settings.close); $.on(window, 'beforeunload', Settings.close); - $.add(d.body, [overlay, dialog]); + $.on(dialog, 'click', Settings.close); + $.on(dialog.firstElementChild, 'click', function(e) { + return e.stopPropagation(); + }); + $.add(d.body, dialog); return $.event('OpenSettings', null, dialog); }, close: function() { @@ -9736,9 +12798,7 @@ Settings = (function() { if ((ref = d.activeElement) != null) { ref.blur(); } - $.rm(Settings.overlay); $.rm(Settings.dialog); - delete Settings.overlay; return delete Settings.dialog; }, sections: [], @@ -9773,32 +12833,28 @@ Settings = (function() { if ($.cantSync) { why = $.cantSet ? 'save your settings' : 'synchronize settings between tabs'; return cb($.el('li', { - textContent: "4chan X needs local storage to " + why + ".\nEnable it on boards.4chan.org in your browser's privacy settings (may be listed as part of \"local data\" or \"cookies\")." + textContent: "4chan X needs local storage to " + why + ".\nEnable it on boards." + (location.hostname.split('.')[1]) + ".org in your browser's privacy settings (may be listed as part of \"local data\" or \"cookies\")." })); } }, ads: function(cb) { - return $.onExists(doc, '.ad-cnt', function(ad) { - return $.onExists(ad, 'img', function() { + return $.onExists(doc, '.adg-rects > .desktop', function(ad) { + return $.onExists(ad, 'iframe', function() { var url; url = Redirect.to('thread', { boardID: 'qa', threadID: 362590 }); - return cb($.el('li', { - innerHTML: "To protect yourself from malicious ads, you should block ads on 4chan." - })); + return cb($.el('li', {innerHTML: "To protect yourself from malicious ads, you should block ads on 4chan."})); }); }); } }, main: function(section) { - var addWarning, arr, button, container, containers, description, div, fs, input, inputs, items, key, level, obj, ref, ref1, warning, warnings; + var addCheckboxes, addWarning, button, div, fs, inputs, items, key, keyFS, obj, ref, ref1, warning, warnings; warnings = $.el('fieldset', { hidden: true - }, { - innerHTML: "Warnings
    " - }); + }, {innerHTML: "Warnings
      "}); addWarning = function(item) { $.add($('ul', warnings), item); return warnings.hidden = false; @@ -9809,28 +12865,24 @@ Settings = (function() { warning(addWarning); } $.add(section, warnings); - items = {}; - inputs = {}; - ref1 = Config.main; - for (key in ref1) { - obj = ref1[key]; - fs = $.el('fieldset', { - innerHTML: "" + E(key) + "" - }); - containers = [fs]; + items = $.dict(); + inputs = $.dict(); + addCheckboxes = function(root, obj) { + var arr, container, containers, description, div, input, level, results; + containers = [root]; + results = []; for (key in obj) { arr = obj[key]; - description = arr[1]; - div = $.el('div', { - innerHTML: ": " + E(description) + "" - }); - if ($.engine !== 'gecko' && key === 'Remember QR Size') { - div.hidden = true; + if (!(arr instanceof Array)) { + continue; } + description = arr[1]; + div = $.el('div', {innerHTML: ": " + E(description) + ""}); + div.dataset.name = key; input = $('input', div); + $.on(input, 'change', $.cb.checked); $.on(input, 'change', function() { - this.parentNode.parentNode.dataset.checked = this.checked; - return $.cb.checked.call(this); + return this.parentNode.parentNode.dataset.checked = this.checked; }); items[key] = Conf[key]; inputs[key] = input; @@ -9844,10 +12896,30 @@ Settings = (function() { } else if (containers.length > level + 1) { containers.splice(level + 1, containers.length - (level + 1)); } - $.add(containers[level], div); + results.push($.add(containers[level], div)); + } + return results; + }; + ref1 = Config.main; + for (keyFS in ref1) { + obj = ref1[keyFS]; + fs = $.el('fieldset', {innerHTML: "" + E(keyFS) + ""}); + addCheckboxes(fs, obj); + if (keyFS === 'Posting and Captchas') { + $.add(fs, $.el('p', {innerHTML: "For more info on captcha options and issues, see the captcha FAQ."})); } $.add(section, fs); } + addCheckboxes($('div[data-name="JSON Index"] > .suboption-list', section), Config.Index); + if ($.engine !== 'gecko') { + $('div[data-name="Remember QR Size"]', section).hidden = true; + } + if ($.perProtocolSettings || location.protocol !== 'https:') { + $('div[data-name="Redirect to HTTPS"]', section).hidden = true; + } + if ($.platform !== 'crx') { + $('div[data-name="Work around CORB Bug"]', section).hidden = true; + } $.get(items, function(items) { var val; for (key in items) { @@ -9856,25 +12928,46 @@ Settings = (function() { inputs[key].parentNode.parentNode.dataset.checked = val; } }); - div = $.el('div', { - innerHTML: ": Clear manually-hidden threads and posts on all boards. Reload the page to apply." - }); + div = $.el('div', {innerHTML: ": Clear manually-hidden threads and posts on all boards. Reload the page to apply."}); button = $('button', div); $.get({ - hiddenThreads: {}, - hiddenPosts: {} + hiddenThreads: $.dict(), + hiddenPosts: $.dict() }, function(arg) { - var ID, board, hiddenNum, hiddenPosts, hiddenThreads, ref2, ref3, thread; + var ID, board, hiddenNum, hiddenPosts, hiddenThreads, ref2, ref3, ref4, ref5, site, thread; hiddenThreads = arg.hiddenThreads, hiddenPosts = arg.hiddenPosts; hiddenNum = 0; - ref2 = hiddenThreads.boards; - for (ID in ref2) { - board = ref2[ID]; - hiddenNum += Object.keys(board).length; + for (ID in hiddenThreads) { + site = hiddenThreads[ID]; + if (ID !== 'boards') { + ref2 = site.boards; + for (ID in ref2) { + board = ref2[ID]; + hiddenNum += Object.keys(board).length; + } + } } - ref3 = hiddenPosts.boards; + ref3 = hiddenThreads.boards; for (ID in ref3) { board = ref3[ID]; + hiddenNum += Object.keys(board).length; + } + for (ID in hiddenPosts) { + site = hiddenPosts[ID]; + if (ID !== 'boards') { + ref4 = site.boards; + for (ID in ref4) { + board = ref4[ID]; + for (ID in board) { + thread = board[ID]; + hiddenNum += Object.keys(thread).length; + } + } + } + } + ref5 = hiddenPosts.boards; + for (ID in ref5) { + board = ref5[ID]; for (ID in board) { thread = board[ID]; hiddenNum += Object.keys(thread).length; @@ -9884,10 +12977,13 @@ Settings = (function() { }); $.on(button, 'click', function() { this.textContent = 'Hidden: 0'; - return $.get('hiddenThreads', {}, function(arg) { - var boardID, hiddenThreads; + return $.get('hiddenThreads', $.dict(), function(arg) { + var boardID, hiddenThreads, ref2; hiddenThreads = arg.hiddenThreads; - if ($.hasStorage) { + if ($.hasStorage && g.SITE.software === 'yotsuba') { + for (boardID in (ref2 = hiddenThreads['4chan.org']) != null ? ref2.boards : void 0) { + localStorage.removeItem("4chan-hide-t-" + boardID); + } for (boardID in hiddenThreads.boards) { localStorage.removeItem("4chan-hide-t-" + boardID); } @@ -9898,19 +12994,27 @@ Settings = (function() { return $.after($('input[name="Stubs"]', section).parentNode.parentNode, div); }, "export": function() { - return $.get(Conf, function(Conf) { + var Conf2; + Conf2 = $.dict(); + $.extend(Conf2, Conf); + return $.get(Conf2, function(Conf2) { + delete Conf2['boardConfig']; return Settings.downloadExport({ version: g.VERSION, date: Date.now(), - Conf: Conf + Conf: Conf2 }); }); }, downloadExport: function(data) { - var a, p; + var a, blob, p, url; + blob = new Blob([JSON.stringify(data, null, 2)], { + type: 'application/json' + }); + url = URL.createObjectURL(blob); a = $.el('a', { download: "4chan X 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); $.rmAll(p); @@ -9935,15 +13039,15 @@ Settings = (function() { reader.onload = function(e) { var err; try { - return Settings.loadSettings(JSON.parse(e.target.result), function(err) { + return Settings.loadSettings($.dict.json(e.target.result), function(err) { if (err) { return output.textContent = 'Import failed due to an error.'; } else if (confirm('Import successful. Reload now?')) { return window.location.reload(); } }); - } catch (_error) { - err = _error; + } catch (error) { + err = error; output.textContent = 'Import failed due to an error.'; return c.error(err.stack); } @@ -9968,17 +13072,20 @@ Settings = (function() { 'Disable 4chan\'s extension': 'Disable Native Extension', 'Comment Auto-Expansion': '', 'Remove Slug': '', + 'Always HTTPS': 'Redirect to HTTPS', 'Check for Updates': '', 'Recursive Filtering': 'Recursive Hiding', 'Reply Hiding': 'Reply Hiding Buttons', 'Thread Hiding': 'Thread Hiding Buttons', 'Show Stubs': 'Stubs', 'Image Auto-Gif': 'Replace GIF', + 'Expand All WebM': 'Expand videos', 'Reveal Spoilers': 'Reveal Spoiler Thumbnails', 'Expand From Current': 'Expand from here', 'Current Page': 'Page Count in Stats', 'Current Page Position': '', 'Alternative captcha': 'Use Recaptcha v1', + 'Alt index captcha': 'Use Recaptcha v1 on Index', 'Auto Submit': 'Post on Captcha Completion', 'Open Reply in New Tab': 'Open Post in New Tab', 'Remember QR size': 'Remember QR Size', @@ -10000,6 +13107,7 @@ Settings = (function() { 'spoiler': 'Spoiler tags', 'sageru': 'Toggle sage', 'code': 'Code tags', + 'sjis': 'SJIS tags', 'submit': 'Submit QR', 'watch': 'Watch', 'update': 'Update', @@ -10020,6 +13128,10 @@ Settings = (function() { 'Scrolling': 'Auto Scroll', 'Verbose': '' }); + if ('Always CDN' in data.Conf) { + data.Conf['fourchanImageHost'] = data.Conf['Always CDN'] ? 'i.4cdn.org' : ''; + delete data.Conf['Always CDN']; + } data.Conf.sauces = data.Conf.sauces.replace(/\$\d/g, function(c) { switch (c) { case '$1': @@ -10046,15 +13158,17 @@ Settings = (function() { } } if (data.WatchedThreads) { - data.Conf['watchedThreads'] = { - boards: {} - }; + data.Conf['watchedThreads'] = $.dict.clone({ + '4chan.org': { + boards: {} + } + }); ref1 = data.WatchedThreads; for (boardID in ref1) { threads = ref1[boardID]; for (threadID in threads) { threadData = threads[threadID]; - ((base = data.Conf['watchedThreads'].boards)[boardID] || (base[boardID] = {}))[threadID] = { + ((base = data.Conf['watchedThreads']['4chan.org'].boards)[boardID] || (base[boardID] = $.dict()))[threadID] = { excerpt: threadData.textContent }; } @@ -10064,11 +13178,16 @@ Settings = (function() { } }, upgrade: function(data, version) { - var addCSS, addSauces, boardID, changes, compareString, j, key, len, name, record, ref, ref1, ref2, ref3, ref4, ref5, rice, set, type, uids, value; - changes = {}; + var addCSS, addSauces, boardID, boards, changes, compareString, corrupted, db, hostname, j, k, key, l, lastChecked, len, len1, len2, len3, line, list, m, name, record, ref, ref1, ref10, ref11, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, rice, set, setD, siteProperties, software, type, uids, val, val2, value; + changes = $.dict(); set = function(key, value) { return data[key] = changes[key] = value; }; + setD = function(key, value) { + if (data[key] == null) { + return set(key, value); + } + }; addSauces = function(sauces) { if (data['sauces'] != null) { sauces = sauces.filter(function(s) { @@ -10087,9 +13206,35 @@ Settings = (function() { return set('usercss', css + '\n\n' + data['usercss']); } }; + if ((corrupted = version[0] === '"')) { + try { + version = JSON.parse(version); + } catch (error) {} + } compareString = version.replace(/\d+/g, function(x) { return ('0000' + x).slice(-5); }); + if (compareString < '00001.00013.00014.00008') { + for (key in data) { + val = data[key]; + if (!(typeof val === 'string' && typeof Conf[key] !== 'string' && (key !== 'Index Sort' && key !== 'Last Long Reply Thresholds 0' && key !== 'Last Long Reply Thresholds 1'))) { + continue; + } + corrupted = true; + break; + } + } + if (corrupted) { + for (key in data) { + val = data[key]; + if (typeof val === 'string') { + try { + val2 = JSON.parse(val); + set(key, val2); + } catch (error) {} + } + } + } if (compareString < '00001.00011.00008.00000') { if (data['Fixed Thread Watcher'] == null) { set('Fixed Thread Watcher', (ref = data['Toggleable Thread Watcher']) != null ? ref : true); @@ -10118,7 +13263,7 @@ Settings = (function() { record = ref2[boardID]; for (type in record) { name = record[type]; - if (name in uids) { + if ($.hasOwn(uids, name)) { record[type] = uids[name]; } } @@ -10147,7 +13292,7 @@ Settings = (function() { set('sauces', data['sauces'].replace(/^(#?\s*)http:\/\/iqdb\.org\//mg, '$1//iqdb.org/')); } } - if (compareString < '00001.00011.00019.00003' && !Settings.overlay) { + if (compareString < '00001.00011.00019.00003' && !Settings.dialog) { $.queueTask(function() { return Settings.warnings.ads(function(item) { return new Notice('warning', slice.call(item.childNodes)); @@ -10226,10 +13371,153 @@ Settings = (function() { addCSS('.qr-link-container-bottom {display: none;}'); } } + if (compareString < '00001.00012.00000.00006') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/^(#?\s*)https:\/\/(?:desustorage|cuckchan)\.org\//mg, '$1https://desuarchive.org/')); + } + } + if (compareString < '00001.00012.00001.00000') { + if ((data['Persistent Thread Watcher'] == null) && (data['Toggleable Thread Watcher'] != null)) { + set('Persistent Thread Watcher', !data['Toggleable Thread Watcher']); + } + } + if (compareString < '00001.00012.00003.00000') { + ref6 = ['Image Hover in Catalog', 'Auto Watch', 'Auto Watch Reply']; + for (k = 0, len1 = ref6.length; k < len1; k++) { + key = ref6[k]; + setD(key, false); + } + } + if (compareString < '00001.00013.00001.00002') { + addSauces(['#//www.bing.com/images/search?q=imgurl:%IMG&view=detailv2&iss=sbi#enterInsights']); + } + if (compareString < '00001.00013.00005.00000') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/^(#?\s*)http:\/\/regex\.info\/exif\.cgi/mg, '$1http://exif.regex.info/exif.cgi')); + } + addSauces(Config['sauces'].match(/# Known filename formats:(?:\n.+)*|$/)[0].split('\n')); + } + if (compareString < '00001.00013.00007.00002') { + setD('Require OP Quote Link', true); + } + if (compareString < '00001.00013.00008.00000') { + setD('Download Link', true); + } + if (compareString < '00001.00013.00009.00003') { + if (data['jsWhitelist'] != null) { + list = data['jsWhitelist'].split('\n'); + if (indexOf.call(list, 'https://cdnjs.cloudflare.com') < 0 && indexOf.call(list, 'https://cdn.mathjax.org') >= 0) { + set('jsWhitelist', data['jsWhitelist'] + '\n\nhttps://cdnjs.cloudflare.com'); + } + } + } + if (compareString < '00001.00014.00000.00006') { + if (data['siteSoftware'] != null) { + set('siteSoftware', data['siteSoftware'] + '\n4cdn.org yotsuba'); + } + } + if (compareString < '00001.00014.00003.00002') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/^(#?\s*)https:\/\/whatanime\.ga\//mg, '$1https://trace.moe/')); + } + } + if (compareString < '00001.00014.00004.00004') { + if ((data['siteSoftware'] != null) && !/^4channel\.org yotsuba$/m.test(data['siteSoftware'])) { + set('siteSoftware', data['siteSoftware'] + '\n4channel.org yotsuba'); + } + } + if (compareString < '00001.00014.00005.00000') { + ref7 = DataBoard.keys; + for (l = 0, len2 = ref7.length; l < len2; l++) { + db = ref7[l]; + if ((ref8 = data[db]) != null ? ref8.boards : void 0) { + ref9 = data[db], boards = ref9.boards, lastChecked = ref9.lastChecked; + data[db]['4chan.org'] = { + boards: boards, + lastChecked: lastChecked + }; + delete data[db].boards; + delete data[db].lastChecked; + set(db, data[db]); + } + } + if ((data['siteSoftware'] != null) && (data['siteProperties'] == null)) { + siteProperties = $.dict(); + ref10 = data['siteSoftware'].split('\n'); + for (m = 0, len3 = ref10.length; m < len3; m++) { + line = ref10[m]; + ref11 = line.split(' '), hostname = ref11[0], software = ref11[1]; + siteProperties[hostname] = { + software: software + }; + } + set('siteProperties', siteProperties); + } + } + if (compareString < '00001.00014.00006.00006') { + if (data['sauces'] != null) { + 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}-/')); + } + } + if (compareString < '00001.00014.00008.00000') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/https:\/\/www\.yandex\.com\/images\/search/g, 'https://yandex.com/images/search')); + } + } + if (compareString < '00001.00014.00009.00000') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/^(#?\s*)(?:http:)?\/\/(www\.pixiv\.net|www\.deviantart\.com|imgur\.com|flickr\.com)\//mg, '$1https://$2/')); + set('sauces', data['sauces'].replace(/https:\/\/yandex\.com\/images\/search\?rpt=imageview&img_url=%IMG/g, 'https://yandex.com/images/search?rpt=imageview&url=%IMG')); + } + } + if (compareString < '00001.00014.00009.00001') { + if ((data['Use Faster Image Host'] != null) && (data['fourchanImageHost'] == null)) { + set('fourchanImageHost', (data['Use Faster Image Host'] ? 'i.4cdn.org' : '')); + } + } + if (compareString < '00001.00014.00010.00001') { + if (data['Filter in Native Catalog'] == null) { + set('Filter in Native Catalog', false); + } + } + if (compareString < '00001.00014.00012.00008') { + if (data['boardnav'] == null) { + set('boardnav', "[ toggle-all ]\na-replace\nc-replace\ng-replace\nk-replace\nv-replace\nvg-replace\nvr-replace\nck-replace\nco-replace\nfit-replace\njp-replace\nmu-replace\nsp-replace\ntv-replace\nvp-replace\n[external-text:\"FAQ\",\"https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions\"]"); + } + } + if (compareString < '00001.00014.00016.00001') { + if (data['archiveLists'] != null) { + set('archiveLists', data['archiveLists'].replace('https://mayhemydg.github.io/archives.json/archives.json', 'https://nstepien.github.io/archives.json/archives.json')); + } + } + if (compareString < '00001.00014.00016.00007') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/https:\/\/www\.deviantart\.com\/gallery\/#\/d%\$1%\$2;regexp:\/\^\\w\+_by_\\w\+\[_-\]d\(\[\\da-z\]\{6\}\)\\b\|\^d\(\[\\da-z\]\{6\}\)-\[\\da-z\]\{8\}-\//g, 'javascript:void(open("https://www.deviantart.com/"+%$1.replace(/_/g,"-")+"/art/"+parseInt(%$2,36)));regexp:/^\\w+_by_(\\w+)[_-]d([\\da-z]{6})\\b/').replace(/\/\/imgops\.com\/%URL/g, '//imgops.com/start?url=%URL')); + } + } + if (compareString < '00001.00014.00017.00002') { + if (data['jsWhitelist'] != null) { + set('jsWhitelist', data['jsWhitelist'] + '\n\nhttps://hcaptcha.com\nhttps://*.hcaptcha.com'); + } + } + if (compareString < '00001.00014.00020.00004') { + if (data['archiveLists'] != null) { + set('archiveLists', data['archiveLists'].replace('https://nstepien.github.io/archives.json/archives.json', 'https://4chenz.github.io/archives.json/archives.json')); + } + } + if (compareString < '00001.00014.00022.00003') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/^#?\s*https:\/\/www\.google\.com\/searchbyimage\?image_url=%(IMG|T?URL)&safe=off(?=$|;)/mg, 'https://www.google.com/searchbyimage?sbisrc=4chanx&image_url=%$1&safe=off')); + if (compareString === '00001.00014.00022.00002' && !/\bsbisrc=/.test(data['sauces'])) { + set('sauces', data['sauces'].replace(/^#?\s*https:\/\/lens\.google\.com\/uploadbyurl\?url=%(IMG|T?URL)(?=$|;)/m, 'https://www.google.com/searchbyimage?sbisrc=4chanx&image_url=%$1&safe=off')); + } + } + addSauces(['#https://lens.google.com/uploadbyurl?url=%IMG;text:lens']); + } return changes; }, loadSettings: function(data, cb) { - if (data.version.split('.')[0] === '2') { + if (data.version.split('.')[0] === '2' && "Disable 4chan's extension" in data.Conf) { data = Settings.convertFrom.loadletter(data); } else if (data.version !== g.VERSION) { Settings.upgrade(data.Conf, data.version); @@ -10254,58 +13542,59 @@ Settings = (function() { }, filter: function(section) { var select; - $.extend(section, { - innerHTML: "
      " - }); + $.extend(section, {innerHTML: "
      "}); select = $('select', section); $.on(select, 'change', Settings.selectFilter); return Settings.selectFilter.call(select); }, selectFilter: function() { - var div, name, ta; + var div, filterTypes, name, ta; div = this.nextElementSibling; if ((name = this.value) !== 'guide') { + if (!$.hasOwn(Config.filter, name)) { + return; + } $.rmAll(div); ta = $.el('textarea', { name: name, className: 'field', spellcheck: false }); + $.on(ta, 'change', $.cb.value); $.get(name, Conf[name], function(item) { - return ta.value = item[name]; + ta.value = item[name]; + return $.add(div, ta); }); - $.on(ta, 'change', $.cb.value); - $.add(div, ta); return; } - $.extend(div, { - innerHTML: "
      Filter is disabled.

      Use regular expressions, one per line.
      Lines starting with a # will be ignored.
      For example, /weeaboo/i will filter posts containing the string \`weeaboo\`, case-insensitive.
      MD5 filtering uses exact string matching, not regular expressions.

        You can use these settings with each regular expression, separate them with semicolons:
      • Per boards, separate them with commas. It is global if not specified.
        For example: boards:a,jp;.
      • In case of a global rule, select boards to be excluded from the filter.
        For example: exclude:vg,v;.
      • Filter OPs only along with their threads (\`only\`), replies only (\`no\`), or both (\`yes\`, this is default).
        For example: op:only;, op:no; or op:yes;.
      • Overrule the \`Show Stubs\` setting if specified: create a stub (\`yes\`) or not (\`no\`).
        For example: stub:yes; or stub:no;.
      • Highlight instead of hiding. You can specify a class name to use with a userstyle.
        For example: highlight; or highlight:wallpaper;.
      • Highlighted OPs will have their threads put on top of the board index by default.
        For example: top:yes; or top:no;.

      Note: If you're using the native catalog rather than 4chan X's catalog, 4chan X's filters do not apply there.
      The native catalog has its own separate filter list.

      " + filterTypes = Object.keys(Config.filter).filter(function(x) { + return x !== 'general'; + }).map(function(x, i) { + return {innerHTML: ((i) ? "," : "") + "" + E(x)}; }); + $.extend(div, {innerHTML: "
      Filter is disabled.

      Use regular expressions, one per line.
      Lines starting with a # will be ignored.
      For example, /weeaboo/i will filter posts containing the string `weeaboo`, case-insensitive.
      MD5 and Unique ID filtering use exact string matching, not regular expressions.

        You can use these settings with each regular expression, separate them with semicolons:
      • Per boards, separate them with commas. It is global if not specified. Use sfw and nsfw to reference all worksafe or not-worksafe boards.
        For example: boards:a,jp;.
        To specify boards on a particular site, put the beginning of the domain and a slash character before the list.
        Any initial www. should not be included, and all 4chan domains are considered 4chan.org.
        For example: boards:4:a,jp,sama:a,z;.
        An asterisk can be used to specify all boards on a site.
        For example: boards:4:*;.
      • Select boards to be excluded from the filter. The syntax is the same as for the boards: option above.
        For example: exclude:vg,v;.
      • Filter OPs only along with their threads (`only`) or replies only (`no`).
        For example: op:only; or op:no;.
      • Filter only posts with files (`only`) or only posts without files (`no`).
        For example: file:only; or file:no;.
      • Overrule the `Show Stubs` setting if specified: create a stub (`yes`) or not (`no`).
        For example: stub:yes; or stub:no;.
      • Highlight instead of hiding. You can specify a class name to use with a userstyle.
        For example: highlight; or highlight:wallpaper;.
      • Highlighted OPs will have their threads put on top of the board index by default.
        For example: top:yes; or top:no;.
      • Show a desktop notification instead of hiding.
        For example: notify;.
      • Filters in the \"General\" section apply to multiple fields, by default subject,name,filename,comment.
        The fields can be specified with the type option, separated by commas.
        For example: type:" + E.cat(filterTypes) + ";.
        Types can also be combined with a + sign; this indicates the filter applies to the given fields joined by newlines.
        For example: type:filename+filesize+dimensions;.
      "}); return $('.warning', div).hidden = Conf['Filter']; }, sauce: function(section) { var ta; - $.extend(section, { - innerHTML: "
      Sauce is disabled.
      Lines starting with a # will be ignored.
      You can specify a display text by appending ;text:[text] to the URL.
      You can specify the applicable boards by appending ;boards:[board1],[board2].
      You can specify the applicable file types by appending ;types:[extension1],[extension2].
        These parameters will be replaced by their corresponding values:
      • %TURL: Thumbnail URL.
      • %URL: Full image URL.
      • %IMG: Full image URL for GIF, JPG, and PNG; thumbnail URL for other types.
      • %MD5: MD5 hash in base64.
      • %sMD5: MD5 hash in base64 using - and _.
      • %hMD5: MD5 hash in hexadecimal.
      • %name: Original file name.
      • %board: Current board.
      • %%, %semi: Literal % and ;.
      " - }); + $.extend(section, {innerHTML: "
      Sauce is disabled.
      These parameters will be replaced by their corresponding values in the URL and displayed text:
      • %IMG: Full image URL for GIF, JPG, and PNG; thumbnail URL for other types.
      • %URL: Full image URL.
      • %TURL: Thumbnail URL.
      • %name: Original file name.
      • %board: Current board.
      • %MD5: MD5 hash in base64.
      • %sMD5: MD5 hash in base64 using - and _.
      • %hMD5: MD5 hash in hexadecimal.
      • %$0: Matched regular expression within the filename.
      • %$1, %$2, %$3, ... : Subexpressions within the matched regular expression.
      • %%, %semi: Literal % and ;.
      Lines starting with a # will be ignored.
      You can specify a display text by appending ;text:[text] to the URL.
      You can specify the applicable boards/sites by appending ;boards:[board1],[board2]. See the Filter guide for details.
      You can specify the applicable file types by appending ;types:[extension1],[extension2].
      You can specify a regular expression the filename must match by appending ;regexp:[regular expression].
      "}); $('.warning', section).hidden = Conf['Sauce']; ta = $('textarea', section); $.get('sauces', Conf['sauces'], function(item) { - return ta.value = item['sauces']; + ta.value = item['sauces']; + return ta.hidden = false; }); return $.on(ta, 'change', $.cb.value); }, advanced: function(section) { - var applyCSS, boardSelect, customCSS, event, input, inputs, interval, items, itemsArchive, j, k, l, len, len1, len2, len3, m, name, ref, ref1, ref2, ref3, table, updateArchives, warning; - $.extend(section, { - innerHTML: "
      Archives
      404 Redirect is disabled.
      Thread redirectionPost fetchingFile redirection

      Archive Lists: Each line below should be an archive list in this format or a URL to load an archive list from.
      Archive properties can be overriden by another item with the same uid (or if absent, its name).
      Last updated:
      Captcha Language
      Choose from list of language codes. Leave blank to autoselect.
      Custom Board Navigation
      New lines will be converted into spaces.

      In the following examples for /g/, g can be changed to a different board ID (a, b, etc...), the current board (current), or the Twitter link (@).
      Board link: g
      Archive link: g-archive
      Internal archive link: g-expired
      Title link: g-title
      Board link (Replace with title when on that board): g-replace
      Full text link: g-full
      Custom text link: g-text:"Install Gentoo"
      Index-only link: g-index
      Catalog-only link: g-catalog
      Index mode: g-mode:"infinite scrolling"
      Index sort: g-sort:"creation date"
      External link: external-text:"Google","http://www.google.com"
      Combinations are possible: g-index-text:"Technology Index"
      Full board list toggle: toggle-all

      [ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:"Piracy"]
      will give you
      [ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
      if you are on /g/.
      Time Formatting is disabled.
      :
      Day: %a, %A, %d, %e
      Month: %m, %b, %B
      Year: %y, %Y
      Hour: %k, %H, %l, %I, %p, %P
      Minute: %M
      Second: %S
      Literal %: %%
      Quote Backlinks formatting is disabled.
      :
      File Info Formatting is disabled.
      :
      Link: %l (truncated), %L (untruncated), %T (4chan filename)
      Filename: %n (truncated), %N (untruncated), %t (4chan filename)
      Download button: %d
      Spoiler indicator: %p
      Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
      Resolution: %r (Displays 'PDF' for PDF files)
      Tag: %g
      Literal %: %%
      Quick Reply Personas

      One item per line.
      Items will be added in the relevant input's auto-completion list.
      Password items will always be used, since there is no password input.
      Lines starting with a # will be ignored.

        You can use these settings with each item, separate them with semicolons:
      • Possible items are: name, options (or equivalently email), subject and password.
      • Wrap values of items with quotes, like this: options:"sage".
      • Force values as defaults with the always keyword, for example: options:"sage";always.
      • Select specific boards for an item, separated with commas, for example: options:"sage";boards:jp;always.
      Unread Favicon is disabled.
      Thread Updater is disabled.
      Interval: seconds
      Custom Cooldown Time
      Seconds:
      Javascript Whitelist
      Sources from which Javascript is allowed to be loaded by Content Security Policy.
      " - }); + var applyCSS, boardSelect, customCSS, event, input, inputs, interval, items, itemsArchive, j, k, l, len, len1, len2, len3, listImageHost, m, name, ref, ref1, ref2, ref3, ref4, table, textContent, updateArchives, warning; + $.extend(section, {innerHTML: "
      Archives
      404 Redirect is disabled.
      Thread redirectionPost fetchingFile redirection

      Archive Lists: Each line below should be an archive list in this format or a URL to load an archive list from.
      Archive properties can be overriden by another item with the same uid (or if absent, its name).
      Last updated:
      External Catalog
      External Catalog is disabled. This will be used only as a fallback.
      URLs of external catalog sites, where %board is to be replaced by the board name.
      Each URL should be followed by ;boards: and optionally ;exclude: and a list of supported/excluded boards in the format explained in the Filter guide.
      Override 4chan Image Host
      Change 4chan image links to this domain. Leave blank for no change.
      Captcha Language
      Choose from list of language codes. Leave blank to autoselect.
      Custom Board Navigation
      New lines will be converted into spaces.

      In the following examples for /g/, g can be changed to a different board ID (a, b, etc...), the current board (current), or the Twitter link (@).
      Board link: g
      Archive link: g-archive
      Internal archive link: g-expired
      Title link: g-title
      Board link (Replace with title when on that board): g-replace
      Full text link: g-full
      Custom text link: g-text:"Install Gentoo"
      Index-only link: g-index
      Catalog-only link: g-catalog
      Index mode: g-mode:"infinite scrolling"
      Index sort: g-sort:"creation date rev"
      External link: external-text:"Google","http://www.google.com"
      Open in new tab: g-nt
      Combinations are possible: g-index-text:"Technology Index"
      Full board list toggle: toggle-all

      [ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:"Piracy"]
      will give you
      [ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
      if you are on /g/.
      Time Formatting is disabled.
      :
      Day: %a, %A, %d, %e
      Month: %m, %b, %B
      Year: %y, %Y
      Hour: %k, %H, %l, %I, %p, %P
      Minute: %M
      Second: %S
      Literal %: %%
      Quote Backlinks formatting is disabled.
      :
      Default pasted content filename
      .png
      File Info Formatting is disabled.
      :
      Link: %l (truncated), %L (untruncated), %T (4chan filename)
      Filename: %n (truncated), %N (untruncated), %t (4chan filename)
      Download button: %d
      Quick filter MD5: %f
      Spoiler indicator: %p
      Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
      Resolution: %r (Displays 'PDF' for PDF files)
      Tag: %g
      Literal %: %%
      Quick Reply Personas

      One item per line.
      Items will be added in the relevant input's auto-completion list.
      Password items will always be used, since there is no password input.
      Lines starting with a # will be ignored.

        You can use these settings with each item, separate them with semicolons:
      • Possible items are: name, options (or equivalently email), subject and password.
      • Wrap values of items with quotes, like this: options:"sage".
      • Force values as defaults with the always keyword, for example: options:"sage";always.
      • Select specific boards for an item, separated with commas, for example: options:"sage";boards:jp;always.
      Unread Favicon is disabled.
      Thread Updater is disabled.
      Interval: seconds
      Custom Cooldown Time
      Seconds:
      For more information about customizing 4chan X's CSS, see the styling guide.
      Javascript Whitelist
      Sources from which Javascript is allowed to be loaded by Content Security Policy.
      Lines starting with a # will be ignored. Remove or comment out all lines to allow everything.
      Known Banners
      List of known banners, used for click-to-change feature.
      "}); ref = $$('.warning', section); for (j = 0, len = ref.length; j < len; j++) { warning = ref[j]; warning.hidden = Conf[warning.dataset.feature]; } - inputs = {}; + inputs = $.dict(); ref1 = $$('[name]', section); for (k = 0, len1 = ref1.length; k < len1; k++) { input = ref1[k]; @@ -10316,13 +13605,14 @@ Settings = (function() { Conf['lastarchivecheck'] = 0; return $.id('lastarchivecheck').textContent = 'never'; }); - items = {}; - ref2 = ['archiveLists', 'archiveAutoUpdate', 'captchaLanguage', 'boardnav', 'time', 'backlink', 'fileInfo', 'QR.personas', 'favicon', 'usercss', 'customCooldown', 'jsWhitelist']; - for (l = 0, len2 = ref2.length; l < len2; l++) { - name = ref2[l]; - items[name] = Conf[name]; + items = $.dict(); + for (name in inputs) { input = inputs[name]; - event = name === 'archiveLists' || name === 'archiveAutoUpdate' || name === 'QR.personas' || name === 'favicon' || name === 'usercss' ? 'change' : 'input'; + if (!(name !== 'Interval' && name !== 'Custom CSS')) { + continue; + } + items[name] = Conf[name]; + event = (input.nodeName === 'SELECT' || ((ref2 = input.type) === 'checkbox' || ref2 === 'radio') || (input.nodeName === 'TEXTAREA' && !(name in Settings))) ? 'change' : 'input'; $.on(input, event, $.cb[input.type === 'checkbox' ? 'checked' : 'value']); if (name in Settings) { $.on(input, event, Settings[name]); @@ -10334,11 +13624,20 @@ Settings = (function() { val = items[key]; input = inputs[key]; input[input.type === 'checkbox' ? 'checked' : 'value'] = val; + input.hidden = false; if (key in Settings) { Settings[key].call(input); } } }); + listImageHost = $.id('list-fourchanImageHost'); + ref3 = ImageHost.suggestions; + for (l = 0, len2 = ref3.length; l < len2; l++) { + textContent = ref3[l]; + $.add(listImageHost, $.el('option', { + textContent: textContent + })); + } interval = inputs['Interval']; customCSS = inputs['Custom CSS']; applyCSS = $('#apply-css', section); @@ -10351,10 +13650,10 @@ Settings = (function() { $.on(applyCSS, 'click', function() { return CustomCSS.update(); }); - itemsArchive = {}; - ref3 = ['archives', 'selectedArchives', 'lastarchivecheck']; - for (m = 0, len3 = ref3.length; m < len3; m++) { - name = ref3[m]; + itemsArchive = $.dict(); + ref4 = ['archives', 'selectedArchives', 'lastarchivecheck']; + for (m = 0, len3 = ref4.length; m < len3; m++) { + name = ref4[m]; itemsArchive[name] = Conf[name]; } $.get(itemsArchive, function(itemsArchive) { @@ -10383,7 +13682,7 @@ Settings = (function() { tbody = $('tbody', section); $.rmAll(boardSelect); $.rmAll(tbody); - archBoards = {}; + archBoards = $.dict(); ref = Conf['archives']; for (j = 0, len = ref.length; j < len; j++) { ref1 = ref[j], uid = ref1.uid, name = ref1.name, boards = ref1.boards, files = ref1.files, software = ref1.software; @@ -10472,9 +13771,7 @@ Settings = (function() { textContent: archive[1] })); } - $.extend(td, { - innerHTML: "" - }); + $.extend(td, {innerHTML: ""}); select = td.firstElementChild; if (!(select.disabled = length === 1)) { select.setAttribute('data-boardid', boardID); @@ -10489,7 +13786,7 @@ Settings = (function() { return function(arg) { var name1, selectedArchives; selectedArchives = arg.selectedArchives; - (selectedArchives[name1 = _this.dataset.boardid] || (selectedArchives[name1] = {}))[_this.dataset.type] = JSON.parse(_this.value); + (selectedArchives[name1 = _this.dataset.boardid] || (selectedArchives[name1] = $.dict()))[_this.dataset.type] = JSON.parse(_this.value); $.set('selectedArchives', selectedArchives); Conf['selectedArchives'] = selectedArchives; return Redirect.selectArchives(); @@ -10502,6 +13799,9 @@ Settings = (function() { time: function() { return this.nextElementSibling.textContent = Time.format(this.value, new Date()); }, + timeLocale: function() { + return Settings.time.call($('[name=time]', Settings.dialog)); + }, backlink: function() { return this.nextElementSibling.textContent = this.value.replace(/%(?:id|%)/g, function(x) { return { @@ -10515,7 +13815,7 @@ Settings = (function() { data = { isReply: true, file: { - url: '//i.4cdn.org/g/1334437723720.jpg', + url: "//" + (ImageHost.host()) + "/g/1334437723720.jpg", name: 'd9bb2efc98dd0df141a94399ff5880b7.jpg', size: '276 KB', sizeInBytes: 276 * 1024, @@ -10529,16 +13829,21 @@ Settings = (function() { return FileInfo.format(this.value, data, this.nextElementSibling); }, favicon: function() { - var img; + var f, i, icon, img, j, len, ref; Favicon["switch"](); if (g.VIEW === 'thread' && Conf['Unread Favicon']) { Unread.update(); } img = this.nextElementSibling.children; - img[0].src = Favicon["default"]; - img[1].src = Favicon.unreadSFW; - img[2].src = Favicon.unreadNSFW; - return img[3].src = Favicon.unreadDead; + f = Favicon; + ref = [f.SFW, f.unreadSFW, f.unreadSFWY, f.NSFW, f.unreadNSFW, f.unreadNSFWY, f.dead, f.unreadDead, f.unreadDeadY]; + for (i = j = 0, len = ref.length; j < len; i = ++j) { + icon = ref[i]; + if (!img[i]) { + $.add(this.nextElementSibling, $.el('img')); + } + img[i].src = icon; + } }, togglecss: function() { if ($('textarea[name=usercss]', $.x('ancestor::fieldset[1]', this)).disabled = $.id('apply-css').disabled = !this.checked) { @@ -10550,19 +13855,15 @@ Settings = (function() { }, keybinds: function(section) { var arr, input, inputs, items, key, ref, tbody, tr; - $.extend(section, { - innerHTML: "
      Keybinds are disabled.
      Allowed keys: a-z, 0-9, Ctrl, Shift, Alt, Meta, Enter, Esc, Up, Down, Right, Left.
      Press Backspace to disable a keybind.
      ActionsKeybinds
      " - }); + $.extend(section, {innerHTML: "
      Keybinds are disabled.
      Allowed keys: a-z, 0-9, Ctrl, Shift, Alt, Meta, Enter, Esc, Up, Down, Right, Left.
      Press Backspace to disable a keybind.
      ActionsKeybinds
      "}); $('.warning', section).hidden = Conf['Keybinds']; tbody = $('tbody', section); - items = {}; - inputs = {}; + items = $.dict(); + inputs = $.dict(); ref = Config.hotkeys; for (key in ref) { arr = ref[key]; - tr = $.el('tr', { - innerHTML: "" + E(arr[1]) + "" - }); + tr = $.el('tr', {innerHTML: "" + E(arr[1]) + ""}); input = $('input', tr); input.name = key; input.spellcheck = false; @@ -10598,22 +13899,24 @@ Settings = (function() { }).call(this); +Test = (function() { + return Test; + +}).call(this); + UI = (function() { - var Menu, checkbox, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove, + var Menu, UI, checkbox, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, slice = [].slice; - dialog = function(id, position, properties) { + dialog = function(id, properties) { var child, el, i, len, move, ref; el = $.el('div', { className: 'dialog', id: id }); $.extend(el, properties); - el.style.cssText = position; - $.get(id + ".position", position, function(item) { - return el.style.cssText = item[id + ".position"]; - }); + el.style.cssText = Conf[id + ".position"]; move = $('.move', el); $.on(move, 'touchstart mousedown', dragstart); ref = move.children; @@ -10706,7 +14009,7 @@ UI = (function() { $.on(d, 'click CloseMenu', this.close); $.on(d, 'scroll', this.setPosition); $.on(window, 'resize', this.setPosition); - $.add(button, menu); + $.after(button, menu); this.setPosition(); entry = $('.entry', menu); this.focus(entry); @@ -10739,8 +14042,8 @@ UI = (function() { if (!entry.open(data)) { return; } - } catch (_error) { - err = _error; + } catch (error) { + err = error; Main.handleErrors({ message: "Error in building the " + this.type + " menu.", error: err @@ -10943,11 +14246,11 @@ UI = (function() { var bottom, clientX, clientY, left, right, style, top; clientX = e.clientX, clientY = e.clientY; left = clientX - this.dx; - left = left < 10 ? 0 : this.width - left < 10 ? null : left / this.screenWidth * 100 + '%'; + left = left < 10 ? 0 : this.width - left < 10 ? '' : left / this.screenWidth * 100 + '%'; top = clientY - this.dy; - top = top < (10 + this.topBorder) ? this.topBorder + 'px' : this.height - top < (10 + this.bottomBorder) ? null : top / this.screenHeight * 100 + '%'; - right = left === null ? 0 : null; - bottom = top === null ? this.bottomBorder + 'px' : null; + top = top < (10 + this.topBorder) ? this.topBorder + 'px' : this.height - top < (10 + this.bottomBorder) ? '' : top / this.screenHeight * 100 + '%'; + right = left === '' ? 0 : ''; + bottom = top === '' ? this.bottomBorder + 'px' : ''; style = this.style; style.left = left; style.right = right; @@ -10979,8 +14282,9 @@ UI = (function() { }; hoverstart = function(arg) { - var cb, el, endEvents, height, latestEvent, noRemove, o, ref, root; - root = arg.root, el = arg.el, latestEvent = arg.latestEvent, endEvents = arg.endEvents, height = arg.height, cb = arg.cb, noRemove = arg.noRemove; + var cb, el, endEvents, height, latestEvent, noRemove, o, rect, ref, root, width; + root = arg.root, el = arg.el, latestEvent = arg.latestEvent, endEvents = arg.endEvents, height = arg.height, width = arg.width, cb = arg.cb, noRemove = arg.noRemove; + rect = root.getBoundingClientRect(); o = { root: root, el: el, @@ -10992,7 +14296,10 @@ UI = (function() { clientHeight: doc.clientHeight, clientWidth: doc.clientWidth, height: height, - noRemove: noRemove + width: width, + noRemove: noRemove, + clientX: (rect.left + rect.right) / 2, + clientY: (rect.top + rect.bottom) / 2 }; o.hover = hover.bind(o); o.hoverend = hoverend.bind(o); @@ -11020,16 +14327,22 @@ UI = (function() { hoverstart.padding = 25; hover = function(e) { - var clientX, clientY, height, left, ref, right, style, threshold, top; + var clientX, clientY, height, left, marginX, ref, ref1, right, style, threshold, top, width; this.latestEvent = e; height = (this.height || this.el.offsetHeight) + hoverstart.padding; - clientX = e.clientX, clientY = e.clientY; + width = this.width || this.el.offsetWidth; + ref = Conf['Follow Cursor'] ? e : this, clientX = ref.clientX, clientY = ref.clientY; top = this.isImage ? Math.max(0, clientY * (this.clientHeight - height) / this.clientHeight) : Math.max(0, Math.min(this.clientHeight - height, clientY - 120)); threshold = this.clientWidth / 2; if (!this.isImage) { threshold = Math.max(threshold, this.clientWidth - 400); } - ref = clientX <= threshold ? [clientX + 45 + 'px', null] : [null, this.clientWidth - clientX + 45 + 'px'], left = ref[0], right = ref[1]; + marginX = (clientX <= threshold ? clientX : this.clientWidth - clientX) + 45; + if (this.isImage) { + marginX = Math.min(marginX, this.clientWidth - width); + } + marginX += 'px'; + ref1 = clientX <= threshold ? [marginX, ''] : ['', marginX], left = ref1[0], right = ref1[1]; style = this.style; style.top = top + 'px'; style.left = left; @@ -11067,13 +14380,15 @@ UI = (function() { return label; }; - return { + UI = { dialog: dialog, Menu: Menu, hover: hoverstart, checkbox: checkbox }; + return UI; + }).call(this); FappeTyme = (function() { @@ -11082,7 +14397,7 @@ FappeTyme = (function() { FappeTyme = { init: function() { var el, i, indicator, lc, len, ref, ref1, type; - if (!((Conf['Fappe Tyme'] || Conf['Werk Tyme']) && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { + if (!((Conf['Fappe Tyme'] || Conf['Werk Tyme']) && ((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive'))) { return; } this.nodes = {}; @@ -11115,7 +14430,7 @@ FappeTyme = (function() { }); $.on(indicator, 'click', function() { var check; - check = FappeTyme.nodes[this.parentNode.id.replace('shortcut-', '')]; + check = $.getOwn(FappeTyme.nodes, this.parentNode.id.replace('shortcut-', '')); check.checked = !check.checked; return $.event('change', null, check); }); @@ -11134,11 +14449,11 @@ FappeTyme = (function() { }); }, node: function() { - return this.nodes.root.classList.toggle('noFile', !this.file); + return this.nodes.root.classList.toggle('noFile', !this.files.length); }, catalogNode: function() { var file, filename; - file = this.thread.OP.file; + file = this.thread.OP.files[0]; if (!file) { return; } @@ -11170,7 +14485,7 @@ Gallery = (function() { Gallery = { init: function() { var el, ref; - if (!(this.enabled = Conf['Gallery'] && ((ref = g.VIEW) === 'index' || ref === 'thread') && g.BOARD.ID !== 'f')) { + if (!(this.enabled = Conf['Gallery'] && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { return; } this.delay = Conf['Slide Delay']; @@ -11188,20 +14503,28 @@ Gallery = (function() { }); }, node: function() { - var ref; - if (!((ref = this.file) != null ? ref.thumb : void 0)) { - return; - } - if (Gallery.nodes) { - Gallery.generateThumb(this); - Gallery.nodes.total.textContent = Gallery.images.length; - } - if (!Conf['Image Expansion']) { - return $.on(this.file.thumb.parentNode, 'click', Gallery.cb.image); + var file, i, len, ref, results; + ref = this.files; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + if (!file.thumb) { + continue; + } + if (Gallery.nodes) { + Gallery.generateThumb(this, file); + Gallery.nodes.total.textContent = Gallery.images.length; + } + if (!(Conf['Image Expansion'] || (g.SITE.software === 'tinyboard' && Main.jsEnabled))) { + results.push($.on(file.thumbLink, 'click', Gallery.cb.image)); + } else { + results.push(void 0); + } } + return results; }, build: function(image) { - var candidate, cb, dialog, entry, file, i, j, key, len, len1, menuButton, nodes, post, ref, ref1, ref2, ref3, thumb, value; + var candidate, cb, dialog, entry, file, i, j, k, key, len, len1, len2, menuButton, nodes, post, postThumb, ref, ref1, ref2, ref3, thumb, value; cb = Gallery.cb; if (Conf['Fullscreen Gallery']) { $.one(d, 'fullscreenchange mozfullscreenchange webkitfullscreenchange', function() { @@ -11216,20 +14539,19 @@ Gallery = (function() { } Gallery.images = []; nodes = Gallery.nodes = {}; - Gallery.fullIDs = {}; + Gallery.fileIDs = $.dict(); Gallery.slideshow = false; nodes.el = dialog = $.el('div', { id: 'a-gallery' }); - $.extend(dialog, { - innerHTML: "
      " - }); + $.extend(dialog, {innerHTML: "
      "}); ref = { buttons: '.gal-buttons', frame: '.gal-image', name: '.gal-name', count: '.count', total: '.total', + sauce: '.gal-sauce', thumbs: '.gal-thumbnails', next: '.gal-image a', current: '.gal-image img' @@ -11265,18 +14587,24 @@ Gallery = (function() { $.off(d, 'keydown', Keybinds.keydown); } $.on(window, 'resize', Gallery.cb.setHeight); - ref2 = $$('.post .file'); + ref2 = $$(g.SITE.selectors.file.thumb); for (j = 0, len1 = ref2.length; j < len1; j++) { - file = ref2[j]; - post = Get.postFromNode(file); - if (!((ref3 = post.file) != null ? ref3.thumb : void 0)) { + postThumb = ref2[j]; + if (!(post = Get.postFromNode(postThumb))) { continue; } - Gallery.generateThumb(post); - if (!image && Gallery.fullIDs[post.fullID]) { - candidate = post.file.thumb.parentNode; - if (Header.getTopOf(candidate) + candidate.getBoundingClientRect().height >= 0) { - image = candidate; + ref3 = post.files; + for (k = 0, len2 = ref3.length; k < len2; k++) { + file = ref3[k]; + if (!file.thumb) { + continue; + } + Gallery.generateThumb(post, file); + if (!image && Gallery.fileIDs[post.fullID + "." + file.index]) { + candidate = file.thumbLink; + if (Header.getTopOf(candidate) + candidate.getBoundingClientRect().height >= 0) { + image = candidate; + } } } } @@ -11294,27 +14622,28 @@ Gallery = (function() { doc.style.overflow = 'hidden'; return nodes.total.textContent = Gallery.images.length; }, - generateThumb: function(post) { + generateThumb: function(post, file) { var thumb, thumbImg; if (post.isClone || post.isHidden) { return; } - if (!(post.file && post.file.thumb && (post.file.isImage || post.file.isVideo || Conf['PDF in Gallery']))) { + if (!(file && file.thumb && (file.isImage || file.isVideo || Conf['PDF in Gallery']))) { return; } - if (Gallery.fullIDs[post.fullID]) { + if (Gallery.fileIDs[post.fullID + "." + file.index]) { return; } - Gallery.fullIDs[post.fullID] = true; + Gallery.fileIDs[post.fullID + "." + file.index] = true; thumb = $.el('a', { className: 'gal-thumb', - href: post.file.url, + href: file.url, target: '_blank', - title: post.file.name + title: file.name }); thumb.dataset.id = Gallery.images.length; thumb.dataset.post = post.fullID; - thumbImg = post.file.thumb.cloneNode(false); + thumb.dataset.file = file.index; + thumbImg = file.thumb.cloneNode(false); thumbImg.style.cssText = ''; $.add(thumb, thumbImg); $.on(thumb, 'click', Gallery.cb.open); @@ -11324,20 +14653,20 @@ Gallery = (function() { load: function(thumb, errorCB) { var elType, ext, file; ext = thumb.href.match(/\w*$/); - elType = { + elType = $.getOwn({ 'webm': 'video', + 'mp4': 'video', + 'ogv': 'video', 'pdf': 'iframe' - }[ext] || 'img'; - file = $.el(elType, { - title: thumb.title - }); + }, ext) || 'img'; + file = $.el(elType); $.extend(file.dataset, thumb.dataset); $.on(file, 'error', errorCB); file.src = thumb.href; return file; }, open: function(thumb) { - var el, file, newID, nodes, oldID, post, ref; + var el, file, i, len, link, newID, node, nodes, oldID, post, ref, ref1, sauces; nodes = Gallery.nodes; oldID = +nodes.current.dataset.id; newID = +thumb.dataset.id; @@ -11374,12 +14703,24 @@ Gallery = (function() { nodes.name.href = thumb.href; nodes.frame.scrollTop = 0; nodes.next.focus(); + $.rmAll(nodes.sauce); + if (Conf['Sauce'] && Sauce.links && (post = g.posts.get(file.dataset.post))) { + sauces = []; + ref1 = Sauce.links; + for (i = 0, len = ref1.length; i < len; i++) { + link = ref1[i]; + if ((node = Sauce.createSauceLink(link, post, post.files[+file.dataset.file]))) { + sauces.push($.tn(' '), node); + } + } + $.add(nodes.sauce, sauces); + } if (Gallery.slideshow && (newID > oldID || (oldID === Gallery.images.length - 1 && newID === 0))) { Gallery.setupTimer(); } else { Gallery.cb.stop(); } - if (Conf['Scroll to Post'] && (post = g.posts[file.dataset.post])) { + if (Conf['Scroll to Post'] && (post = g.posts.get(file.dataset.post))) { Header.scrollTo(post.nodes.root); } if (isNaN(oldID) || newID === (oldID + 1) % Gallery.images.length) { @@ -11387,19 +14728,21 @@ Gallery = (function() { } }, error: function() { - var ref; + var file, post, ref; if (((ref = this.error) != null ? ref.code : void 0) === MediaError.MEDIA_ERR_DECODE) { return new Notice('error', 'Corrupt or unplayable video', 30); } - if (this.src.split('/')[2] !== 'i.4cdn.org') { + if (ImageCommon.isFromArchive(this)) { return; } - return ImageCommon.error(this, g.posts[this.dataset.post], null, (function(_this) { + post = g.posts.get(this.dataset.post); + file = post.files[+this.dataset.file]; + return ImageCommon.error(this, post, file, null, (function(_this) { return function(url) { if (!url) { return; } - Gallery.images[_this.dataset.id].href = url; + Gallery.images[+_this.dataset.id].href = url; if (Gallery.nodes.current === _this) { return _this.src = url; } @@ -11454,17 +14797,22 @@ Gallery = (function() { case Conf['Close']: case Conf['Open Gallery']: return Gallery.cb.close; - case 'Right': + case Conf['Next Gallery Image']: return Gallery.cb.next; - case 'Enter': + case Conf['Advance Gallery']: return Gallery.cb.advance; - case 'Left': - case '': + case Conf['Previous Gallery Image']: return Gallery.cb.prev; case Conf['Pause']: return Gallery.cb.pause; case Conf['Slideshow']: return Gallery.cb.toggleSlideshow; + case Conf['Rotate image anticlockwise']: + return Gallery.cb.rotateLeft; + case Conf['Rotate image clockwise']: + return Gallery.cb.rotateRight; + case Conf['Download Gallery Image']: + return Gallery.cb.download; } })(); if (!cb) { @@ -11518,6 +14866,11 @@ Gallery = (function() { toggleSlideshow: function() { return Gallery.cb[Gallery.slideshow ? 'stop' : 'start'](); }, + download: function() { + var name; + name = $('.gal-name'); + return name.click(); + }, pause: function() { var current; Gallery.cb.stop(); @@ -11544,6 +14897,22 @@ Gallery = (function() { $.rmClass(Gallery.nodes.buttons, 'gal-playing'); return Gallery.slideshow = false; }, + rotateLeft: function() { + return Gallery.cb.rotate(270); + }, + rotateRight: function() { + return Gallery.cb.rotate(90); + }, + rotate: $.debounce(100, function(delta) { + var current; + current = Gallery.nodes.current; + if (current.nodeName === 'IFRAME') { + return; + } + current.dataRotate = ((current.dataRotate || 0) + delta) % 360; + current.style.transform = "rotate(" + current.dataRotate + "deg)"; + return Gallery.cb.setHeight(); + }), close: function() { $.off(Gallery.nodes.current, 'error', Gallery.error); ImageCommon.pause(Gallery.nodes.current); @@ -11559,7 +14928,7 @@ Gallery = (function() { } } delete Gallery.nodes; - delete Gallery.fullIDs; + delete Gallery.fileIDs; doc.style.overflow = ''; $.off(d, 'keydown', Gallery.cb.keybinds); if (Conf['Keybinds']) { @@ -11572,16 +14941,29 @@ Gallery = (function() { return (this.checked ? $.addClass : $.rmClass)(doc, "gal-" + (this.name.toLowerCase().replace(/\s+/g, '-'))); }, setHeight: $.debounce(100, function() { - var current, dim, frame, height, minHeight, ref, ref1, ref2, style, width; + var containerHeight, containerWidth, current, dim, frame, height, margin, minHeight, ref, ref1, ref2, ref3, style, width; ref = Gallery.nodes, current = ref.current, frame = ref.frame; style = current.style; - if (Conf['Stretch to Fit'] && (dim = (ref1 = g.posts[current.dataset.post]) != null ? ref1.file.dimensions : void 0)) { + if (Conf['Stretch to Fit'] && (dim = (ref1 = g.posts.get(current.dataset.post)) != null ? ref1.files[+current.dataset.file].dimensions : void 0)) { ref2 = dim.split('x'), width = ref2[0], height = ref2[1]; - minHeight = Math.min(doc.clientHeight - 25, height / width * frame.clientWidth); + containerWidth = frame.clientWidth; + containerHeight = doc.clientHeight - 25; + if ((current.dataRotate || 0) % 180 === 90) { + ref3 = [containerHeight, containerWidth], containerWidth = ref3[0], containerHeight = ref3[1]; + } + minHeight = Math.min(containerHeight, height / width * containerWidth); style.minHeight = minHeight + 'px'; - return style.minWidth = (width / height * minHeight) + 'px'; + style.minWidth = (width / height * minHeight) + 'px'; } else { - return style.minHeight = style.minWidth = null; + style.minHeight = style.minWidth = ''; + } + if ((current.dataRotate || 0) % 180 === 90) { + style.maxWidth = Conf['Fit Height'] ? (doc.clientHeight - 25) + "px" : 'none'; + style.maxHeight = Conf['Fit Width'] ? frame.clientWidth + "px" : 'none'; + margin = (current.clientWidth - current.clientHeight) / 2; + return style.margin = margin + "px " + (-margin) + "px"; + } else { + return style.maxWidth = style.maxHeight = style.margin = ''; } }), setDelay: function() { @@ -11632,9 +15014,7 @@ Gallery = (function() { } return results; })(); - delayLabel = $.el('label', { - innerHTML: "Slide Delay: " - }); + delayLabel = $.el('label', {innerHTML: "Slide Delay: "}); delayInput = delayLabel.firstElementChild; delayInput.value = Gallery.delay; $.on(delayInput, 'change', Gallery.cb.setDelay); @@ -11652,7 +15032,8 @@ Gallery = (function() { }).call(this); ImageCommon = (function() { - var ImageCommon; + var ImageCommon, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; ImageCommon = { pause: function(video) { @@ -11663,6 +15044,17 @@ ImageCommon = (function() { $.off(video, 'volumechange', Volume.change); return video.muted = true; }, + rewind: function(el) { + if (el.nodeName === 'VIDEO') { + if (el.readyState >= el.HAVE_METADATA) { + return el.currentTime = 0; + } + } else if (/\.gif$/.test(el.src)) { + return $.queueTask(function() { + return el.src = el.src; + }); + } + }, pushCache: function(el) { ImageCommon.cache = el; return $.on(el, 'error', ImageCommon.cacheError); @@ -11679,74 +15071,95 @@ ImageCommon = (function() { return delete ImageCommon.cache; } }, - decodeError: function(file, post) { + decodeError: function(file, fileObj) { var message, ref; if (((ref = file.error) != null ? ref.code : void 0) !== MediaError.MEDIA_ERR_DECODE) { return false; } - if (!(message = $('.warning', post.file.thumb.parentNode))) { + if (!(message = $('.warning', fileObj.thumb.parentNode))) { message = $.el('div', { className: 'warning' }); - $.after(post.file.thumb, message); + $.after(fileObj.thumb, message); } message.textContent = 'Error: Corrupt or unplayable video'; return true; }, - error: function(file, post, delay, cb) { - var URL, redirect, src, timeoutID; - src = post.file.url.split('/'); - URL = Redirect.to('file', { - boardID: post.board.ID, - filename: src[src.length - 1] - }); - if (!(Conf['404 Redirect'] && URL && Redirect.securityCheck(URL))) { - URL = null; + isFromArchive: function(file) { + return g.SITE.software === 'yotsuba' && !ImageHost.test(file.src.split('/')[2]); + }, + error: function(file, post, fileObj, delay, cb) { + var base, parseJSON, redirect, src, threadJSON, timeoutID, url; + src = fileObj.url.split('/'); + url = null; + if (g.SITE.software === 'yotsuba' && Conf['404 Redirect']) { + url = Redirect.to('file', { + boardID: post.board.ID, + filename: src[src.length - 1] + }); + } + if (!(url && Redirect.securityCheck(url))) { + url = null; } - if ((post.isDead || post.file.isDead) && file.src.split('/')[2] === 'i.4cdn.org') { - return cb(URL); + if ((post.isDead || fileObj.isDead) && !ImageCommon.isFromArchive(file)) { + return cb(url); } if (delay != null) { timeoutID = setTimeout((function() { - return cb(URL); + return cb(url); }), delay); } - if (post.isDead || post.file.isDead) { + if (post.isDead || fileObj.isDead) { return; } redirect = function() { - if (file.src.split('/')[2] === 'i.4cdn.org') { + if (!ImageCommon.isFromArchive(file)) { if (delay != null) { clearTimeout(timeoutID); } - return cb(URL); + return cb(url); + } + }; + threadJSON = typeof (base = g.SITE.urls).threadJSON === "function" ? base.threadJSON(post) : void 0; + if (!threadJSON) { + return; + } + parseJSON = function(isArchiveURL) { + var archivedThreadJSON, base1, i, len, postObj, ref, ref1; + if (this.status === 404) { + if (!isArchiveURL && (archivedThreadJSON = typeof (base1 = g.SITE.urls).archivedThreadJSON === "function" ? base1.archivedThreadJSON(post) : void 0)) { + $.ajax(archivedThreadJSON, { + onloadend: function() { + return parseJSON.call(this, true); + } + }); + } else { + post.kill(!post.isClone, fileObj.index); + } + } + if (this.status !== 200) { + return redirect(); + } + ref = this.response.posts; + for (i = 0, len = ref.length; i < len; i++) { + postObj = ref[i]; + if (postObj.no === post.ID) { + break; + } + } + if (postObj.no !== post.ID) { + post.kill(); + return redirect(); + } else if (ref1 = fileObj.docIndex, indexOf.call(g.SITE.Build.parseJSON(postObj, post.board).filesDeleted, ref1) >= 0) { + post.kill(true); + return redirect(); + } else { + return url = fileObj.url; } }; - return $.ajax("//a.4cdn.org/" + post.board + "/thread/" + post.thread + ".json", { - onload: function() { - var i, len, postObj, ref; - if (this.status === 404) { - post.kill(!post.isClone); - } - if (this.status !== 200) { - return redirect(); - } - ref = this.response.posts; - for (i = 0, len = ref.length; i < len; i++) { - postObj = ref[i]; - if (postObj.no === post.ID) { - break; - } - } - if (postObj.no !== post.ID) { - post.kill(); - return redirect(); - } else if (postObj.filedeleted) { - post.kill(true); - return redirect(); - } else { - return URL = post.file.url; - } + return $.ajax(threadJSON, { + onloadend: function() { + return parseJSON.call(this); } }); }, @@ -11768,12 +15181,12 @@ ImageCommon = (function() { return (Conf['Show Controls'] && Conf['Click Passthrough'] && e.target.nodeName === 'VIDEO') || (e.target.controls && e.target.getBoundingClientRect().bottom - e.clientY < 35); }, download: function(e) { - var download, href; + var download, href, ref; if (this.protocol === 'blob:') { return true; } e.preventDefault(); - href = this.href, download = this.download; + ref = this, href = ref.href, download = ref.download; return CrossOrigin.file(href, function(blob) { var a; if (blob) { @@ -11803,7 +15216,7 @@ ImageExpand = (function() { ImageExpand = { init: function() { var ref; - if (!(this.enabled = Conf['Image Expansion'] && ((ref = g.VIEW) === 'index' || ref === 'thread') && g.BOARD.ID !== 'f')) { + if (!(this.enabled = Conf['Image Expansion'] && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { return; } this.EAI = $.el('a', { @@ -11818,9 +15231,7 @@ ImageExpand = (function() { this.videoControls = $.el('span', { className: 'video-controls' }); - $.extend(this.videoControls, { - innerHTML: " contract" - }); + $.extend(this.videoControls, {innerHTML: " contract"}); return Callbacks.Post.push({ name: 'Image Expansion', cb: this.node @@ -11831,7 +15242,7 @@ ImageExpand = (function() { if (!(this.file && (this.file.isImage || this.file.isVideo))) { return; } - $.on(this.file.thumb.parentNode, 'click', ImageExpand.cb.toggle); + $.on(this.file.thumbLink, 'click', ImageExpand.cb.toggle); if (this.isClone) { if (this.file.isExpanding) { ImageExpand.contract(this); @@ -11848,7 +15259,7 @@ ImageExpand = (function() { cb: { toggle: function(e) { var file, post, ref; - if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { + if ($.modifiedClick(e)) { return; } post = Get.postFromNode(this); @@ -11864,15 +15275,16 @@ ImageExpand = (function() { } }, toggleAll: function() { - var func, toggle; + var func, threadRoot, toggle; $.event('CloseMenu'); + threadRoot = Nav.getThread(); toggle = function(post) { var file; file = post.file; if (!(file && (file.isImage || file.isVideo) && doc.contains(post.nodes.root))) { return; } - if (ImageExpand.on && (!Conf['Expand spoilers'] && file.isSpoiler || !Conf['Expand videos'] && file.isVideo || Conf['Expand from here'] && Header.getTopOf(file.thumb) < 0)) { + if (ImageExpand.on && (!Conf['Expand spoilers'] && file.isSpoiler || !Conf['Expand videos'] && file.isVideo || Conf['Expand from here'] && Header.getTopOf(file.thumb) < 0 || Conf['Expand thread only'] && g.VIEW === 'index' && !(threadRoot != null ? threadRoot.contains(file.thumb) : void 0))) { return; } return $.queueTask(func, post); @@ -11953,8 +15365,8 @@ ImageExpand = (function() { $.rmClass(post.nodes.root, 'expanded-image'); $.rmClass(file.thumb, 'expanding'); $.rm(file.videoControls); - file.thumb.parentNode.href = file.url; - file.thumb.parentNode.target = '_blank'; + file.thumbLink.href = file.url; + file.thumbLink.target = '_blank'; ref = ['isExpanding', 'isExpanded', 'videoControls', 'wasPlaying', 'scrollIntoView']; for (i = 0, len = ref.length; i < len; i++) { x = ref[i]; @@ -11983,6 +15395,9 @@ ImageExpand = (function() { $.off(el, eventName, cb); } } + if (Conf['Restart when Opened']) { + ImageCommon.rewind(file.thumb); + } delete file.fullImage; return $.queueTask(function() { if (file.isExpanding || file.isExpanded) { @@ -11996,9 +15411,9 @@ ImageExpand = (function() { }); }, expand: function(post, src) { - var el, file, isVideo, ref, thumb; + var el, file, isVideo, ref, thumb, thumbLink; file = post.file; - thumb = file.thumb, isVideo = file.isVideo; + thumb = file.thumb, thumbLink = file.thumbLink, isVideo = file.isVideo; if (post.isHidden || file.isExpanding || file.isExpanded) { return; } @@ -12006,25 +15421,28 @@ ImageExpand = (function() { file.isExpanding = true; if (file.fullImage) { el = file.fullImage; - } else if (((ref = ImageCommon.cache) != null ? ref.dataset.fullID : void 0) === post.fullID) { + } else if (((ref = ImageCommon.cache) != null ? ref.dataset.fileID : void 0) === (post.fullID + "." + file.index)) { el = file.fullImage = ImageCommon.popCache(); $.on(el, 'error', ImageExpand.error); + if (Conf['Restart when Opened'] && el.id !== 'ihover') { + ImageCommon.rewind(el); + } el.removeAttribute('id'); } else { el = file.fullImage = $.el((isVideo ? 'video' : 'img')); - el.dataset.fullID = post.fullID; + el.dataset.fileID = post.fullID + "." + file.index; $.on(el, 'error', ImageExpand.error); el.src = src || file.url; } el.className = 'full-image'; $.after(thumb, el); if (isVideo) { - if (Conf['Show Controls'] && Conf['Click Passthrough'] && !file.videoControls) { + if (!file.videoControls) { file.videoControls = ImageExpand.videoControls.cloneNode(true); $.add(file.text, file.videoControls); } - thumb.parentNode.removeAttribute('href'); - thumb.parentNode.removeAttribute('target'); + thumbLink.removeAttribute('href'); + thumbLink.removeAttribute('target'); el.loop = true; Volume.setup(el); ImageExpand.setupVideoCB(post); @@ -12109,7 +15527,7 @@ ImageExpand = (function() { } }, mouseout: function(e) { - if (mousedown && e.clientX <= this.getBoundingClientRect().left) { + if (((e.buttons & 1) || mousedown) && e.clientX <= this.getBoundingClientRect().left) { return ImageExpand.toggle(Get.postFromNode(this)); } } @@ -12136,13 +15554,13 @@ ImageExpand = (function() { if (!(post.file.isExpanding || post.file.isExpanded)) { return; } - if (ImageCommon.decodeError(this, post)) { + if (ImageCommon.decodeError(this, post.file)) { return ImageExpand.contract(post); } - if (this.src.split('/')[2] !== 'i.4cdn.org') { + if (ImageCommon.isFromArchive(this)) { return ImageExpand.contract(post); } - return ImageCommon.error(this, post, 10 * $.SECOND, function(URL) { + return ImageCommon.error(this, post, post.file, 10 * $.SECOND, function(URL) { if (post.file.isExpanding || post.file.isExpanded) { ImageExpand.contract(post); if (URL) { @@ -12195,6 +15613,68 @@ ImageExpand = (function() { }).call(this); +ImageHost = (function() { + var ImageHost; + + ImageHost = { + init: function() { + var ref; + if (!((this.useFaster = /\S/.test(Conf['fourchanImageHost'])) && g.SITE.software === 'yotsuba' && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { + return; + } + return Callbacks.Post.push({ + name: 'Image Host Rewriting', + cb: this.node + }); + }, + suggestions: ['i.4cdn.org', 'is2.4chan.org'], + host: function() { + return Conf['fourchanImageHost'].trim() || 'i.4cdn.org'; + }, + flashHost: function() { + return 'i.4cdn.org'; + }, + thumbHost: function() { + return 'i.4cdn.org'; + }, + test: function(hostname) { + return hostname === 'i.4cdn.org' || ImageHost.regex.test(hostname); + }, + regex: /^is\d*\.4chan(?:nel)?\.org$/, + node: function() { + var host; + if (this.isClone) { + return; + } + host = ImageHost.host(); + if (this.file && ImageHost.test(this.file.url.split('/')[2]) && !/\.swf$/.test(this.file.url)) { + this.file.link.hostname = host; + if (this.file.thumbLink) { + this.file.thumbLink.hostname = host; + } + this.file.url = this.file.link.href; + } + return ImageHost.fixLinks($$('a', this.nodes.comment)); + }, + fixLinks: function(links) { + var host, i, len, link; + for (i = 0, len = links.length; i < len; i++) { + link = links[i]; + if (!(ImageHost.test(link.hostname) && !/\.swf$/.test(link.pathname))) { + continue; + } + host = ImageHost.host(); + if (link.hostname !== host) { + link.hostname = host; + } + } + } + }; + + return ImageHost; + +}).call(this); + ImageHover = (function() { var ImageHover; @@ -12218,40 +15698,49 @@ ImageHover = (function() { } }, node: function() { - if (!(this.file && (this.file.isImage || this.file.isVideo))) { - return; + var file, i, len, ref, results; + ref = this.files; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + if ((file.isImage || file.isVideo) && file.thumb) { + results.push($.on(file.thumb, 'mouseover', ImageHover.mouseover(this, file))); + } } - return $.on(this.file.thumb, 'mouseover', ImageHover.mouseover(this)); + return results; }, catalogNode: function() { var file; - file = this.thread.OP.file; + file = this.thread.OP.files[0]; if (!(file && (file.isImage || file.isVideo))) { return; } - return $.on(this.nodes.thumb, 'mouseover', ImageHover.mouseover(this.thread.OP)); + return $.on(this.nodes.thumb, 'mouseover', ImageHover.mouseover(this.thread.OP, file)); }, - mouseover: function(post) { + mouseover: function(post, file) { return function(e) { - var el, error, file, height, isVideo, left, maxHeight, maxWidth, ref, ref1, ref2, right, scale, width, x; + var base, el, error, height, isVideo, maxHeight, maxWidth, ref, ref1, scale, width, x; if (!doc.contains(this)) { return; } - file = post.file; isVideo = file.isVideo; - if (file.isExpanding || file.isExpanded) { + if (file.isExpanding || file.isExpanded || (typeof (base = g.SITE).isThumbExpanded === "function" ? base.isThumbExpanded(file) : void 0)) { return; } - error = ImageHover.error(post); - if (((ref = ImageCommon.cache) != null ? ref.dataset.fullID : void 0) === post.fullID) { + error = ImageHover.error(post, file); + if (((ref = ImageCommon.cache) != null ? ref.dataset.fileID : void 0) === (post.fullID + "." + file.index)) { el = ImageCommon.popCache(); $.on(el, 'error', error); } else { el = $.el((isVideo ? 'video' : 'img')); - el.dataset.fullID = post.fullID; + el.dataset.fileID = post.fullID + "." + file.index; $.on(el, 'error', error); el.src = file.url; } + if (Conf['Restart when Opened']) { + ImageCommon.rewind(el); + ImageCommon.rewind(this); + } el.id = 'ihover'; $.add(Header.hover, el); if (isVideo) { @@ -12265,28 +15754,32 @@ ImageHover = (function() { } } } - ref1 = (function() { - var i, len, ref1, results; - ref1 = file.dimensions.split('x'); - results = []; - for (i = 0, len = ref1.length; i < len; i++) { - x = ref1[i]; - results.push(+x); - } - return results; - })(), width = ref1[0], height = ref1[1]; - ref2 = this.getBoundingClientRect(), left = ref2.left, right = ref2.right; - maxWidth = Math.max(left, doc.clientWidth - right); - maxHeight = doc.clientHeight - UI.hover.padding; - scale = Math.min(1, maxWidth / width, maxHeight / height); - el.style.maxWidth = (scale * width) + "px"; - el.style.maxHeight = (scale * height) + "px"; + if (file.dimensions) { + ref1 = (function() { + var i, len, ref1, results; + ref1 = file.dimensions.split('x'); + results = []; + for (i = 0, len = ref1.length; i < len; i++) { + x = ref1[i]; + results.push(+x); + } + return results; + })(), width = ref1[0], height = ref1[1]; + maxWidth = doc.clientWidth; + maxHeight = doc.clientHeight - UI.hover.padding; + scale = Math.min(1, maxWidth / width, maxHeight / height); + width *= scale; + height *= scale; + el.style.maxWidth = width + "px"; + el.style.maxHeight = height + "px"; + } return UI.hover({ root: this, el: el, latestEvent: e, endEvents: 'mouseout click', - height: scale * height, + height: height, + width: width, noRemove: true, cb: function() { $.off(el, 'error', error); @@ -12298,12 +15791,12 @@ ImageHover = (function() { }); }; }, - error: function(post) { + error: function(post, file) { return function() { - if (ImageCommon.decodeError(this, post)) { + if (ImageCommon.decodeError(this, file)) { return; } - return ImageCommon.error(this, post, 3 * $.SECOND, (function(_this) { + return ImageCommon.error(this, post, file, 3 * $.SECOND, (function(_this) { return function(URL) { if (URL) { return _this.src = URL + (_this.src === URL ? '?' + Date.now() : ''); @@ -12326,11 +15819,12 @@ ImageLoader = (function() { ImageLoader = { init: function() { - var prefetch, ref; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && g.BOARD.ID !== 'f')) { + var el, ref, ref1, replace; + if ((ref = g.VIEW) !== 'index' && ref !== 'thread' && ref !== 'archive') { return; } - if (!(Conf['Image Prefetching'] || Conf['Replace JPG'] || Conf['Replace PNG'] || Conf['Replace GIF'] || Conf['Replace WEBM'])) { + replace = Conf['Replace JPG'] || Conf['Replace PNG'] || Conf['Replace GIF'] || Conf['Replace WEBM']; + if (!(Conf['Image Prefetching'] || replace)) { return; } Callbacks.Post.push({ @@ -12338,36 +15832,41 @@ ImageLoader = (function() { cb: this.node }); $.on(d, 'PostsInserted', function() { - return g.posts.forEach(ImageLoader.prefetch); + if (ImageLoader.prefetchEnabled || replace) { + return g.posts.forEach(ImageLoader.prefetchAll); + } }); if (Conf['Replace WEBM']) { $.on(d, 'scroll visibilitychange 4chanXInitFinished PostsInserted', this.playVideos); } - if (!Conf['Image Prefetching']) { + if (!(Conf['Image Prefetching'] && ((ref1 = g.VIEW) === 'index' || ref1 === 'thread'))) { return; } - prefetch = $.el('label', { - innerHTML: " Prefetch Images" - }); - this.el = prefetch.firstElementChild; - $.on(this.el, 'change', this.toggle); - return Header.menu.addEntry({ - el: prefetch, - order: 98 + el = $.el('a', { + href: 'javascript:;', + title: 'Prefetch Images', + className: 'fa fa-bolt disabled', + textContent: 'Prefetch' }); + $.on(el, 'click', this.toggle); + return Header.addShortcut('prefetch', el, 525); }, node: function() { - if (this.isClone || !this.file) { + var file, i, len, ref; + if (this.isClone) { return; } - if (Conf['Replace WEBM'] && this.file.isVideo) { - ImageLoader.replaceVideo(this); + ref = this.files; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + if (Conf['Replace WEBM'] && file.isVideo) { + ImageLoader.replaceVideo(this, file); + } + ImageLoader.prefetch(this, file); } - return ImageLoader.prefetch(this); }, - replaceVideo: function(post) { - var attr, file, i, len, ref, thumb, video; - file = post.file; + replaceVideo: function(post, file) { + var attr, i, len, ref, thumb, video; thumb = file.thumb; video = $.el('video', { preload: 'none', @@ -12389,19 +15888,25 @@ ImageLoader = (function() { file.thumb = video; return file.videoThumb = true; }, - prefetch: function(post) { - var clone, el, file, i, isImage, isVideo, len, match, ref, replace, thumb, type, url; - file = post.file; - if (!file) { - return; - } + prefetch: function(post, file) { + var clone, el, i, isImage, isVideo, len, ref, ref1, replace, thumb, type, url; isImage = file.isImage, isVideo = file.isVideo, thumb = file.thumb, url = file.url; if (file.isPrefetched || !(isImage || isVideo) || post.isHidden || post.thread.isHidden) { return; } - type = (match = url.match(/\.([^.]+)$/)[1].toUpperCase()) === 'JPEG' ? 'JPG' : match; + if (isVideo) { + type = 'WEBM'; + } else { + type = (ref = url.match(/\.([^.]+)$/)) != null ? ref[1].toUpperCase() : void 0; + if (type === 'JPEG') { + type = 'JPG'; + } + } replace = Conf["Replace " + type] && !/spoiler/.test(thumb.src || thumb.dataset.src); - if (!(replace || Conf['prefetch'])) { + if (!(replace || ImageLoader.prefetchEnabled)) { + return; + } + if ($.hasClass(doc, 'catalog-mode')) { return; } if (![post].concat(slice.call(post.clones)).some(function(clone) { @@ -12411,9 +15916,9 @@ ImageLoader = (function() { } file.isPrefetched = true; if (file.videoThumb) { - ref = post.clones; - for (i = 0, len = ref.length; i < len; i++) { - clone = ref[i]; + ref1 = post.clones; + for (i = 0, len = ref1.length; i < len; i++) { + clone = ref1[i]; clone.file.thumb.preload = 'auto'; } thumb.preload = 'auto'; @@ -12425,41 +15930,57 @@ ImageLoader = (function() { return; } el = $.el(isImage ? 'img' : 'video'); + if (isVideo) { + el.preload = 'auto'; + } if (replace && isImage) { $.on(el, 'load', function() { - var j, len1, ref1; - ref1 = post.clones; - for (j = 0, len1 = ref1.length; j < len1; j++) { - clone = ref1[j]; + var j, len1, ref2; + ref2 = post.clones; + for (j = 0, len1 = ref2.length; j < len1; j++) { + clone = ref2[j]; clone.file.thumb.src = url; } - thumb.src = url; - return thumb.removeAttribute('data-src'); + return thumb.src = url; }); } return el.src = url; }, + prefetchAll: function(post) { + var file, i, len, ref; + ref = post.files; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + ImageLoader.prefetch(post, file); + } + }, toggle: function() { - if (Conf['prefetch'] = this.checked) { - g.posts.forEach(ImageLoader.prefetch); + ImageLoader.prefetchEnabled = !ImageLoader.prefetchEnabled; + this.classList.toggle('disabled', !ImageLoader.prefetchEnabled); + if (ImageLoader.prefetchEnabled) { + g.posts.forEach(ImageLoader.prefetchAll); } }, playVideos: function() { var qpClone, ref; qpClone = (ref = $.id('qp')) != null ? ref.firstElementChild : void 0; return g.posts.forEach(function(post) { - var i, len, ref1, ref2, thumb; + var file, i, j, len, len1, ref1, ref2, thumb; ref1 = [post].concat(slice.call(post.clones)); for (i = 0, len = ref1.length; i < len; i++) { post = ref1[i]; - if (!((ref2 = post.file) != null ? ref2.videoThumb : void 0)) { - continue; - } - thumb = post.file.thumb; - if (Header.isNodeVisible(thumb) || post.nodes.root === qpClone) { - thumb.play(); - } else { - thumb.pause(); + ref2 = post.files; + for (j = 0, len1 = ref2.length; j < len1; j++) { + file = ref2[j]; + if (!file.videoThumb) { + continue; + } + thumb = file.thumb; + if (Header.isNodeVisible(thumb) || post.nodes.root === qpClone) { + thumb.play(); + } else { + thumb.pause(); + } } } }); @@ -12476,7 +15997,7 @@ Metadata = (function() { Metadata = { init: function() { var ref; - if (!(Conf['WEBM Metadata'] && ((ref = g.VIEW) === 'index' || ref === 'thread') && g.BOARD.ID !== 'f')) { + if (!(Conf['WEBM Metadata'] && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { return; } return Callbacks.Post.push({ @@ -12485,29 +16006,34 @@ Metadata = (function() { }); }, node: function() { - var el; - if (!(this.file && /webm$/i.test(this.file.url))) { - return; - } - if (this.isClone) { - el = $('.webm-title', this.file.text); - } else { - el = $.el('span', { - className: 'webm-title' - }); - $.extend(el, { - innerHTML: "" - }); - $.add(this.file.text, [$.tn(' '), el]); - } - if (el.children.length === 1) { - return $.one(el.lastElementChild, 'mouseover focus', Metadata.load); + var el, file, i, j, len1, ref; + ref = this.files; + for (i = j = 0, len1 = ref.length; j < len1; i = ++j) { + file = ref[i]; + if (!(/webm$/i.test(file.url))) { + continue; + } + if (this.isClone) { + el = $('.webm-title', file.text); + } else { + el = $.el('span', { + className: 'webm-title' + }); + el.dataset.index = i; + $.extend(el, {innerHTML: ""}); + $.add(file.text, [$.tn(' '), el]); + } + if (el.children.length === 1) { + $.one(el.lastElementChild, 'mouseover focus', Metadata.load); + } } }, load: function() { + var index; $.rmClass(this.parentNode, 'error'); $.addClass(this.parentNode, 'loading'); - return CrossOrigin.binary(Get.postFromNode(this).file.url, (function(_this) { + index = this.parentNode.dataset.index; + return CrossOrigin.binary(Get.postFromNode(this).files[+index].url, (function(_this) { return function(data) { var output, title; $.rmClass(_this.parentNode, 'loading'); @@ -12577,7 +16103,7 @@ RevealSpoilers = (function() { RevealSpoilers = { init: function() { var ref; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Reveal Spoiler Thumbnails'])) { + if (!(((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Reveal Spoiler Thumbnails'])) { return; } return Callbacks.Post.push({ @@ -12586,17 +16112,24 @@ RevealSpoilers = (function() { }); }, node: function() { - var thumb; - if (!(!this.isClone && this.file && this.file.thumb && this.file.isSpoiler)) { + var file, i, len, ref, thumb; + if (this.isClone) { return; } - thumb = this.file.thumb; - thumb.removeAttribute('style'); - thumb.style.maxHeight = thumb.style.maxWidth = this.isReply ? '125px' : '250px'; - if (thumb.src) { - return thumb.src = this.file.thumbURL; - } else { - return thumb.dataset.src = this.file.thumbURL; + ref = this.files; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + if (!(file.thumb && file.isSpoiler)) { + continue; + } + thumb = file.thumb; + thumb.removeAttribute('style'); + thumb.style.maxHeight = thumb.style.maxWidth = this.isReply ? '125px' : '250px'; + if (thumb.src) { + thumb.src = file.thumbURL; + } else { + thumb.dataset.src = file.thumbURL; + } } } }; @@ -12611,20 +16144,17 @@ Sauce = (function() { Sauce = { init: function() { - var err, j, len, link, links, ref, ref1; + var j, len, link, linkData, links, ref, ref1; if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Sauce'])) { return; } + $.addClass(doc, 'show-sauce'); links = []; ref1 = Conf['sauces'].split('\n'); for (j = 0, len = ref1.length; j < len; j++) { link = ref1[j]; - try { - if (link[0] !== '#') { - links.push(link.trim()); - } - } catch (_error) { - err = _error; + if (link[0] !== '#' && (linkData = this.parseLink(link))) { + links.push(linkData); } } if (!links.length) { @@ -12640,13 +16170,13 @@ Sauce = (function() { cb: this.node }); }, - createSauceLink: function(link, post) { - var a, ext, i, j, key, len, m, part, parts, ref, ref1, ref2, skip; + parseLink: function(link) { + var err, i, j, len, m, part, parts, ref, ref1, regexp; if (!(link = link.trim())) { return null; } - parts = {}; - ref = link.split(/;(?=(?:text|boards|types|sandbox):?)/); + parts = $.dict(); + ref = link.split(/;(?=(?:text|boards|types|regexp|sandbox):?)/); for (i = j = 0, len = ref.length; j < len; i = ++j) { part = ref[i]; if (i === 0) { @@ -12657,15 +16187,55 @@ Sauce = (function() { } } parts['text'] || (parts['text'] = ((ref1 = parts['url'].match(/(\w+)\.\w+\//)) != null ? ref1[1] : void 0) || '?'); - ext = post.file.url.match(/[^.]*$/)[0]; - skip = false; - for (key in parts) { - parts[key] = parts[key].replace(/%(T?URL|IMG|[sh]?MD5|board|name|%|semi)/g, function(_, parameter) { + if ('boards' in parts) { + parts['boards'] = Filter.parseBoards(parts['boards']); + } + if ('regexp' in parts) { + try { + if ((regexp = parts['regexp'].match(/^\/(.*)\/(\w*)$/))) { + parts['regexp'] = RegExp(regexp[1], regexp[2]); + } else { + parts['regexp'] = RegExp(parts['regexp']); + } + } catch (error) { + err = error; + new Notice('warning', [$.tn("Invalid regexp for Sauce link:"), $.el('br'), $.tn(link), $.el('br'), $.tn(err.message)], 60); + return null; + } + } + return parts; + }, + createSauceLink: function(link, post, file) { + var a, base, ext, j, key, len, matches, missing, parts, ref; + ext = file.url.match(/[^.]*$/)[0]; + parts = $.dict(); + $.extend(parts, link); + if (!(!parts['boards'] || parts['boards'][post.siteID + "/" + post.boardID] || parts['boards'][post.siteID + "/*"])) { + return null; + } + if (!(!parts['types'] || indexOf.call(parts['types'].split(','), ext) >= 0)) { + return null; + } + if (!(!parts['regexp'] || (matches = file.name.match(parts['regexp'])))) { + return null; + } + missing = []; + ref = ['url', 'text']; + for (j = 0, len = ref.length; j < len; j++) { + key = ref[j]; + parts[key] = parts[key].replace(/%(T?URL|IMG|[sh]?MD5|board|name|%|semi|\$\d+)/g, function(orig, parameter) { var type; - type = Sauce.formatters[parameter](post, ext); - if (type == null) { - skip = true; - return ''; + if (parameter[0] === '$') { + if (!matches) { + return orig; + } + type = matches[parameter.slice(1)] || ''; + } else { + type = Sauce.formatters[parameter](post, file, ext); + if (type == null) { + missing.push(parameter); + return ''; + } } if (key === 'url' && (parameter !== '%' && parameter !== 'semi')) { if (/^javascript:/i.test(parts['url'])) { @@ -12676,13 +16246,14 @@ Sauce = (function() { return type; }); } - if (skip) { - return null; - } - if (!(!parts['boards'] || (ref2 = post.board.ID, indexOf.call(parts['boards'].split(','), ref2) >= 0))) { - return null; + if ((typeof (base = g.SITE).areMD5sDeferred === "function" ? base.areMD5sDeferred(post.board) : void 0) && missing.length && !missing.filter(function(x) { + return !/^.?MD5$/.test(x); + }).length) { + a = Sauce.link.cloneNode(false); + a.dataset.skip = '1'; + return a; } - if (!(!parts['types'] || indexOf.call(parts['types'].split(','), ext) >= 0)) { + if (missing.length) { return null; } a = Sauce.link.cloneNode(false); @@ -12694,62 +16265,69 @@ Sauce = (function() { return a; }, node: function() { - var j, len, link, node, nodes, observer, ref, skipped; - if (this.isClone || !this.file) { + var file, j, len, ref; + if (this.isClone) { return; } + ref = this.files; + for (j = 0, len = ref.length; j < len; j++) { + file = ref[j]; + Sauce.file(this, file); + } + }, + file: function(post, file) { + var j, len, link, node, nodes, observer, ref, skipped; nodes = []; skipped = []; ref = Sauce.links; for (j = 0, len = ref.length; j < len; j++) { link = ref[j]; - if (!(node = Sauce.createSauceLink(link, this))) { - node = Sauce.link.cloneNode(false); - skipped.push([link, node]); - } - nodes.push($.tn(' '), node); - } - $.add(this.file.text, nodes); - if (this.board.ID === 'f') { - observer = new MutationObserver((function(_this) { - return function() { - var k, len1, node2, ref1; - if (_this.file.text.dataset.md5) { - for (k = 0, len1 = skipped.length; k < len1; k++) { - ref1 = skipped[k], link = ref1[0], node = ref1[1]; - if ((node2 = Sauce.createSauceLink(link, _this))) { - $.replace(node, node2); - } + if ((node = Sauce.createSauceLink(link, post, file))) { + nodes.push($.tn(' '), node); + if (node.dataset.skip) { + skipped.push([link, node]); + } + } + } + $.add(file.text, nodes); + if (skipped.length) { + observer = new MutationObserver(function() { + var k, len1, node2, ref1; + if (file.text.dataset.md5) { + for (k = 0, len1 = skipped.length; k < len1; k++) { + ref1 = skipped[k], link = ref1[0], node = ref1[1]; + if ((node2 = Sauce.createSauceLink(link, post, file))) { + $.replace(node, node2); } - return observer.disconnect(); } - }; - })(this)); - return observer.observe(this.file.text, { + return observer.disconnect(); + } + }); + return observer.observe(file.text, { attributes: true }); } }, formatters: { - TURL: function(post) { - return post.file.thumbURL; + TURL: function(post, file) { + return file.thumbURL; }, - URL: function(post) { - return post.file.url; + URL: function(post, file) { + return file.url; }, - IMG: function(post, ext) { - if (ext === 'gif' || ext === 'jpg' || ext === 'png') { - return post.file.url; + IMG: function(post, file, ext) { + if (ext === 'gif' || ext === 'jpg' || ext === 'jpeg' || ext === 'png') { + return file.url; } else { - return post.file.thumbURL; + return file.thumbURL; } }, - MD5: function(post) { - return post.file.MD5; + MD5: function(post, file) { + return file.MD5; }, - sMD5: function(post) { + sMD5: function(post, file) { var ref; - return (ref = post.file.MD5) != null ? ref.replace(/[+\/=]/g, function(c) { + return (ref = file.MD5) != null ? ref.replace(/[+\/=]/g, function(c) { return { '+': '-', '/': '_', @@ -12757,12 +16335,12 @@ Sauce = (function() { }[c]; }) : void 0; }, - hMD5: function(post) { + hMD5: function(post, file) { var c; - if (post.file.MD5) { + if (file.MD5) { return ((function() { var j, len, ref, results; - ref = atob(post.file.MD5); + ref = atob(file.MD5); results = []; for (j = 0, len = ref.length; j < len; j++) { c = ref[j]; @@ -12775,8 +16353,8 @@ Sauce = (function() { board: function(post) { return post.board.ID; }, - name: function(post) { - return post.file.name; + name: function(post, file) { + return file.name; }, '%': function() { return '%'; @@ -12796,7 +16374,7 @@ Volume = (function() { Volume = { init: function() { - var ref, ref1, unmuteEntry, volumeEntry; + var base, ref, unmuteEntry, volumeEntry; if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && (Conf['Image Expansion'] || Conf['Image Hover'] || Conf['Image Hover in Catalog'] || Conf['Gallery']))) { return; } @@ -12816,7 +16394,7 @@ Volume = (function() { cb: this.node }); } - if ((ref1 = g.BOARD.ID) !== 'gif' && ref1 !== 'wsg') { + if (typeof (base = g.SITE).noAudio === "function" ? base.noAudio(g.BOARD) : void 0) { return; } if (Conf['Mouse Wheel Volume']) { @@ -12830,9 +16408,7 @@ Volume = (function() { volumeEntry = $.el('label', { title: 'Default volume for videos.' }); - $.extend(volumeEntry, { - innerHTML: " Volume" - }); + $.extend(volumeEntry, {innerHTML: " Volume"}); this.inputs = { unmute: unmuteEntry.firstElementChild, volume: volumeEntry.firstElementChild @@ -12854,8 +16430,8 @@ Volume = (function() { return $.on(video, 'volumechange', Volume.change); }, change: function() { - var items, key, muted, val, volume; - muted = this.muted, volume = this.volume; + var items, key, muted, ref, val, volume; + ref = this, muted = ref.muted, volume = ref.volume; items = { 'Allow Sound': !muted, 'Default Volume': volume @@ -12874,16 +16450,25 @@ Volume = (function() { } }, node: function() { - var ref, ref1; - if (!(((ref = this.board.ID) === 'gif' || ref === 'wsg') && ((ref1 = this.file) != null ? ref1.isVideo : void 0))) { + var base, file, i, len, ref; + if (typeof (base = g.SITE).noAudio === "function" ? base.noAudio(this.board) : void 0) { return; } - $.on(this.file.thumb, 'wheel', Volume.wheel.bind(Header.hover)); - return $.on($('a', this.file.text), 'wheel', Volume.wheel.bind(this.file.thumb.parentNode)); + ref = this.files; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + if (!file.isVideo) { + continue; + } + if (file.thumb) { + $.on(file.thumb, 'wheel', Volume.wheel.bind(Header.hover)); + } + $.on($('.file-info', file.text) || file.link, 'wheel', Volume.wheel.bind(file.thumbLink)); + } }, catalogNode: function() { var file; - file = this.thread.OP.file; + file = this.thread.OP.files[0]; if (!(file != null ? file.isVideo : void 0)) { return; } @@ -12917,34 +16502,47 @@ Volume = (function() { }).call(this); Embedding = (function() { - var Embedding; + var Embedding, + slice = [].slice; Embedding = { init: function() { - var j, len, ref, type; - if (!(Conf['Embedding'] || Conf['Link Title'])) { + var j, len, ref, ref1, type; + if (!(((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Linkify'] && (Conf['Embedding'] || Conf['Link Title'] || Conf['Cover Preview']))) { return; } - this.types = {}; - ref = this.ordered_types; - for (j = 0, len = ref.length; j < len; j++) { - type = ref[j]; + this.types = $.dict(); + ref1 = this.ordered_types; + for (j = 0, len = ref1.length; j < len; j++) { + type = ref1[j]; this.types[type.key] = type; } - if (Conf['Floating Embeds']) { - this.dialog = UI.dialog('embedding', 'top: 50px; right: 0px;', { - innerHTML: "
      " - }); + if (Conf['Embedding'] && g.VIEW !== 'archive') { + this.dialog = UI.dialog('embedding', {innerHTML: "
      "}); this.media = $('#media-embed', this.dialog); $.one(d, '4chanXInitFinished', this.ready); + $.on(d, 'IndexRefreshInternal', function() { + return g.posts.forEach(function(post) { + var embed, k, l, len1, len2, ref2, ref3; + ref2 = [post].concat(slice.call(post.clones)); + for (k = 0, len1 = ref2.length; k < len1; k++) { + post = ref2[k]; + ref3 = post.nodes.embedlinks; + for (l = 0, len2 = ref3.length; l < len2; l++) { + embed = ref3[l]; + Embedding.cb.catalogRemove.call(embed); + } + } + }); + }); } if (Conf['Link Title']) { return $.on(d, '4chanXInitFinished PostsInserted', function() { - var key, ref1, ref2, service; - ref1 = Embedding.types; - for (key in ref1) { - service = ref1[key]; - if ((ref2 = service.title) != null ? ref2.batchSize : void 0) { + var key, ref2, ref3, service; + ref2 = Embedding.types; + for (key in ref2) { + service = ref2[key]; + if ((ref3 = service.title) != null ? ref3.batchSize : void 0) { Embedding.flushTitles(service.title); } } @@ -12952,22 +16550,33 @@ Embedding = (function() { } }, events: function(post) { - var el, i, items; - if (!Conf['Embedding']) { + var data, el, i, items; + if (g.VIEW === 'archive') { return; } - i = 0; - items = $$('.embedder', post.nodes.comment); - while (el = items[i++]) { - $.on(el, 'click', Embedding.cb.toggle); - if ($.hasClass(el, 'embedded')) { - Embedding.cb.toggle.call(el); + if (Conf['Embedding']) { + i = 0; + items = post.nodes.embedlinks = $$('.embedder', post.nodes.comment); + while (el = items[i++]) { + $.on(el, 'click', Embedding.cb.click); + if ($.hasClass(el, 'embedded')) { + Embedding.cb.toggle.call(el); + } + } + } + if (Conf['Cover Preview']) { + i = 0; + items = $$('.linkify', post.nodes.comment); + while (el = items[i++]) { + if ((data = Embedding.services(el))) { + Embedding.preview(data); + } } } }, process: function(link, post) { var data; - if (!(Conf['Embedding'] || Conf['Link Title'])) { + if (!(Conf['Embedding'] || Conf['Link Title'] || Conf['Cover Preview'])) { return; } if ($.x('ancestor::pre', link)) { @@ -12975,11 +16584,14 @@ Embedding = (function() { } if (data = Embedding.services(link)) { data.post = post; - if (Conf['Embedding']) { + if (Conf['Embedding'] && g.VIEW !== 'archive') { Embedding.embed(data); } if (Conf['Link Title']) { - return Embedding.title(data); + Embedding.title(data); + } + if (Conf['Cover Preview'] && g.VIEW !== 'archive') { + return Embedding.preview(data); } } }, @@ -13003,15 +16615,11 @@ Embedding = (function() { var embed, href, key, link, name, options, post, ref, uid, value; key = data.key, uid = data.uid, options = data.options, link = data.link, post = data.post; href = link.href; - if (Embedding.types[key].httpOnly && location.protocol !== 'http:') { - return; - } $.addClass(link, key.toLowerCase()); embed = $.el('a', { className: 'embedder', - href: 'javascript:;', - textContent: '(embed)' - }); + href: 'javascript:;' + }, {innerHTML: "(unembed)"}); ref = { key: key, uid: uid, @@ -13022,17 +16630,21 @@ Embedding = (function() { value = ref[name]; embed.dataset[name] = value; } - $.on(embed, 'click', Embedding.cb.toggle); + $.on(embed, 'click', Embedding.cb.click); $.after(link, [$.tn(' '), embed]); + post.nodes.embedlinks.push(embed); if (Conf['Auto-embed'] && !Conf['Floating Embeds'] && !post.isFetchedQuote) { - return $.asap((function() { - return doc.contains(embed); - }), function() { + if ($.hasClass(doc, 'catalog-mode')) { + return $.addClass(embed, 'embed-removed'); + } else { return Embedding.cb.toggle.call(embed); - }); + } } }, ready: function() { + if (!Main.isThisPageLegit()) { + return; + } $.addClass(Embedding.dialog, 'empty'); $.on($('.close', Embedding.dialog), 'click', Embedding.closeFloat); $.on($('.move', Embedding.dialog), 'mousedown', Embedding.dragEmbed); @@ -13054,12 +16666,12 @@ Embedding = (function() { if (Embedding.dragEmbed.mouseup) { $.off(d, 'mouseup', Embedding.dragEmbed); Embedding.dragEmbed.mouseup = false; - style.visibility = ''; + style.pointerEvents = ''; return; } $.on(d, 'mouseup', Embedding.dragEmbed); Embedding.dragEmbed.mouseup = true; - return style.visibility = 'hidden'; + return style.pointerEvents = 'none'; }, title: function(data) { var key, link, options, post, service, uid; @@ -13074,19 +16686,13 @@ Embedding = (function() { return Embedding.flushTitles(service); } } else { - if (!$.cache(service.api(uid), (function() { + return CrossOrigin.cache(service.api(uid), (function() { return Embedding.cb.title(this, data); - }), { - responseType: 'json' - })) { - return $.extend(link, { - innerHTML: "[" + E(key) + "] Title Link Blocked (are you using NoScript?)" - }); - } + })); } }, flushTitles: function(service) { - var cb, data, j, len, queue; + var cb, data, queue; queue = service.queue; if (!(queue != null ? queue.length : void 0)) { return; @@ -13099,7 +16705,7 @@ Embedding = (function() { Embedding.cb.title(this, data); } }; - if (!$.cache(service.api((function() { + return CrossOrigin.cache(service.api((function() { var j, len, results; results = []; for (j = 0, len = queue.length; j < len; j++) { @@ -13107,61 +16713,98 @@ Embedding = (function() { results.push(data.uid); } return results; - })()), cb, { - responseType: 'json' - })) { - for (j = 0, len = queue.length; j < len; j++) { - data = queue[j]; - $.extend(data.link, { - innerHTML: "[" + E(data.key) + "] Title Link Blocked (are you using NoScript?)" - }); - } + })()), cb); + }, + preview: function(data) { + var key, link, service, uid; + key = data.key, uid = data.uid, link = data.link; + if (!(service = Embedding.types[key].preview)) { + return; } + return $.on(link, 'mouseover', function(e) { + var el, height, src; + src = service.url(uid); + height = service.height; + el = $.el('img', { + src: src, + id: 'ihover' + }); + $.add(Header.hover, el); + return UI.hover({ + root: link, + el: el, + latestEvent: e, + endEvents: 'mouseout click', + height: height + }); + }); }, cb: { - toggle: function(e) { + click: function(e) { var div; - if (e != null) { - e.preventDefault(); - } - if (Conf['Floating Embeds']) { + e.preventDefault(); + if (!$.hasClass(this, 'embedded') && (Conf['Floating Embeds'] || $.hasClass(doc, 'catalog-mode'))) { if (!(div = Embedding.media.firstChild)) { return; } $.replace(div, Embedding.cb.embed(this)); Embedding.lastEmbed = Get.postFromNode(this).nodes.root; - $.rmClass(Embedding.dialog, 'empty'); - return; + return $.rmClass(Embedding.dialog, 'empty'); + } else { + return Embedding.cb.toggle.call(this); } + }, + toggle: function() { if ($.hasClass(this, "embedded")) { $.rm(this.nextElementSibling); - this.textContent = '(embed)'; } else { $.after(this, Embedding.cb.embed(this)); - this.textContent = '(unembed)'; } return $.toggleClass(this, 'embedded'); }, embed: function(a) { var container, el, type; - container = $.el('div'); + container = $.el('div', { + className: 'media-embed' + }); $.add(container, el = (type = Embedding.types[a.dataset.key]).el(a)); el.style.cssText = type.style != null ? type.style : 'border: none; width: 640px; height: 360px;'; return container; }, + catalogRemove: function() { + var isCatalog; + isCatalog = $.hasClass(doc, 'catalog-mode'); + if ((isCatalog && $.hasClass(this, 'embedded')) || (!isCatalog && $.hasClass(this, 'embed-removed'))) { + Embedding.cb.toggle.call(this); + return $.toggleClass(this, 'embed-removed'); + } + }, title: function(req, data) { var base1, j, k, key, len, len1, link, link2, options, post, post2, ref, ref1, service, status, text, uid; key = data.key, uid = data.uid, options = data.options, link = data.link, post = data.post; - status = req.status; service = Embedding.types[key].title; + status = req.status; + if ((status === 200 || status === 304) && service.status) { + status = service.status(req.response)[0]; + } + if (!status) { + return; + } text = "[" + key + "] " + ((function() { switch (status) { case 200: case 304: - return service.text(req.response, uid); + text = service.text(req.response, uid); + if (typeof text === 'string') { + return text; + } else { + return text = link.textContent; + } + break; case 404: return "Not Found"; case 403: + case 401: return "Forbidden or Private"; default: return status + "'d"; @@ -13189,7 +16832,7 @@ Embedding = (function() { ordered_types: [ { key: 'audio', - regExp: /^[^?#]+\.(?:mp3|oga|wav)(?:[?#]|$)/i, + regExp: /^[^?#]+\.(?:mp3|m4a|oga|wav|flac)(?:[?#]|$)/i, style: '', el: function(a) { return $.el('audio', { @@ -13200,12 +16843,10 @@ Embedding = (function() { } }, { key: 'image', - regExp: /^[^?#]+\.(?:gif|png|jpg|jpeg|bmp)(?:[?#]|$)/i, + regExp: /^[^?#]+\.(?:gif|png|jpg|jpeg|bmp|webp)(?::\w+)?(?:[?#]|$)/i, style: '', el: function(a) { - return $.el('div', { - innerHTML: "" - }); + return $.el('div', {innerHTML: ""}); } }, { key: 'video', @@ -13218,7 +16859,7 @@ Embedding = (function() { controls: true, preload: 'auto', src: a.dataset.href, - loop: /^https?:\/\/i\.4cdn\.org\//.test(a.dataset.href) + loop: ImageHost.test(a.dataset.href.split('/')[2]) }); $.on(el, 'loadedmetadata', function() { if (el.videoHeight === 0 && el.parentNode) { @@ -13230,19 +16871,45 @@ Embedding = (function() { return el; } }, { - key: 'Clyp', - regExp: /^\w+:\/\/(?:www\.)?clyp\.it\/(\w+)/, - style: '', + key: 'PeerTube', + regExp: /^(\w+:\/\/[^\/]+\/videos\/watch\/\w{8}-\w{4}-\w{4}-\w{4}-\w{12})(.*)/, + el: function(a) { + var el, options, start; + options = (start = a.dataset.options.match(/[?&](start=\w+)/)) ? "?" + start[1] : ''; + el = $.el('iframe', { + src: a.dataset.uid.replace('/videos/watch/', '/videos/embed/') + options + }); + el.setAttribute("allowfullscreen", "true"); + return el; + } + }, { + key: 'BitChute', + regExp: /^\w+:\/\/(?:www\.)?bitchute\.com\/video\/([\w\-]+)/, el: function(a) { - var el, type; - el = $.el('audio', { - controls: true, - preload: 'auto' + var el; + el = $.el('iframe', { + src: "https://www.bitchute.com/embed/" + a.dataset.uid + "/" }); - type = el.canPlayType('audio/ogg') ? 'ogg' : 'mp3'; - el.src = "https://clyp.it/" + a.dataset.uid + "." + type; + el.setAttribute("allowfullscreen", "true"); return el; } + }, { + key: 'Clyp', + regExp: /^\w+:\/\/(?:www\.)?clyp\.it\/(\w{8})/, + style: 'border: 0; width: 640px; height: 160px;', + el: function(a) { + return $.el('iframe', { + src: "https://clyp.it/" + a.dataset.uid + "/widget" + }); + }, + title: { + api: function(uid) { + return "https://api.clyp.it/oembed?url=https://clyp.it/" + uid; + }, + text: function(_) { + return _.title; + } + } }, { key: 'Dailymotion', regExp: /^\w+:\/\/(?:(?:www\.)?dailymotion\.com\/(?:embed\/)?video|dai\.ly)\/([A-Za-z0-9]+)[^?]*(.*)/, @@ -13262,29 +16929,50 @@ Embedding = (function() { text: function(_) { return _.title; } + }, + preview: { + url: function(uid) { + return "https://www.dailymotion.com/thumbnail/video/" + uid; + }, + height: 240 } }, { key: 'Gfycat', regExp: /^\w+:\/\/(?:www\.)?gfycat\.com\/(?:iframe\/)?(\w+)/, el: function(a) { - var div; - return div = $.el('iframe', { - src: "//gfycat.com/iframe/" + a.dataset.uid + var el; + el = $.el('iframe', { + src: "//gfycat.com/ifr/" + a.dataset.uid }); + el.setAttribute("allowfullscreen", "true"); + return el; } }, { key: 'Gist', regExp: /^\w+:\/\/gist\.github\.com\/[\w\-]+\/(\w+)/, - el: function(a) { - var content, el; - el = $.el('iframe'); - el.setAttribute('sandbox', 'allow-scripts'); - content = { - innerHTML: "" + E(a.dataset.uid) + "" + style: '', + el: (function() { + var counter; + counter = 0; + return function(a) { + var el; + el = $.el('pre', { + hidden: true, + id: "gist-embed-" + (counter++) + }); + CrossOrigin.cache("https://api.github.com/gists/" + a.dataset.uid, function() { + el.textContent = Object.values(this.response.files)[0].content; + el.className = 'prettyprint'; + $.global(function() { + return typeof window.prettyPrint === "function" ? window.prettyPrint((function() {}), document.getElementById(document.currentScript.dataset.id).parentNode) : void 0; + }, { + id: el.id + }); + return el.hidden = false; + }); + return el; }; - el.src = E.url(content); - return el; - }, + })(), title: { api: function(uid) { return "https://api.github.com/gists/" + uid; @@ -13309,19 +16997,18 @@ Embedding = (function() { } }, { key: 'LiveLeak', - regExp: /^\w+:\/\/(?:\w+\.)?liveleak\.com\/.*\?.*i=(\w+)/, - httpOnly: true, + regExp: /^\w+:\/\/(?:\w+\.)?liveleak\.com\/.*\?.*[tif]=(\w+)/, el: function(a) { var el; el = $.el('iframe', { - src: "http://www.liveleak.com/ll_embed?i=" + a.dataset.uid + src: "https://www.liveleak.com/e/" + a.dataset.uid }); el.setAttribute("allowfullscreen", "true"); return el; } }, { key: 'Loopvid', - regExp: /^\w+:\/\/(?:www\.)?loopvid.appspot.com\/#?((?:pf|kd|lv|gd|gh|db|dx|nn|cp|wu|ig|ky|mf|m2|pc|1c|pi|wl|ko|gc)\/[\w\-\/]+(?:,[\w\-\/]+)*|fc\/\w+\/\d+|https?:\/\/.+)/, + regExp: /^\w+:\/\/(?:www\.)?loopvid.appspot.com\/#?((?:pf|kd|lv|gd|gh|db|dx|nn|cp|wu|ig|ky|mf|m2|pc|1c|pi|ni|wl|ko|mm|ic|gc)\/[\w\-\/]+(?:,[\w\-\/]+)*|fc\/\w+\/\d+|https?:\/\/.+)/, style: 'max-width: 80vw; max-height: 80vh;', el: function(a) { var _, base, el, host, j, k, l, len, len1, len2, name, names, ref, ref1, type, types, url, urls; @@ -13360,9 +17047,9 @@ Embedding = (function() { case 'pf': return ["https://kastden.org/_loopvid_media/pf/" + base, "https://web.archive.org/web/2/http://a.pomf.se/" + base]; case 'kd': - return ["http://kastden.org/loopvid/" + base]; + return ["https://kastden.org/loopvid/" + base]; case 'lv': - return ["http://lv.kastden.org/" + base]; + return ["https://lv.kastden.org/" + base]; case 'gd': return ["https://docs.google.com/uc?export=download&id=" + base]; case 'gh': @@ -13372,7 +17059,7 @@ Embedding = (function() { case 'dx': return ["https://dl.dropboxusercontent.com/" + base]; case 'nn': - return ["http://naenara.eu/loopvids/" + base]; + return ["https://kastden.org/_loopvid_media/nn/" + base]; case 'cp': return ["https://copy.com/" + base]; case 'wu': @@ -13380,23 +17067,29 @@ Embedding = (function() { case 'ig': return ["https://i.imgur.com/" + base]; case 'ky': - return ["https://kiyo.me/" + base]; + return ["https://kastden.org/_loopvid_media/ky/" + base]; case 'mf': return ["https://kastden.org/_loopvid_media/mf/" + base, "https://web.archive.org/web/2/https://d.maxfile.ro/" + base]; case 'm2': return ["https://kastden.org/_loopvid_media/m2/" + base]; case 'pc': - return ["http://a.pomf.cat/" + base]; + return ["https://kastden.org/_loopvid_media/pc/" + base, "https://web.archive.org/web/2/http://a.pomf.cat/" + base]; case '1c': return ["http://b.1339.cf/" + base]; case 'pi': - return ["https://u.pomf.is/" + base]; + return ["https://kastden.org/_loopvid_media/pi/" + base, "https://web.archive.org/web/2/https://u.pomf.is/" + base]; + case 'ni': + return ["https://kastden.org/_loopvid_media/ni/" + base, "https://web.archive.org/web/2/https://u.nya.is/" + base]; case 'wl': return ["http://webm.land/media/" + base]; case 'ko': return ["https://kordy.kastden.org/loopvid/" + base]; + case 'mm': + return ["https://kastden.org/_loopvid_media/mm/" + base, "https://web.archive.org/web/2/https://my.mixtape.moe/" + base]; + case 'ic': + return ["https://media.8ch.net/file_store/" + base]; case 'fc': - return ["//i.4cdn.org/" + base + ".webm"]; + return ["//" + (ImageHost.host()) + "/" + base + ".webm"]; case 'gc': return ["https://" + type + ".gfycat.com/" + name + ".webm"]; } @@ -13413,15 +17106,15 @@ Embedding = (function() { } }, { key: 'Openings.moe', - regExp: /^\w+:\/\/openings.moe\/\?video=([^&=]+\.webm)/, - style: 'max-width: 80vw; max-height: 80vh;', + regExp: /^\w+:\/\/openings.moe\/\?video=([^.&=]+)/, + style: 'width: 1280px; height: 720px; max-width: 80vw; max-height: 80vh;', el: function(a) { - return $.el('video', { - controls: true, - preload: 'auto', - src: "//openings.moe/video/" + a.dataset.uid, - loop: true + var el; + el = $.el('iframe', { + src: "https://openings.moe/?video=" + a.dataset.uid }); + el.setAttribute("allowfullscreen", "true"); + return el; } }, { key: 'Pastebin', @@ -13443,7 +17136,7 @@ Embedding = (function() { }, title: { api: function(uid) { - return "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid)); + return location.protocol + "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid)); }, text: function(_) { return _.title; @@ -13455,18 +17148,42 @@ Embedding = (function() { style: 'border: 0; width: 600px; height: 406px;', el: function(a) { return $.el('iframe', { - src: "//www.strawpoll.me/embed_1/" + a.dataset.uid + src: "https://www.strawpoll.me/embed_1/" + a.dataset.uid + }); + } + }, { + key: 'Streamable', + regExp: /^\w+:\/\/(?:www\.)?streamable\.com\/(\w+)/, + el: function(a) { + var el; + el = $.el('iframe', { + src: "https://streamable.com/o/" + a.dataset.uid }); + el.setAttribute("allowfullscreen", "true"); + return el; + }, + title: { + api: function(uid) { + return "https://api.streamable.com/oembed?url=https://streamable.com/" + uid; + }, + text: function(_) { + return _.title; + } } }, { key: 'TwitchTV', - regExp: /^\w+:\/\/(?:www\.|secure\.)?twitch\.tv\/(\w[^#\&\?]*)/, + regExp: /^\w+:\/\/(?:www\.|secure\.|clips\.|m\.)?twitch\.tv\/(\w[^#\&\?]*)/, el: function(a) { var el, m, time, url; - m = a.dataset.uid.match(/(\w+)(?:\/v\/(\d+))?/); - url = "//player.twitch.tv/?" + (m[2] ? "video=v" + m[2] : "channel=" + m[1]) + "&autoplay=false"; - if ((time = a.dataset.href.match(/\bt=(\w+)/))) { - url += "&time=" + time[1]; + m = a.dataset.href.match(/^\w+:\/\/(?:(clips\.)|\w+\.)?twitch\.tv\/(?:\w+\/)?(clip\/)?(\w[^#\&\?]*)/); + if (m[1] || m[2]) { + url = "//clips.twitch.tv/embed?clip=" + m[3] + "&parent=" + location.hostname; + } else { + m = a.dataset.uid.match(/(\w+)(?:\/(?:v\/)?(\d+))?/); + url = "//player.twitch.tv/?" + (m[2] ? "video=v" + m[2] : "channel=" + m[1]) + "&autoplay=false&parent=" + location.hostname; + if ((time = a.dataset.href.match(/\bt=(\w+)/))) { + url += "&time=" + time[1]; + } } el = $.el('iframe', { src: url @@ -13476,11 +17193,45 @@ Embedding = (function() { } }, { key: 'Twitter', - regExp: /^\w+:\/\/(?:www\.)?twitter\.com\/(\w+\/status\/\d+)/, + regExp: /^\w+:\/\/(?:www\.|mobile\.)?twitter\.com\/(\w+\/status\/\d+)/, + style: 'border: none; width: 550px; height: 250px; overflow: hidden; resize: both;', el: function(a) { - return $.el('iframe', { - src: "https://twitframe.com/show?url=https://twitter.com/" + a.dataset.uid + var cont, el, onMessage; + el = $.el('iframe'); + $.on(el, 'load', function() { + return this.contentWindow.postMessage({ + element: 't', + query: 'height' + }, 'https://twitframe.com'); + }); + onMessage = function(e) { + if (e.source === el.contentWindow && e.origin === 'https://twitframe.com') { + $.off(window, 'message', onMessage); + return (cont || el).style.height = (+$.minmax(e.data.height, 250, 0.8 * doc.clientHeight)) + "px"; + } + }; + $.on(window, 'message', onMessage); + el.src = "https://twitframe.com/show?url=https://twitter.com/" + a.dataset.uid; + if ($.engine === 'gecko') { + el.style.cssText = 'border: none; width: 100%; height: 100%;'; + cont = $.el('div'); + $.add(cont, el); + return cont; + } else { + return el; + } + } + }, { + key: 'VidLii', + regExp: /^\w+:\/\/(?:www\.)?vidlii\.com\/watch\?v=(\w{11})/, + style: 'border: none; width: 640px; height: 392px;', + el: function(a) { + var el; + el = $.el('iframe', { + src: "https://www.vidlii.com/embed?v=" + a.dataset.uid + "&a=0" }); + el.setAttribute("allowfullscreen", "true"); + return el; } }, { key: 'Vimeo', @@ -13512,21 +17263,20 @@ Embedding = (function() { } }, { key: 'Vocaroo', - regExp: /^\w+:\/\/(?:www\.)?vocaroo\.com\/i\/(\w+)/, + regExp: /^\w+:\/\/(?:(?:www\.|old\.)?vocaroo\.com|voca\.ro)\/((?:i\/)?\w+)/, style: '', el: function(a) { - var el, type; - el = $.el('audio', { - controls: true, - preload: 'auto' - }); - type = el.canPlayType('audio/webm') ? 'webm' : 'mp3'; - el.src = "http://vocaroo.com/media_command.php?media=" + a.dataset.uid + "&command=download_" + type; + var el; + el = $.el('iframe'); + el.width = 300; + el.height = 60; + el.setAttribute('frameborder', 0); + el.src = "https://vocaroo.com/embed/" + (a.dataset.uid.replace(/^i\//, '')) + "?autoplay=0"; return el; } }, { key: 'YouTube', - regExp: /^\w+:\/\/(?:youtu.be\/|[\w.]*youtube[\w.]*\/.*(?:v=|\bembed\/|\bv\/))([\w\-]{11})(.*)/, + regExp: /^\w+:\/\/(?:youtu.be\/|[\w.]*youtube[\w.]*\/.*(?:v=|\bembed\/|\bv\/|live\/|shorts\/))([\w\-]{11})(.*)/, el: function(a) { var el, start; start = a.dataset.options.match(/\b(?:star)?t\=(\w+)/); @@ -13538,30 +17288,33 @@ Embedding = (function() { start = 3600 * start.match(/(\d+)h/)[1] + 60 * start.match(/(\d+)m/)[1] + 1 * start.match(/(\d+)s/)[1]; } el = $.el('iframe', { - src: "//www.youtube.com/embed/" + a.dataset.uid + "?wmode=opaque" + (start ? '&start=' + start : '') + src: "//www.youtube.com/embed/" + a.dataset.uid + "?rel=0&wmode=opaque" + (start ? '&start=' + start : '') }); el.setAttribute("allowfullscreen", "true"); return el; }, title: { - batchSize: 50, - api: function(uids) { - var ids, key; - ids = encodeURIComponent(uids.join(',')); - key = 'AIzaSyB5_zaen_-46Uhz1xGR-lz1YoUMHqCD6CE'; - return "https://www.googleapis.com/youtube/v3/videos?part=snippet&id=" + ids + "&fields=items%28id%2Csnippet%28title%29%29&key=" + key; + api: function(uid) { + return "https://www.youtube.com/oembed?url=https%3A//www.youtube.com/watch%3Fv%3D" + uid + "&format=json"; }, - text: function(data, uid) { - var item, j, len, ref; - ref = data.items; - for (j = 0, len = ref.length; j < len; j++) { - item = ref[j]; - if (item.id === uid) { - return item.snippet.title; - } + text: function(_) { + return _.title; + }, + status: function(_) { + var m; + if (_.error) { + m = _.error.match(/^(\d*)\s*(.*)/); + return [+m[1], m[2]]; + } else { + return [200, 'OK']; } - return 'Not Found'; } + }, + preview: { + url: function(uid) { + return "https://img.youtube.com/vi/" + uid + "/0.jpg"; + }, + height: 360 } } ] @@ -13577,7 +17330,7 @@ Linkify = (function() { Linkify = { init: function() { var ref; - if (((ref = g.VIEW) !== 'index' && ref !== 'thread') || !Conf['Linkify']) { + if (((ref = g.VIEW) !== 'index' && ref !== 'thread' && ref !== 'archive') || !Conf['Linkify']) { return; } if (Conf['Comment Expansion']) { @@ -13587,38 +17340,37 @@ Linkify = (function() { name: 'Linkify', cb: this.node }); - Callbacks.CatalogThread.push({ - name: 'Linkify', - cb: this.catalogNode - }); return Embedding.init(); }, node: function() { - var j, k, len, len1, link, links, ref; + var base, j, k, len, len1, link, links, ref; if (this.isClone) { return Embedding.events(this); } if (!Linkify.regString.test(this.info.comment)) { return; } - ref = $$('a[href^="http://i.4cdn.org/"], a[href^="https://i.4cdn.org/"]', this.nodes.comment); + ref = $$('a', this.nodes.comment); for (j = 0, len = ref.length; j < len; j++) { link = ref[j]; + if (!(typeof (base = g.SITE).isLinkified === "function" ? base.isLinkified(link) : void 0)) { + continue; + } $.addClass(link, 'linkify'); + if (ImageHost.useFaster) { + ImageHost.fixLinks([link]); + } Embedding.process(link, this); } links = Linkify.process(this.nodes.comment); + if (ImageHost.useFaster) { + ImageHost.fixLinks(links); + } for (k = 0, len1 = links.length; k < len1; k++) { link = links[k]; Embedding.process(link, this); } }, - catalogNode: function() { - if (!Linkify.regString.test(this.thread.OP.info.comment)) { - return; - } - return Linkify.process(this.nodes.comment); - }, process: function(node) { var data, end, endNode, i, index, length, links, part1, part2, ref, ref1, result, saved, snapshot, space, test, word; test = /[^\s"]+/g; @@ -13638,13 +17390,16 @@ Linkify = (function() { if ((length = index + word.length) === data.length) { test.lastIndex = 0; while ((saved = snapshot.snapshotItem(i++))) { - if (saved.nodeName === 'BR') { + if (saved.nodeName === 'BR' || (saved.parentElement.nodeName === 'P' && !saved.previousSibling)) { if ((part1 = word.match(/(https?:\/\/)?([a-z\d-]+\.)*[a-z\d-]+$/i)) && (part2 = (ref = snapshot.snapshotItem(i)) != null ? (ref1 = ref.data) != null ? ref1.match(/^(\.[a-z\d-]+)*\//i) : void 0 : void 0) && (part1[0] + part2[0]).search(Linkify.regString) === 0) { continue; } else { break; } } + if (saved.parentElement.nodeName === "A" && !Linkify.regString.test(word)) { + break; + } endNode = saved; data = saved.data; if (end = space.exec(data)) { @@ -13743,7 +17498,7 @@ ArchiveLink = (function() { ArchiveLink = { init: function() { var div, entry, i, len, ref, ref1, type; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Menu'] && Conf['Archive Link'])) { + if (!(g.SITE.software === 'yotsuba' && ((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Menu'] && Conf['Archive Link'])) { return; } div = $.el('div', { @@ -13751,7 +17506,7 @@ ArchiveLink = (function() { }); entry = { el: div, - order: 90, + order: 60, open: function(arg) { var ID, board, thread; ID = arg.ID, thread = arg.thread, board = arg.board; @@ -13786,14 +17541,15 @@ ArchiveLink = (function() { }); return true; } : function(post) { - var value; - value = type === 'country' ? post.info.flagCode : Filter[type](post); + var ref, typeParam, value; + typeParam = type === 'country' && post.info.flagCodeTroll ? 'troll_country' : type; + value = type === 'country' ? post.info.flagCode || ((ref = post.info.flagCodeTroll) != null ? ref.toLowerCase() : void 0) : Filter.values(type, post)[0]; if (!value) { return false; } el.href = Redirect.to('search', { boardID: post.board.ID, - type: type, + type: typeParam, value: value, isSearch: true }); @@ -13810,11 +17566,54 @@ ArchiveLink = (function() { }).call(this); +CopyTextLink = (function() { + var CopyTextLink; + + CopyTextLink = { + init: function() { + var a, ref; + if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Menu'] && Conf['Copy Text Link'])) { + return; + } + a = $.el('a', { + className: 'copy-text-link', + href: 'javascript:;', + textContent: 'Copy Text' + }); + $.on(a, 'click', CopyTextLink.copy); + return Menu.menu.addEntry({ + el: a, + order: 12, + open: function(post) { + CopyTextLink.text = (post.origin || post).commentOrig(); + return true; + } + }); + }, + copy: function() { + var el; + el = $.el('textarea', { + className: 'copy-text-element', + value: CopyTextLink.text + }); + $.add(d.body, el); + el.select(); + try { + d.execCommand('copy'); + } catch (error) {} + return $.rm(el); + } + }; + + return CopyTextLink; + +}).call(this); + DeleteLink = (function() { var DeleteLink; DeleteLink = { - auto: [{}, {}], + auto: [$.dict(), $.dict()], init: function() { var div, fileEl, fileEntry, postEl, postEntry, ref; if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Menu'] && Conf['Delete Link'])) { @@ -13915,27 +17714,28 @@ DeleteLink = (function() { onlyimgdel: fileOnly, pwd: QR.persona.getPassword() }; - form[post.ID] = 'delete'; + form[+post.ID] = 'delete'; return $.ajax($.id('delform').action.replace("/" + g.BOARD + "/", "/" + post.board + "/"), { responseType: 'document', withCredentials: true, - onload: function() { + onloadend: function() { return DeleteLink.load(link, post, fileOnly, this.response); }, - onerror: function() { - return DeleteLink.error(link, post); - } - }, { form: $.formData(form) }); }, load: function(link, post, fileOnly, resDoc) { var el, msg; + if (!resDoc) { + new Notice('warning', 'Connection error, please retry.', 20); + if (post.fullID === DeleteLink.post.fullID) { + $.on(link, 'click', DeleteLink.toggle); + } + return; + } link.textContent = DeleteLink.linkText(fileOnly); if (resDoc.title === '4chan - Banned') { - el = $.el('span', { - innerHTML: "You can't delete posts because you are banned." - }); + el = $.el('span', {innerHTML: "You can't delete posts because you are banned."}); return new Notice('warning', el, 20); } else if (msg = resDoc.getElementById('errmsg')) { new Notice('warning', msg.textContent, 20); @@ -13959,14 +17759,8 @@ DeleteLink = (function() { } } }, - error: function(link, post) { - new Notice('warning', 'Connection error, please retry.', 20); - if (post.fullID === DeleteLink.post.fullID) { - return $.on(link, 'click', DeleteLink.toggle); - } - }, cooldown: { - seconds: {}, + seconds: $.dict(), start: function(post, seconds) { if (DeleteLink.cooldown.seconds[post.fullID] != null) { return; @@ -14053,9 +17847,7 @@ Menu = (function() { className: 'menu-button', href: 'javascript:;' }); - $.extend(this.button, { - innerHTML: "" - }); + $.extend(this.button, {innerHTML: ""}); this.menu = new UI.Menu('post'); Callbacks.Post.push({ name: 'Menu', @@ -14071,7 +17863,7 @@ Menu = (function() { if (this.isClone) { button = $('.menu-button', this.nodes.info); $.rmClass(button, 'active'); - $.rm($('.dialog', button)); + $.rm($('.dialog', this.nodes.info)); Menu.makeButton(this, button); return; } @@ -14104,26 +17896,21 @@ ReportLink = (function() { } a = $.el('a', { className: 'report-link', - href: 'javascript:;' + href: 'javascript:;', + textContent: 'Report' }); $.on(a, 'click', ReportLink.report); return Menu.menu.addEntry({ el: a, order: 10, open: function(post) { - if (!(post.isDead || (post.thread.isDead && !post.thread.isArchived))) { - a.textContent = 'Report'; - ReportLink.url = "//sys.4chan.org/" + post.board + "/imgboard.php?mode=report&no=" + post; - if ((Conf['Use Recaptcha v1 in Reports'] && Main.jsEnabled) || d.cookie.indexOf('pass_enabled=1') >= 0) { - ReportLink.url += '&altc=1'; - ReportLink.dims = 'width=350,height=275'; - } else { - ReportLink.dims = 'width=400,height=550'; - } + ReportLink.url = "//sys." + (location.hostname.split('.')[1]) + ".org/" + post.board + "/imgboard.php?mode=report&no=" + post; + if (d.cookie.indexOf('pass_enabled=1') >= 0) { + ReportLink.dims = 'width=350,height=275'; } else { - ReportLink.url = ''; + ReportLink.dims = 'width=400,height=550'; } - return !!ReportLink.url; + return true; } }); }, @@ -14164,10 +17951,6 @@ AntiAutoplay = (function() { name: 'Disable Autoplaying Sounds', cb: this.node }); - Callbacks.CatalogThread.push({ - name: 'Disable Autoplaying Sounds', - cb: this.node - }); return $.ready((function(_this) { return function() { return _this.process(d.body); @@ -14187,22 +17970,27 @@ AntiAutoplay = (function() { return $.addClass(audio, 'controls-added'); }, node: function() { - return AntiAutoplay.process(this.nodes.root); + return AntiAutoplay.process(this.nodes.comment); }, process: function(root) { var i, iframe, j, len, len1, object, ref, ref1; ref = $$('iframe[src*="youtube"][src*="autoplay=1"]', root); for (i = 0, len = ref.length; i < len; i++) { iframe = ref[i]; - iframe.src = iframe.src.replace(/\?autoplay=1&?/, '?').replace('&autoplay=1', ''); - $.addClass(iframe, 'autoplay-removed'); + AntiAutoplay.processVideo(iframe, 'src'); } ref1 = $$('object[data*="youtube"][data*="autoplay=1"]', root); for (j = 0, len1 = ref1.length; j < len1; j++) { object = ref1[j]; - object.data = object.data.replace(/\?autoplay=1&?/, '?').replace('&autoplay=1', ''); - $.addClass(object, 'autoplay-removed'); + AntiAutoplay.processVideo(object, 'data'); + } + }, + processVideo: function(el, attr) { + el[attr] = el[attr].replace(/\?autoplay=1&?/, '?').replace('&autoplay=1', ''); + if (window.getComputedStyle(el).display === 'none') { + el.style.display = 'block'; } + return $.addClass(el, 'autoplay-removed'); } }; @@ -14215,7 +18003,6 @@ Banner = (function() { slice = [].slice; Banner = { - banners: ["0.jpg","1.jpg","2.jpg","4.jpg","6.jpg","7.jpg","8.jpg","9.jpg","10.jpg","11.jpg","12.jpg","13.jpg","14.jpg","16.jpg","17.jpg","18.jpg","19.jpg","20.jpg","21.jpg","22.jpg","24.jpg","25.jpg","26.jpg","28.jpg","29.jpg","33.jpg","38.jpg","39.jpg","43.jpg","44.jpg","45.jpg","46.jpg","47.jpg","52.jpg","54.jpg","57.jpg","59.jpg","60.jpg","61.jpg","64.jpg","66.jpg","67.jpg","69.jpg","71.jpg","72.jpg","76.jpg","77.jpg","81.jpg","82.jpg","83.jpg","84.jpg","88.jpg","90.jpg","91.jpg","96.jpg","98.jpg","99.jpg","100.jpg","104.jpg","106.jpg","116.jpg","119.jpg","137.jpg","140.jpg","148.jpg","149.jpg","150.jpg","154.jpg","156.jpg","157.jpg","158.jpg","159.jpg","161.jpg","162.jpg","164.jpg","165.jpg","166.jpg","167.jpg","168.jpg","169.jpg","170.jpg","171.jpg","172.jpg","173.jpg","174.jpg","175.jpg","176.jpg","178.jpg","179.jpg","180.jpg","181.jpg","182.jpg","183.jpg","186.jpg","189.jpg","190.jpg","192.jpg","193.jpg","194.jpg","197.jpg","198.jpg","200.jpg","201.jpg","202.jpg","203.jpg","205.jpg","206.jpg","207.jpg","208.jpg","210.jpg","213.jpg","214.jpg","215.jpg","216.jpg","218.jpg","219.jpg","220.jpg","221.jpg","222.jpg","223.jpg","224.jpg","227.jpg","0.png","1.png","2.png","3.png","5.png","6.png","9.png","10.png","11.png","12.png","14.png","16.png","19.png","20.png","21.png","22.png","23.png","24.png","26.png","27.png","28.png","29.png","30.png","31.png","32.png","33.png","34.png","37.png","39.png","40.png","41.png","42.png","43.png","44.png","45.png","48.png","49.png","50.png","51.png","52.png","53.png","57.png","58.png","59.png","64.png","66.png","67.png","68.png","69.png","70.png","71.png","72.png","76.png","78.png","79.png","81.png","82.png","85.png","86.png","87.png","89.png","95.png","98.png","100.png","101.png","102.png","105.png","106.png","107.png","109.png","110.png","111.png","112.png","113.png","114.png","115.png","116.png","118.png","119.png","120.png","121.png","122.png","123.png","126.png","128.png","130.png","134.png","136.png","138.png","139.png","140.png","142.png","145.png","146.png","149.png","150.png","151.png","152.png","153.png","154.png","155.png","156.png","157.png","158.png","159.png","160.png","163.png","164.png","165.png","166.png","167.png","168.png","169.png","170.png","171.png","172.png","173.png","174.png","178.png","179.png","180.png","181.png","182.png","184.png","186.png","188.png","190.png","192.png","193.png","194.png","195.png","196.png","197.png","198.png","200.png","202.png","203.png","205.png","206.png","207.png","209.png","212.png","213.png","214.png","216.png","217.png","218.png","219.png","220.png","221.png","222.png","223.png","224.png","225.png","226.png","229.png","231.png","232.png","233.png","234.png","235.png","237.png","238.png","239.png","240.png","241.png","242.png","244.png","245.png","246.png","247.png","248.png","249.png","250.png","253.png","254.png","255.png","256.png","257.png","258.png","259.png","260.png","262.png","268.png","0.gif","1.gif","2.gif","3.gif","4.gif","5.gif","6.gif","7.gif","8.gif","9.gif","10.gif","12.gif","13.gif","14.gif","15.gif","16.gif","18.gif","19.gif","20.gif","21.gif","22.gif","23.gif","24.gif","28.gif","29.gif","30.gif","33.gif","34.gif","35.gif","36.gif","37.gif","39.gif","40.gif","42.gif","44.gif","45.gif","46.gif","48.gif","50.gif","52.gif","54.gif","55.gif","57.gif","58.gif","59.gif","60.gif","61.gif","63.gif","64.gif","66.gif","67.gif","68.gif","69.gif","70.gif","72.gif","73.gif","75.gif","76.gif","77.gif","78.gif","80.gif","81.gif","82.gif","83.gif","86.gif","87.gif","88.gif","92.gif","93.gif","94.gif","95.gif","96.gif","97.gif","98.gif","99.gif","100.gif","101.gif","102.gif","103.gif","104.gif","105.gif","106.gif","108.gif","109.gif","110.gif","111.gif","112.gif","113.gif","115.gif","116.gif","117.gif","118.gif","119.gif","120.gif","122.gif","123.gif","124.gif","127.gif","129.gif","130.gif","131.gif","134.gif","135.gif","136.gif","138.gif","139.gif","141.gif","144.gif","146.gif","148.gif","149.gif","153.gif","154.gif","155.gif","157.gif","158.gif","159.gif","160.gif","161.gif","162.gif","164.gif","166.gif","167.gif","168.gif","169.gif","170.gif","171.gif","172.gif","173.gif","174.gif","175.gif","176.gif","177.gif","178.gif","181.gif","182.gif","183.gif","185.gif","186.gif","187.gif","188.gif","189.gif","190.gif","191.gif","192.gif","193.gif","195.gif","196.gif","197.gif","200.gif","201.gif","202.gif","203.gif","204.gif","205.gif","206.gif","207.gif","208.gif","209.gif","210.gif","211.gif","212.gif","213.gif","214.gif","215.gif","216.gif","217.gif","219.gif","220.gif","221.gif","222.gif","224.gif","225.gif","226.gif","227.gif","228.gif","230.gif","232.gif","233.gif","234.gif","235.gif","238.gif","240.gif","241.gif","243.gif","244.gif","245.gif","246.gif","247.gif","249.gif","250.gif","251.gif","253.gif"], init: function() { if (Conf['Custom Board Titles']) { this.db = new DataBoard('customTitles', null, true); @@ -14237,6 +18024,9 @@ Banner = (function() { var banner, children; banner = $(".boardBanner"); children = banner.children; + if (g.VIEW === 'thread' && Conf['Remove Thread Excerpt']) { + Banner.setTitle(children[1].textContent); + } children[0].title = "Click to change"; $.on(children[0], 'click', Banner.cb.toggle); if (Conf['Custom Board Titles']) { @@ -14269,7 +18059,7 @@ Banner = (function() { toggle: function() { var banner, i, ref; if (!((ref = Banner.choices) != null ? ref.length : void 0)) { - Banner.choices = Banner.banners.slice(); + Banner.choices = Conf['knownBanners'].split(',').slice(); } i = Math.floor(Banner.choices.length * Math.random()); banner = Banner.choices.splice(i, 1); @@ -14324,7 +18114,7 @@ Banner = (function() { } } }, - original: {}, + original: $.dict(), custom: function(child) { var className, data, event, j, len, ref; className = child.className; @@ -14362,7 +18152,7 @@ CatalogLinks = (function() { CatalogLinks = { init: function() { var el, input, selector; - if ((Conf['External Catalog'] || Conf['JSON Index']) && !(Conf['JSON Index'] && g.VIEW === 'index')) { + if (g.SITE.software === 'yotsuba' && (Conf['External Catalog'] || Conf['JSON Index']) && !(Conf['JSON Index'] && g.VIEW === 'index')) { selector = (function() { switch (g.VIEW) { case 'thread': @@ -14375,7 +18165,7 @@ CatalogLinks = (function() { } })(); $.ready(function() { - var catalogLink, i, len, link, ref; + var base, catalogLink, catalogURL, i, len, link, link2, ref; ref = $$(selector); for (i = 0, len = ref.length; i < len; i++) { link = ref[i]; @@ -14389,26 +18179,23 @@ CatalogLinks = (function() { case "/" + g.BOARD + "/catalog": link.href = CatalogLinks.catalog(); } - if (g.VIEW === 'catalog' && Conf['JSON Index'] && Conf['Use 4chan X Catalog']) { + if (g.VIEW === 'catalog' && (catalogURL = CatalogLinks.catalog()) !== (typeof (base = g.SITE.urls).catalog === "function" ? base.catalog(g.BOARD) : void 0)) { catalogLink = link.parentNode.cloneNode(true); - catalogLink.firstElementChild.textContent = '4chan X Catalog'; - catalogLink.firstElementChild.href = CatalogLinks.catalog(); + link2 = catalogLink.firstElementChild; + link2.href = catalogURL; + link2.textContent = link2.hostname === location.hostname ? '4chan X Catalog' : 'External Catalog'; $.after(link.parentNode, [$.tn(' '), catalogLink]); } } }); } - if (Conf['JSON Index'] && Conf['Use 4chan X Catalog']) { + if (g.SITE.software === 'yotsuba' && Conf['JSON Index'] && Conf['Use 4chan X Catalog']) { Callbacks.Post.push({ name: 'Catalog Link Rewrite', cb: this.node }); - Callbacks.CatalogThread.push({ - name: 'Catalog Link Rewrite', - cb: this.node - }); } - if (Conf['Catalog Links']) { + if ((this.enabled = Conf['Catalog Links'])) { CatalogLinks.el = el = UI.checkbox('Header catalog links', 'Catalog Links'); el.id = 'toggleCatalog'; input = $('input', el); @@ -14425,66 +18212,121 @@ CatalogLinks = (function() { ref = $$('a', this.nodes.comment); for (i = 0, len = ref.length; i < len; i++) { a = ref[i]; - if (m = a.href.match(/^https?:\/\/boards\.4chan\.org\/([^\/]+)\/catalog(#s=.*)?/)) { - a.href = "//boards.4chan.org/" + m[1] + "/" + (m[2] || '#catalog'); + if (m = a.href.match(/^https?:\/\/(boards\.4chan(?:nel)?\.org\/[^\/]+)\/catalog(#s=.*)?/)) { + a.href = "//" + m[1] + "/" + (m[2] || '#catalog'); } } }, - initBoardList: function() { - if (!CatalogLinks.el) { - return; - } - return CatalogLinks.set(Conf['Header catalog links']); - }, toggle: function() { $.event('CloseMenu'); $.set('Header catalog links', this.checked); return CatalogLinks.set(this.checked); }, set: function(useCatalog) { - var a, board, i, len, ref, ref1; - ref = $$('a:not([data-only])', Header.boardList).concat($$('a', Header.bottomBoardList)); + Conf['Header catalog links'] = useCatalog; + CatalogLinks.setLinks(Header.boardList); + CatalogLinks.setLinks(Header.bottomBoardList); + CatalogLinks.el.title = "Turn catalog links " + (useCatalog ? 'off' : 'on') + "."; + return $('input', CatalogLinks.el).checked = useCatalog; + }, + setLinks: function(list) { + var VIEW, a, board, boardID, i, len, ref, ref1, ref2, ref3, siteID, tail, url; + if (!(((ref = CatalogLinks.enabled) != null ? ref : Conf['Catalog Links']) && list)) { + return; + } + tail = /(?:index)?(?:\.\w+)?$/; + ref1 = $$('a:not([data-only])', list); + for (i = 0, len = ref1.length; i < len; i++) { + a = ref1[i]; + ref2 = a.dataset, siteID = ref2.siteID, boardID = ref2.boardID; + if (!(siteID && boardID)) { + ref3 = Site.parseURL(a), siteID = ref3.siteID, boardID = ref3.boardID, VIEW = ref3.VIEW; + if (!(siteID && boardID && (VIEW === 'index' || VIEW === 'catalog') && (a.dataset.indexOptions || a.href.replace(tail, '') === (Get.url(VIEW, { + siteID: siteID, + boardID: boardID + }) || '').replace(tail, '')))) { + continue; + } + $.extend(a.dataset, { + siteID: siteID, + boardID: boardID + }); + } + board = { + siteID: siteID, + boardID: boardID + }; + url = Conf['Header catalog links'] ? CatalogLinks.catalog(board) : Get.url('index', board); + if (url) { + a.href = url; + if (a.dataset.indexOptions && url.split('#')[0] === Get.url('index', board)) { + a.href += (a.hash ? '/' : '#') + a.dataset.indexOptions; + } + } + } + }, + externalParse: function() { + var board, boards, excludes, i, len, line, ref, ref1, ref2, url; + CatalogLinks.externalList = $.dict(); + ref = Conf['externalCatalogURLs'].split('\n'); for (i = 0, len = ref.length; i < len; i++) { - a = ref[i]; - if (((ref1 = a.hostname) !== 'boards.4chan.org' && ref1 !== 'catalog.neet.tv') || !(board = a.pathname.split('/')[1]) || (board === 'f' || board === 'status' || board === '4chan') || a.pathname.split('/')[2] === 'archive' || $.hasClass(a, 'external')) { + line = ref[i]; + if (line[0] === '#') { continue; } - a.href = useCatalog ? CatalogLinks.catalog(board) : "/" + board + "/"; - if (a.dataset.indexOptions && a.hostname === 'boards.4chan.org' && a.pathname.split('/')[2] === '') { - a.href += (a.hash ? '/' : '#') + a.dataset.indexOptions; + url = line.split(';')[0]; + boards = Filter.parseBoards(((ref1 = line.match(/;boards:([^;]+)/)) != null ? ref1[1] : void 0) || '*'); + excludes = Filter.parseBoards((ref2 = line.match(/;exclude:([^;]+)/)) != null ? ref2[1] : void 0) || $.dict(); + for (board in boards) { + if (!(excludes[board] || excludes[board.split('/')[0] + '/*'])) { + CatalogLinks.externalList[board] = url; + } } } - CatalogLinks.el.title = "Turn catalog links " + (useCatalog ? 'off' : 'on') + "."; - return $('input', CatalogLinks.el).checked = useCatalog; + }, + external: function(arg) { + var boardID, external, siteID; + siteID = arg.siteID, boardID = arg.boardID; + if (!CatalogLinks.externalList) { + CatalogLinks.externalParse(); + } + external = CatalogLinks.externalList[siteID + "/" + boardID] || CatalogLinks.externalList[siteID + "/*"]; + if (external) { + return external.replace(/%board/g, boardID); + } else { + return void 0; + } + }, + jsonIndex: function(board, hash) { + if (g.SITE.ID === board.siteID && g.BOARD.ID === board.boardID && g.VIEW === 'index') { + return hash; + } else { + return Get.url('index', board) + hash; + } }, catalog: function(board) { + var external, nativeCatalog; if (board == null) { - board = g.BOARD.ID; - } - if (Conf['External Catalog'] && (board === 'a' || board === 'c' || board === 'g' || board === 'biz' || board === 'k' || board === 'm' || board === 'o' || board === 'p' || board === 'v' || board === 'vg' || board === 'vr' || board === 'w' || board === 'wg' || board === 'cm' || board === '3' || board === 'adv' || board === 'an' || board === 'asp' || board === 'cgl' || board === 'ck' || board === 'co' || board === 'diy' || board === 'fa' || board === 'fit' || board === 'gd' || board === 'int' || board === 'jp' || board === 'lit' || board === 'mlp' || board === 'mu' || board === 'n' || board === 'out' || board === 'po' || board === 'sci' || board === 'sp' || board === 'tg' || board === 'toy' || board === 'trv' || board === 'tv' || board === 'vp' || board === 'wsg' || board === 'x' || board === 'f' || board === 'pol' || board === 's4s' || board === 'lgbt')) { - return "http://catalog.neet.tv/" + board + "/"; - } else if (Conf['JSON Index'] && Conf['Use 4chan X Catalog']) { - if (g.BOARD.ID === board && g.VIEW === 'index') { - return '#catalog'; - } else { - return "/" + board + "/#catalog"; - } + board = g.BOARD; + } + if (Conf['External Catalog'] && (external = CatalogLinks.external(board))) { + return external; + } else if (Index.enabledOn(board) && Conf['Use 4chan X Catalog']) { + return CatalogLinks.jsonIndex(board, '#catalog'); + } else if ((nativeCatalog = Get.url('catalog', board))) { + return nativeCatalog; } else { - return "/" + board + "/catalog"; + return CatalogLinks.external(board); } }, index: function(board) { if (board == null) { - board = g.BOARD.ID; + board = g.BOARD; } - if (Conf['JSON Index'] && board !== 'f') { - if (g.BOARD.ID === board && g.VIEW === 'index') { - return '#index'; - } else { - return "/" + board + "/#index"; - } + if (Index.enabledOn(board)) { + return CatalogLinks.jsonIndex(board, '#index'); } else { - return "/" + board + "/"; + return Get.url('index', board); } } }; @@ -14504,7 +18346,7 @@ CustomCSS = (function() { return this.addStyle(); }, addStyle: function() { - return this.style = $.addStyle(Conf['usercss'], 'custom-css', '#fourchanx-css'); + return this.style = $.addStyle(CSS.sub(Conf['usercss']), 'custom-css', '#fourchanx-css'); }, rmStyle: function() { if (this.style) { @@ -14516,7 +18358,7 @@ CustomCSS = (function() { if (!this.style) { return this.addStyle(); } - return this.style.textContent = Conf['usercss']; + return this.style.textContent = CSS.sub(Conf['usercss']); } }; @@ -14532,12 +18374,6 @@ ExpandComment = (function() { if (g.VIEW !== 'index' || !Conf['Comment Expansion'] || Conf['JSON Index']) { return; } - if (g.BOARD.ID === 'g') { - this.callbacks.push(Fourchan.code); - } - if (g.BOARD.ID === 'sci') { - this.callbacks.push(Fourchan.math); - } return Callbacks.Post.push({ name: 'Comment Expansion', cb: this.node @@ -14565,7 +18401,10 @@ ExpandComment = (function() { return; } a.textContent = "Post No." + post + " Loading..."; - return $.cache("//a.4cdn.org" + (a.pathname.split(/\/+/).splice(0, 4).join('/')) + ".json", function() { + return $.cache(g.SITE.urls.threadJSON({ + boardID: post.boardID, + threadID: post.threadID + }), function() { return ExpandComment.parse(this, a, post); }); }, @@ -14583,12 +18422,12 @@ ExpandComment = (function() { var callback, clone, comment, href, i, j, k, len, len1, len2, postObj, posts, quote, ref, ref1, spoilerRange, status; status = req.status; if (status !== 200 && status !== 304) { - a.textContent = "Error " + req.statusText + " (" + status + ")"; + a.textContent = status ? "Error " + req.statusText + " (" + status + ")" : 'Connection Error'; return; } posts = req.response.posts; if (spoilerRange = posts[0].custom_spoiler) { - Build.spoilerRange[g.BOARD] = spoilerRange; + g.SITE.Build.spoilerRange[g.BOARD] = spoilerRange; } for (i = 0, len = posts.length; i < len; i++) { postObj = posts[i]; @@ -14638,13 +18477,13 @@ ExpandThread = (function() { slice = [].slice; ExpandThread = { - statuses: {}, + statuses: $.dict(), init: function() { - if (g.VIEW === 'thread' || !Conf['Thread Expansion']) { + if (!(g.VIEW === 'index' && Conf['Thread Expansion'])) { return; } if (Conf['JSON Index']) { - return $.on(d, 'IndexRefresh', this.onIndexRefresh); + return $.on(d, 'IndexRefreshInternal', this.onIndexRefresh); } else { return Callbacks.Thread.push({ name: 'Expand Thread', @@ -14655,29 +18494,30 @@ ExpandThread = (function() { } }, setButton: function(thread) { - var a; - if (!(a = $.x('following-sibling::*[contains(@class,"summary")][1]', thread.OP.nodes.root))) { + var a, ref; + if (!(thread.nodes.root && (a = $('.summary', thread.nodes.root)))) { return; } - a.textContent = Build.summaryText.apply(Build, ['+'].concat(slice.call(a.textContent.match(/\d+/g)))); + a.textContent = (ref = g.SITE.Build).summaryText.apply(ref, ['+'].concat(slice.call(a.textContent.match(/\d+/g)))); a.style.cursor = 'pointer'; return $.on(a, 'click', ExpandThread.cbToggle); }, disconnect: function(refresh) { - var ref, ref1, status, threadID; + var oldReq, ref, status, threadID; if (g.VIEW === 'thread' || !Conf['Thread Expansion']) { return; } ref = ExpandThread.statuses; for (threadID in ref) { status = ref[threadID]; - if ((ref1 = status.req) != null) { - ref1.abort(); + if ((oldReq = status.req)) { + delete status.req; + oldReq.abort(); } delete ExpandThread.statuses[threadID]; } if (!refresh) { - return $.off(d, 'IndexRefresh', this.onIndexRefresh); + return $.off(d, 'IndexRefreshInternal', this.onIndexRefresh); } }, onIndexRefresh: function() { @@ -14687,62 +18527,66 @@ ExpandThread = (function() { }); }, cbToggle: function(e) { - if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { + if ($.modifiedClick(e)) { return; } e.preventDefault(); return ExpandThread.toggle(Get.threadFromNode(this)); }, + cbToggleBottom: function(e) { + var bottom, thread; + if ($.modifiedClick(e)) { + return; + } + e.preventDefault(); + thread = Get.threadFromNode(this); + $.rm(this); + bottom = thread.nodes.root.getBoundingClientRect().bottom; + ExpandThread.toggle(thread); + return window.scrollBy(0, thread.nodes.root.getBoundingClientRect().bottom - bottom); + }, toggle: function(thread) { - var a, threadRoot; - threadRoot = thread.OP.nodes.root.parentNode; - if (!(a = $('.summary', threadRoot))) { + var a; + if (!(thread.nodes.root && (a = $('.summary', thread.nodes.root)))) { return; } if (thread.ID in ExpandThread.statuses) { - return ExpandThread.contract(thread, a, threadRoot); + return ExpandThread.contract(thread, a, thread.nodes.root); } else { return ExpandThread.expand(thread, a); } }, expand: function(thread, a) { - var status; + var ref, status; ExpandThread.statuses[thread] = status = {}; - a.textContent = Build.summaryText.apply(Build, ['...'].concat(slice.call(a.textContent.match(/\d+/g)))); - return status.req = $.cache("//a.4cdn.org/" + thread.board + "/thread/" + thread + ".json", function() { + a.textContent = (ref = g.SITE.Build).summaryText.apply(ref, ['...'].concat(slice.call(a.textContent.match(/\d+/g)))); + status.req = $.cache(g.SITE.urls.threadJSON({ + boardID: thread.board.ID, + threadID: thread.ID + }), function() { + if (this !== status.req) { + return; + } delete status.req; return ExpandThread.parse(this, thread, a); }); + return status.numReplies = $$(g.SITE.selectors.replyOriginal, thread.nodes.root).length; }, contract: function(thread, a, threadRoot) { - var filesCount, i, inlined, len, num, postsCount, replies, reply, status; + var filesCount, i, inlined, len, oldReq, postsCount, ref, replies, reply, status; status = ExpandThread.statuses[thread]; delete ExpandThread.statuses[thread]; - if (status.req) { - status.req.abort(); + if ((oldReq = status.req)) { + delete status.req; + oldReq.abort(); if (a) { - a.textContent = Build.summaryText.apply(Build, ['+'].concat(slice.call(a.textContent.match(/\d+/g)))); + a.textContent = (ref = g.SITE.Build).summaryText.apply(ref, ['+'].concat(slice.call(a.textContent.match(/\d+/g)))); } return; } replies = $$('.thread > .replyContainer', threadRoot); - if (!Conf['JSON Index'] || Conf['Show Replies']) { - num = (function() { - if (thread.isSticky) { - return 1; - } else { - switch (g.BOARD.ID) { - case 'b': - case 'vg': - return 3; - case 't': - return 1; - default: - return 5; - } - } - })(); - replies = replies.slice(0, -num); + if (status.numReplies) { + replies = replies.slice(0, -status.numReplies); } postsCount = 0; filesCount = 0; @@ -14759,15 +18603,19 @@ ExpandThread = (function() { } $.rm(reply); } - return a.textContent = Build.summaryText('+', postsCount, filesCount); + if (Index.enabled) { + $.event('PostsRemoved', null, a.parentNode); + } + a.textContent = g.SITE.Build.summaryText('+', postsCount, filesCount); + return $.rm($('.summary-bottom', threadRoot)); }, parse: function(req, thread, a) { - var filesCount, i, len, post, postData, posts, postsCount, postsRoot, ref, ref1, root; + var a2, filesCount, i, len, post, postData, posts, postsCount, postsRoot, ref, ref1, root; if ((ref = req.status) !== 200 && ref !== 304) { - a.textContent = "Error " + req.statusText + " (" + req.status + ")"; + a.textContent = req.status ? "Error " + req.statusText + " (" + req.status + ")" : 'Connection Error'; return; } - Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler; + g.SITE.Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler; posts = []; postsRoot = []; filesCount = 0; @@ -14777,14 +18625,15 @@ ExpandThread = (function() { if (postData.no === thread.ID) { continue; } - if ((post = thread.posts[postData.no]) && !post.isFetchedQuote) { + if ((post = thread.posts.get(postData.no)) && !post.isFetchedQuote) { if ('file' in post) { filesCount++; } - postsRoot.push(post.nodes.root); + root = post.nodes.root; + postsRoot.push(root); continue; } - root = Build.postFromObject(postData, thread.board.ID); + root = g.SITE.Build.postFromObject(postData, thread.board.ID); post = new Post(root, thread, thread.board); if ('file' in post) { filesCount++; @@ -14794,9 +18643,15 @@ ExpandThread = (function() { } Main.callbackNodes('Post', posts); $.after(a, postsRoot); - $.event('PostsInserted'); + $.event('PostsInserted', null, a.parentNode); postsCount = postsRoot.length; - return a.textContent = Build.summaryText('-', postsCount, filesCount); + a.textContent = g.SITE.Build.summaryText('-', postsCount, filesCount); + if (root) { + a2 = a.cloneNode(true); + a2.classList.add('summary-bottom'); + $.on(a2, 'click', ExpandThread.cbToggleBottom); + return $.after(root, a2); + } } }; @@ -14810,7 +18665,7 @@ FileInfo = (function() { FileInfo = { init: function() { var ref; - if (((ref = g.VIEW) !== 'index' && ref !== 'thread') || !Conf['File Info Formatting']) { + if (((ref = g.VIEW) !== 'index' && ref !== 'thread' && ref !== 'archive') || !Conf['File Info Formatting']) { return; } return Callbacks.Post.push({ @@ -14819,7 +18674,7 @@ FileInfo = (function() { }); }, node: function() { - var a, i, info, len, oldInfo, ref; + var a, i, info, j, len, len1, oldInfo, ref, ref1; if (!this.file) { return; } @@ -14829,6 +18684,11 @@ FileInfo = (function() { a = ref[i]; $.on(a, 'click', ImageCommon.download); } + ref1 = $$('.file-info .quick-filter-md5', this.file.text); + for (j = 0, len1 = ref1.length; j < len1; j++) { + a = ref1[j]; + $.on(a, 'click', Filter.quickFilterMD5); + } return; } oldInfo = $.el('span', { @@ -14843,107 +18703,79 @@ FileInfo = (function() { return $.prepend(this.file.text, info); }, format: function(formatString, post, outputNode) { - var a, i, len, output, ref; + var a, i, j, len, len1, output, ref, ref1; output = []; formatString.replace(/%(.)|[^%]+/g, function(s, c) { - output.push(c in FileInfo.formatters ? FileInfo.formatters[c].call(post) : { - innerHTML: E(s) - }); + output.push($.hasOwn(FileInfo.formatters, c) ? FileInfo.formatters[c].call(post) : {innerHTML: E(s)}); return ''; }); - $.extend(outputNode, { - innerHTML: E.cat(output) - }); + $.extend(outputNode, {innerHTML: E.cat(output)}); ref = $$('.download-button', outputNode); for (i = 0, len = ref.length; i < len; i++) { a = ref[i]; $.on(a, 'click', ImageCommon.download); } + ref1 = $$('.quick-filter-md5', outputNode); + for (j = 0, len1 = ref1.length; j < len1; j++) { + a = ref1[j]; + $.on(a, 'click', Filter.quickFilterMD5); + } }, formatters: { t: function() { - return { - innerHTML: E(this.file.url.match(/[^/]*$/)[0]) - }; + return {innerHTML: E(this.file.url.match(/[^/]*$/)[0])}; }, T: function() { - return { - innerHTML: "" + (FileInfo.formatters.t.call(this)).innerHTML + "" - }; + return {innerHTML: "" + (FileInfo.formatters.t.call(this)).innerHTML + ""}; }, l: function() { - return { - innerHTML: "" + (FileInfo.formatters.n.call(this)).innerHTML + "" - }; + return {innerHTML: "" + (FileInfo.formatters.n.call(this)).innerHTML + ""}; }, L: function() { - return { - innerHTML: "" + (FileInfo.formatters.N.call(this)).innerHTML + "" - }; + return {innerHTML: "" + (FileInfo.formatters.N.call(this)).innerHTML + ""}; }, n: function() { var fullname, shortname; fullname = this.file.name; - shortname = Build.shortFilename(this.file.name, this.isReply); + shortname = SW.yotsuba.Build.shortFilename(this.file.name, this.isReply); if (fullname === shortname) { - return { - innerHTML: E(fullname) - }; + return {innerHTML: E(fullname)}; } else { - return { - innerHTML: "" + E(shortname) + "" + E(fullname) + "" - }; + return {innerHTML: "" + E(shortname) + "" + E(fullname) + ""}; } }, N: function() { - return { - innerHTML: E(this.file.name) - }; + return {innerHTML: E(this.file.name)}; }, d: function() { - return { - innerHTML: "" - }; + return {innerHTML: ""}; }, - p: function() { - return { - innerHTML: ((this.file.isSpoiler) ? "Spoiler, " : "") - }; + f: function() { + return {innerHTML: ""}; + }, + p: function() { + return {innerHTML: ((this.file.isSpoiler) ? "Spoiler, " : "")}; }, s: function() { - return { - innerHTML: E(this.file.size) - }; + return {innerHTML: E(this.file.size)}; }, B: function() { - return { - innerHTML: E(Math.round(this.file.sizeInBytes)) + " Bytes" - }; + return {innerHTML: E(Math.round(this.file.sizeInBytes)) + " Bytes"}; }, K: function() { - return { - innerHTML: E(Math.round(this.file.sizeInBytes/1024)) + " KB" - }; + return {innerHTML: E(Math.round(this.file.sizeInBytes/1024)) + " KB"}; }, M: function() { - return { - innerHTML: E(Math.round(this.file.sizeInBytes/1048576*100)/100) + " MB" - }; + return {innerHTML: E(Math.round(this.file.sizeInBytes/1048576*100)/100) + " MB"}; }, r: function() { - return { - innerHTML: E(this.file.dimensions || "PDF") - }; + return {innerHTML: E(this.file.dimensions || "PDF")}; }, g: function() { - return { - innerHTML: ((this.file.tag) ? ", " + E(this.file.tag) : "") - }; + return {innerHTML: ((this.file.tag) ? ", " + E(this.file.tag) : "")}; }, '%': function() { - return { - innerHTML: "%" - }; + return {innerHTML: "%"}; } } }; @@ -14991,16 +18823,20 @@ Fourchan = (function() { Fourchan = { init: function() { var ref; - if ((ref = g.VIEW) !== 'index' && ref !== 'thread') { + if (!(g.SITE.software === 'yotsuba' && ((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive'))) { return; } - if (g.BOARD.ID === 'g') { + BoardConfig.ready(this.initBoard); + return Main.ready(this.initReady); + }, + initBoard: function() { + if (g.BOARD.config.code_tags) { $.on(window, 'prettyprint:cb', function(e) { var post, pre; - if (!(post = g.posts[e.detail.ID])) { + if (!(post = g.posts.get(e.detail.ID))) { return; } - if (!(pre = $$('.prettyprint', post.nodes.comment)[e.detail.i])) { + if (!(pre = $$('.prettyprint', post.nodes.comment)[+e.detail.i])) { return; } if (!$.hasClass(pre, 'prettyprinted')) { @@ -15008,13 +18844,29 @@ Fourchan = (function() { return $.addClass(pre, 'prettyprinted'); } }); - $.globalEval('window.addEventListener(\'prettyprint\', function(e) {\n window.dispatchEvent(new CustomEvent(\'prettyprint:cb\', {\n detail: {\n ID: e.detail.ID,\n i: e.detail.i,\n html: prettyPrintOne(e.detail.html)\n }\n }));\n}, false);'); + $.global(function() { + return window.addEventListener('prettyprint', function(e) { + return window.dispatchEvent(new CustomEvent('prettyprint:cb', { + detail: { + ID: e.detail.ID, + i: e.detail.i, + html: window.prettyPrintOne(e.detail.html) + } + })); + }, false); + }); Callbacks.Post.push({ - name: 'Parse /g/ code', - cb: this.code + name: 'Parse [code] tags', + cb: Fourchan.code + }); + g.posts.forEach(function(post) { + if (post.callbacksExecuted) { + return Callbacks.Post.execute(post, ['Parse [code] tags'], true); + } }); + ExpandComment.callbacks.push(Fourchan.code); } - if (g.BOARD.ID === 'sci') { + if (g.BOARD.config.math_tags) { $.global(function() { return window.addEventListener('mathjax', function(e) { if (window.MathJax) { @@ -15033,24 +18885,26 @@ Fourchan = (function() { }, false); }); Callbacks.Post.push({ - name: 'Parse /sci/ math', - cb: this.math - }); - Callbacks.CatalogThread.push({ - name: 'Parse /sci/ math', - cb: this.math + name: 'Parse [math] tags', + cb: Fourchan.math }); - } - return Main.ready(function() { - return $.global(function() { - var j, len, node, ref1; - window.clickable_ids = false; - ref1 = document.querySelectorAll('.posteruid, .capcode'); - for (j = 0, len = ref1.length; j < len; j++) { - node = ref1[j]; - node.removeEventListener('click', window.idClick, false); + g.posts.forEach(function(post) { + if (post.callbacksExecuted) { + return Callbacks.Post.execute(post, ['Parse [math] tags'], true); } }); + return ExpandComment.callbacks.push(Fourchan.math); + } + }, + initReady: function() { + return $.global(function() { + var j, len, node, ref; + window.clickable_ids = false; + ref = document.querySelectorAll('.posteruid, .capcode'); + for (j = 0, len = ref.length; j < len; j++) { + node = ref[j]; + node.removeEventListener('click', window.idClick, false); + } }); }, code: function() { @@ -15113,9 +18967,8 @@ IDColor = (function() { if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Color User IDs'])) { return; } - this.ids = { - Heaven: [0, 0, 0, '#fff'] - }; + this.ids = $.dict(); + this.ids['Heaven'] = [0, 0, 0, '#fff']; return Callbacks.Post.push({ name: 'Color User IDs', cb: this.node @@ -15123,7 +18976,7 @@ IDColor = (function() { }, node: function() { var rgb, span, style, uid; - if (this.isClone || !((uid = this.info.uniqueID) && (span = $('span.hand', this.nodes.uniqueID)))) { + if (this.isClone || !((uid = this.info.uniqueID) && (span = this.nodes.uniqueID))) { return; } rgb = IDColor.ids[uid] || IDColor.compute(uid); @@ -15134,19 +18987,10 @@ IDColor = (function() { }, compute: function(uid) { var hash, rgb; - hash = IDColor.hash(uid); - rgb = [(hash >> 24) & 0xFF, (hash >> 16) & 0xFF, (hash >> 8) & 0xFF]; - rgb.push((rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114) > 125 ? '#000' : '#fff'); + hash = g.SITE.uidColor ? g.SITE.uidColor(uid) : parseInt(uid, 16); + rgb = [(hash >> 16) & 0xFF, (hash >> 8) & 0xFF, hash & 0xFF]; + rgb.push($.luma(rgb) > 125 ? '#000' : '#fff'); return this.ids[uid] = rgb; - }, - hash: function(uid) { - var i, msg; - msg = 0; - i = 0; - while (i < 8) { - msg = (msg << 5) - msg + uid.charCodeAt(i++); - } - return msg; } }; @@ -15170,8 +19014,8 @@ IDHighlight = (function() { }, uniqueID: null, node: function() { - if (this.nodes.uniqueID) { - $.on(this.nodes.uniqueID, 'click', IDHighlight.click(this)); + if (this.nodes.uniqueIDRoot) { + $.on(this.nodes.uniqueIDRoot, 'click', IDHighlight.click(this)); } if (this.nodes.capcode) { $.on(this.nodes.capcode, 'click', IDHighlight.click(this)); @@ -15199,6 +19043,47 @@ IDHighlight = (function() { }).call(this); +IDPostCount = (function() { + var IDPostCount; + + IDPostCount = { + init: function() { + if (!(g.VIEW === 'thread' && Conf['Count Posts by ID'])) { + return; + } + Callbacks.Thread.push({ + name: 'Count Posts by ID', + cb: function() { + return IDPostCount.thread = this; + } + }); + return Callbacks.Post.push({ + name: 'Count Posts by ID', + cb: this.node + }); + }, + node: function() { + if (this.nodes.uniqueID && this.thread === IDPostCount.thread) { + return $.on(this.nodes.uniqueID, 'mouseover', IDPostCount.count); + } + }, + count: function() { + var n, uniqueID; + uniqueID = Get.postFromNode(this).info.uniqueID; + n = 0; + IDPostCount.thread.posts.forEach(function(post) { + if (post.info.uniqueID === uniqueID) { + return n++; + } + }); + return this.title = n + " post" + (n === 1 ? '' : 's') + " by this ID"; + } + }; + + return IDPostCount; + +}).call(this); + Keybinds = (function() { var Keybinds; @@ -15227,7 +19112,7 @@ Keybinds = (function() { return Conf[hotkey] = key; }, keydown: function(e) { - var form, i, key, len, notification, notifications, op, ref, ref1, ref2, ref3, ref4, ref5, searchInput, target, thread, threadRoot; + var base, base1, catalog, i, key, len, notification, notifications, post, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, searchInput, target, thread, threadRoot; if (!(key = Keybinds.keyCode(e))) { return; } @@ -15237,11 +19122,9 @@ Keybinds = (function() { return; } } - if (!(((ref1 = g.VIEW) !== 'index' && ref1 !== 'thread') || g.VIEW === 'index' && Conf['JSON Index'] && Conf['Index Mode'] === 'catalog' || g.VIEW === 'index' && g.BOARD.ID === 'f')) { + if ((ref1 = g.VIEW) === 'index' || ref1 === 'thread') { threadRoot = Nav.getThread(); - if (op = $('.op', threadRoot)) { - thread = Get.postFromNode(op).thread; - } + thread = Get.threadFromRoot(threadRoot); } switch (key) { case Conf['Toggle board list']: @@ -15324,6 +19207,24 @@ Keybinds = (function() { } Keybinds.sage(); break; + case Conf['Toggle Cooldown']: + if (!(QR.nodes && !QR.nodes.el.hidden && $.hasClass(QR.nodes.fileSubmit, 'custom-cooldown'))) { + return; + } + QR.toggleCustomCooldown(); + break; + case Conf['Post from URL']: + if (!QR.postingIsEnabled) { + return; + } + QR.handleUrl(''); + break; + case Conf['Add new post']: + if (!QR.postingIsEnabled) { + return; + } + QR.addPost(); + break; case Conf['Submit QR']: if (!(QR.nodes && !QR.nodes.el.hidden)) { return; @@ -15335,13 +19236,13 @@ Keybinds = (function() { case Conf['Update']: switch (g.VIEW) { case 'thread': - if (!Conf['Thread Updater']) { + if (!ThreadUpdater.enabled) { return; } ThreadUpdater.update(); break; case 'index': - if (!(Conf['JSON Index'] && g.BOARD.ID !== 'f')) { + if (!Index.enabled) { return; } Index.update(); @@ -15362,17 +19263,38 @@ Keybinds = (function() { } ThreadWatcher.buttonFetchAll(); break; + case Conf['Toggle thread watcher']: + if (!ThreadWatcher.enabled) { + return; + } + ThreadWatcher.toggleWatcher(); + break; + case Conf['Toggle threading']: + if (!QuoteThreading.ready) { + return; + } + QuoteThreading.toggleThreading(); + break; + case Conf['Mark thread read']: + if (!(g.VIEW === 'index' && thread && UnreadIndex.enabled)) { + return; + } + UnreadIndex.markRead.call(threadRoot); + break; case Conf['Expand image']: if (!(ImageExpand.enabled && threadRoot)) { return; } - Keybinds.img(threadRoot); + post = Get.postFromNode(Keybinds.post(threadRoot)); + if (post.file) { + ImageExpand.toggle(post); + } break; case Conf['Expand images']: - if (!(ImageExpand.enabled && threadRoot)) { + if (!ImageExpand.enabled) { return; } - Keybinds.img(threadRoot, true); + ImageExpand.cb.toggleAll(); break; case Conf['Open Gallery']: if (!Gallery.enabled) { @@ -15381,91 +19303,94 @@ Keybinds = (function() { Gallery.cb.toggle(); break; case Conf['fappeTyme']: - if (!(Conf['Fappe Tyme'] && ((ref2 = g.VIEW) === 'index' || ref2 === 'thread'))) { + if (!((ref2 = FappeTyme.nodes) != null ? ref2.fappe : void 0)) { return; } FappeTyme.toggle('fappe'); break; case Conf['werkTyme']: - if (!(Conf['Werk Tyme'] && ((ref3 = g.VIEW) === 'index' || ref3 === 'thread'))) { + if (!((ref3 = FappeTyme.nodes) != null ? ref3.werk : void 0)) { return; } FappeTyme.toggle('werk'); break; case Conf['Front page']: - if (Conf['JSON Index'] && g.VIEW === 'index' && g.BOARD.ID !== 'f') { + if (Index.enabled) { Index.userPageNav(1); } else { - window.location = "/" + g.BOARD + "/"; + location.href = "/" + g.BOARD + "/"; } break; case Conf['Open front page']: - $.open("/" + g.BOARD + "/"); + $.open(location.origin + "/" + g.BOARD + "/"); break; case Conf['Next page']: - if (!(g.VIEW === 'index' && g.BOARD.ID !== 'f')) { + if (!(g.VIEW === 'index' && !(typeof (base = g.SITE).isOnePage === "function" ? base.isOnePage(g.BOARD) : void 0))) { return; } - if (Conf['JSON Index']) { + if (Index.enabled) { if ((ref4 = Conf['Index Mode']) !== 'paged' && ref4 !== 'infinite') { return; } $('.next button', Index.pagelist).click(); } else { - if (form = $('.next form')) { - window.location = form.action; + if ((ref5 = $(g.SITE.selectors.nav.next)) != null) { + ref5.click(); } } break; case Conf['Previous page']: - if (!(g.VIEW === 'index' && g.BOARD.ID !== 'f')) { + if (!(g.VIEW === 'index' && !(typeof (base1 = g.SITE).isOnePage === "function" ? base1.isOnePage(g.BOARD) : void 0))) { return; } - if (Conf['JSON Index']) { - if ((ref5 = Conf['Index Mode']) !== 'paged' && ref5 !== 'infinite') { + if (Index.enabled) { + if ((ref6 = Conf['Index Mode']) !== 'paged' && ref6 !== 'infinite') { return; } $('.prev button', Index.pagelist).click(); } else { - if (form = $('.prev form')) { - window.location = form.action; + if ((ref7 = $(g.SITE.selectors.nav.prev)) != null) { + ref7.click(); } } break; case Conf['Search form']: - if (!(g.VIEW === 'index' && g.BOARD.ID !== 'f')) { + if (g.VIEW !== 'index') { + return; + } + searchInput = Index.enabled ? Index.searchInput : g.SITE.selectors.searchBox ? $(g.SITE.selectors.searchBox) : void 0; + if (!searchInput) { return; } - searchInput = Conf['JSON Index'] ? Index.searchInput : $.id('search-box'); Header.scrollToIfNeeded(searchInput); searchInput.focus(); break; case Conf['Paged mode']: - if (!(Conf['JSON Index'] && g.BOARD.ID !== 'f')) { + if (!Index.enabledOn(g.BOARD)) { return; } - window.location = g.VIEW === 'index' ? '#paged' : "/" + g.BOARD + "/#paged"; + location.href = g.VIEW === 'index' ? '#paged' : "/" + g.BOARD + "/#paged"; break; case Conf['Infinite scrolling mode']: - if (!(Conf['JSON Index'] && g.BOARD.ID !== 'f')) { + if (!Index.enabledOn(g.BOARD)) { return; } - window.location = g.VIEW === 'index' ? '#infinite' : "/" + g.BOARD + "/#infinite"; + location.href = g.VIEW === 'index' ? '#infinite' : "/" + g.BOARD + "/#infinite"; break; case Conf['All pages mode']: - if (!(Conf['JSON Index'] && g.BOARD.ID !== 'f')) { + if (!Index.enabledOn(g.BOARD)) { return; } - window.location = g.VIEW === 'index' ? '#all-pages' : "/" + g.BOARD + "/#all-pages"; + location.href = g.VIEW === 'index' ? '#all-pages' : "/" + g.BOARD + "/#all-pages"; break; case Conf['Open catalog']: - if (g.BOARD.ID === 'f') { + if (!(catalog = CatalogLinks.catalog())) { return; } - window.location = CatalogLinks.catalog(); + location.href = catalog; break; case Conf['Cycle sort type']: - if (!(Conf['JSON Index'] && g.VIEW === 'index' && g.BOARD.ID !== 'f')) { + if (!Index.enabled) { return; } Index.cycleSortType(); @@ -15487,6 +19412,7 @@ Keybinds = (function() { return; } ExpandThread.toggle(thread); + Header.scrollTo(threadRoot); break; case Conf['Open thread']: if (!(g.VIEW === 'index' && threadRoot)) { @@ -15525,6 +19451,14 @@ Keybinds = (function() { Header.scrollTo(threadRoot); ThreadHiding.toggle(thread); break; + case Conf['Quick Filter MD5']: + if (!threadRoot) { + return; + } + post = Keybinds.post(threadRoot); + Keybinds.hl(+1, threadRoot); + Filter.quickFilterMD5.call(post, e); + break; case Conf['Previous Post Quoting You']: if (!(threadRoot && QuoteYou.db)) { return; @@ -15598,31 +19532,40 @@ Keybinds = (function() { } return key; }, + post: function(thread) { + var s; + s = g.SITE.selectors; + return $("" + s.postContainer + s.highlightable.reply + "." + g.SITE.classes.highlight, thread) || $("" + (g.SITE.isOPContainerThread ? s.thread : s.postContainer) + s.highlightable.op, thread); + }, qr: function(thread) { QR.open(); if (thread != null) { - QR.quote.call($('input', $('.post.highlight', thread) || thread)); + QR.quote.call(Keybinds.post(thread)); } return QR.nodes.com.focus(); }, tags: function(tag, ta) { - var range, selEnd, selStart, supported, value; - supported = (function() { - switch (tag) { - case 'spoiler': - return !!$('.postForm input[name=spoiler]'); - case 'code': - return g.BOARD.ID === 'g'; - case 'math': - case 'eqn': - return g.BOARD.ID === 'sci'; - case 'sjis': - return g.BOARD.ID === 'jp'; + var range, selEnd, selStart, value; + BoardConfig.ready(function() { + var config, supported; + config = g.BOARD.config; + supported = (function() { + switch (tag) { + case 'spoiler': + return !!config.spoilers; + case 'code': + return !!config.code_tags; + case 'math': + case 'eqn': + return !!config.math_tags; + case 'sjis': + return !!config.sjis_tags; + } + })(); + if (!supported) { + return new Notice('warning', "[" + tag + "] tags are not supported on /" + g.BOARD + "/.", 20); } - })(); - if (!supported) { - new Notice('warning', "[" + tag + "] tags are not supported on /" + g.BOARD + "/.", 20); - } + }); value = ta.value; selStart = ta.selectionStart; selEnd = ta.selectionEnd; @@ -15636,21 +19579,12 @@ Keybinds = (function() { isSage = /sage/i.test(QR.nodes.email.value); return QR.nodes.email.value = isSage ? "" : "sage"; }, - img: function(thread, all) { - var post; - if (all) { - return ImageExpand.cb.toggleAll(); - } else { - post = Get.postFromNode($('.post.highlight', thread) || $('.op', thread)); - return ImageExpand.toggle(post); - } - }, open: function(thread, tab) { var url; if (g.VIEW !== 'index') { return; } - url = "/" + thread.board + "/thread/" + thread; + url = Get.url('thread', thread); if (tab) { return $.open(url); } else { @@ -15658,43 +19592,45 @@ Keybinds = (function() { } }, hl: function(delta, thread) { - var axis, height, i, len, next, postEl, replies, reply, root; - postEl = $('.reply.highlight', thread); + var axis, height, highlight, i, len, next, postEl, replies, reply, replySelector, root; + replySelector = "" + g.SITE.selectors.postContainer + g.SITE.selectors.highlightable.reply; + highlight = g.SITE.classes.highlight; + postEl = $(replySelector + "." + highlight, thread); if (!delta) { if (postEl) { - $.rmClass(postEl, 'highlight'); + $.rmClass(postEl, highlight); } return; } if (postEl) { height = postEl.getBoundingClientRect().height; if (Header.getTopOf(postEl) >= -height && Header.getBottomOf(postEl) >= -height) { - root = postEl.parentNode; + root = Get.postFromNode(postEl).nodes.root; axis = delta === +1 ? 'following' : 'preceding'; - if (!(next = $.x(axis + "-sibling::div[contains(@class,'replyContainer') and not(@hidden) and not(child::div[@class='stub'])][1]/child::div[contains(@class,'reply')]", root))) { + if (!(next = $.x(axis + "-sibling::" + g.SITE.xpath.replyContainer + "[not(@hidden) and not(child::div[@class='stub'])][1]", root))) { return; } + if (!next.matches(replySelector)) { + next = $(replySelector, next); + } Header.scrollToIfNeeded(next, delta === +1); - this.focus(next); - $.rmClass(postEl, 'highlight'); + $.addClass(next, highlight); + $.rmClass(postEl, highlight); return; } - $.rmClass(postEl, 'highlight'); + $.rmClass(postEl, highlight); } - replies = $$('.reply', thread); + replies = $$(replySelector, thread); if (delta === -1) { replies.reverse(); } for (i = 0, len = replies.length; i < len; i++) { reply = replies[i]; if (delta === +1 && Header.getTopOf(reply) > 0 || delta === -1 && Header.getBottomOf(reply) > 0) { - this.focus(reply); + $.addClass(reply, highlight); return; } } - }, - focus: function(post) { - return $.addClass(post, 'highlight'); } }; @@ -15702,6 +19638,64 @@ Keybinds = (function() { }).call(this); +ModContact = (function() { + var ModContact; + + ModContact = { + init: function() { + var ref; + if (!(g.SITE.software === 'yotsuba' && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { + return; + } + return Callbacks.Post.push({ + name: 'Mod Contact Links', + cb: this.node + }); + }, + node: function() { + var links, moveNote, moved; + if (this.isClone || !$.hasOwn(ModContact.specific, this.info.capcode)) { + return; + } + links = $.el('span', { + className: 'contact-links brackets-wrap' + }); + $.extend(links, ModContact.template(this.info.capcode)); + $.after(this.nodes.capcode, links); + if ((moved = this.info.comment.match(/This thread was moved to >>>\/(\w+)\//)) && $.hasOwn(ModContact.moveNote, moved[1])) { + moveNote = $.el('div', { + className: 'move-note' + }); + $.extend(moveNote, ModContact.moveNote[moved[1]]); + return $.add(this.nodes.post, moveNote); + } + }, + template: function(capcode) { + return {innerHTML: "feedback" + (ModContact.specific[capcode]()).innerHTML}; + }, + specific: { + Mod: function() { + return {innerHTML: " IRC"}; + }, + Manager: function() { + return ModContact.specific.Mod(); + }, + Developer: function() { + return {innerHTML: " github"}; + }, + Admin: function() { + return {innerHTML: " twitter"}; + } + }, + moveNote: { + qa: {innerHTML: "Moving a thread to /qa/ does not imply mods will read it. If you wish to contact mods, use feedback (https://www.4chan.org/feedback) or IRC (https://www.4chan-x.net/4chan-irc.html)."} + } + }; + + return ModContact; + +}).call(this); + Nav = (function() { var Nav; @@ -15758,7 +19752,13 @@ Nav = (function() { }, getThread: function() { var i, len, ref, thread, threadRoot; - ref = $$('.thread'); + if (g.VIEW === 'thread') { + return g.threads.get(g.BOARD + "." + g.THREADID).nodes.root; + } + if ($.hasClass(doc, 'catalog-mode')) { + return; + } + ref = $$(g.SITE.selectors.thread); for (i = 0, len = ref.length; i < len; i++) { threadRoot = ref[i]; thread = Get.threadFromRoot(threadRoot); @@ -15769,7 +19769,6 @@ Nav = (function() { return threadRoot; } } - return $('.board'); }, scroll: function(delta) { var axis, extra, next, ref, thread, top; @@ -15777,8 +19776,11 @@ Nav = (function() { ref.blur(); } thread = Nav.getThread(); + if (!thread) { + return; + } axis = delta === +1 ? 'following' : 'preceding'; - if (next = $.x(axis + "-sibling::div[contains(@class,'thread') and not(@hidden)][1]", thread)) { + if (next = $.x(axis + "-sibling::" + g.SITE.xpath.thread + "[not(@hidden)][1]", thread)) { top = Header.getTopOf(thread); if (delta === +1 && top < 5 || delta === -1 && top > -5) { thread = next; @@ -15800,7 +19802,7 @@ Nav = (function() { if (extra > 0) { return d.body.style.marginBottom = extra + "px"; } else { - d.body.style.marginBottom = null; + d.body.style.marginBottom = ''; delete Nav.haveExtra; return $.off(d, 'scroll', Nav.removeExtra); } @@ -15821,13 +19823,15 @@ NormalizeURL = (function() { return; } pathname = location.pathname.split(/\/+/); - switch (g.VIEW) { - case 'thread': - pathname[2] = 'thread'; - pathname = pathname.slice(0, 4); - break; - case 'index': - pathname = pathname.slice(0, 3); + if (g.SITE.software === 'yotsuba') { + switch (g.VIEW) { + case 'thread': + pathname[2] = 'thread'; + pathname = pathname.slice(0, 4); + break; + case 'index': + pathname = pathname.slice(0, 3); + } } pathname = pathname.join('/'); if (location.pathname !== pathname) { @@ -15840,26 +19844,66 @@ NormalizeURL = (function() { }).call(this); +PSA = (function() { + var PSA, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + PSA = { + init: function() { + var announcement, el; + if (g.SITE.software === 'yotsuba' && g.BOARD.ID === 'qa') { + announcement = { + innerHTML: "Stay in touch with your /qa/ friends!" + }; + el = $.el('div', { + className: 'fcx-announcement' + }, announcement); + $.onExists(doc, '.boardBanner', function(banner) { + return $.after(banner, el); + }); + } + if ('samachan.org' in Conf['siteProperties'] && indexOf.call(Conf['PSAseen'], 'samachan') < 0) { + el = $.el('span', { + innerHTML: "Looking for a new home?
      Some former Samachan users are regrouping on SushiChan.

      (a message from 4chan X)" + }); + return Main.ready(function() { + new Notice('info', el); + Conf['PSAseen'].push('samachan'); + return $.set('PSAseen', Conf['PSAseen']); + }); + } + } + }; + + return PSA; + +}).call(this); + PSAHiding = (function() { - var PSAHiding; + var PSAHiding, + slice = [].slice; PSAHiding = { init: function() { - if (!Conf['Announcement Hiding']) { + if (!(Conf['Announcement Hiding'] && g.SITE.selectors.psa)) { return; } $.addClass(doc, 'hide-announcement'); - return $.one(d, '4chanXInitFinished', this.setup); + $.onExists(doc, g.SITE.selectors.psa, this.setup); + return $.ready(function() { + if (!$(g.SITE.selectors.psa)) { + return $.rmClass(doc, 'hide-announcement'); + } + }); }, - setup: function() { - var btn, entry, hr, psa, ref; - if (!(psa = PSAHiding.psa = $.id('globalMessage'))) { - $.rmClass(doc, 'hide-announcement'); - return; - } - if ((hr = (ref = $.id('globalToggle')) != null ? ref.previousElementSibling : void 0) && hr.nodeName === 'HR') { + setup: function(psa) { + var btn, entry, hr, ref, ref1, ref2; + PSAHiding.psa = psa; + PSAHiding.text = (ref = psa.dataset.utc) != null ? ref : psa.innerHTML; + if (g.SITE.selectors.psaTop && (hr = (ref1 = $(g.SITE.selectors.psaTop)) != null ? ref1.previousElementSibling : void 0) && hr.nodeName === 'HR') { PSAHiding.hr = hr; } + PSAHiding.content = $.el('div'); entry = { el: $.el('a', { textContent: 'Show announcement', @@ -15868,55 +19912,193 @@ PSAHiding = (function() { }), order: 50, open: function() { - return PSAHiding.hidden; + return psa.hidden; } }; Header.menu.addEntry(entry); $.on(entry.el, 'click', PSAHiding.toggle); - PSAHiding.btn = btn = $.el('span', { + PSAHiding.btn = btn = $.el('a', { title: 'Mark announcement as read and hide.', - className: 'hide-announcement' - }); - $.extend(btn, { - innerHTML: "[Dismiss]" + className: 'hide-announcement-button fa fa-minus-square', + href: 'javascript:;' }); $.on(btn, 'click', PSAHiding.toggle); - $.get('hiddenPSA', 0, function(arg) { - var hiddenPSA; - hiddenPSA = arg.hiddenPSA; - PSAHiding.sync(hiddenPSA); - $.add(psa, btn); - return $.rmClass(doc, 'hide-announcement'); - }); - return $.sync('hiddenPSA', PSAHiding.sync); + if (((ref2 = psa.firstChild) != null ? ref2.tagName : void 0) === 'HR') { + $.after(psa.firstChild, btn); + } else { + $.prepend(psa, btn); + } + PSAHiding.sync(Conf['hiddenPSAList']); + $.rmClass(doc, 'hide-announcement'); + return $.sync('hiddenPSAList', PSAHiding.sync); }, toggle: function() { - var UTC; - if ($.hasClass(this, 'hide-announcement')) { - UTC = +$.id('globalMessage').dataset.utc; - $.set('hiddenPSA', UTC); + var hide, set; + hide = $.hasClass(this, 'hide-announcement-button'); + set = function(hiddenPSAList) { + if (hide) { + return hiddenPSAList[g.SITE.ID] = PSAHiding.text; + } else { + return delete hiddenPSAList[g.SITE.ID]; + } + }; + set(Conf['hiddenPSAList']); + PSAHiding.sync(Conf['hiddenPSAList']); + return $.get('hiddenPSAList', Conf['hiddenPSAList'], function(arg) { + var hiddenPSAList; + hiddenPSAList = arg.hiddenPSAList; + set(hiddenPSAList); + return $.set('hiddenPSAList', hiddenPSAList); + }); + }, + sync: function(hiddenPSAList) { + var content, psa, ref; + psa = PSAHiding.psa, content = PSAHiding.content; + psa.hidden = hiddenPSAList[g.SITE.ID] === PSAHiding.text; + if (psa.hidden) { + $.add(content, slice.call(psa.childNodes)); } else { - $.event('CloseMenu'); - $["delete"]('hiddenPSA'); + $.add(psa, slice.call(content.childNodes)); + } + return (ref = PSAHiding.hr) != null ? ref.hidden = psa.hidden : void 0; + } + }; + + return PSAHiding; + +}).call(this); + +PassMessage = (function() { + var PassMessage; + + PassMessage = { + init: function() { + var close, msg; + if (Conf['passMessageClosed']) { + return; + } + msg = $.el('div', { + className: 'box-outer top-box' + }, {innerHTML: "

      Trouble buying a 4chan Pass? (a message from 4chan X) ×

      Check the 4chan X wiki for alternative solutions.
      "}); + msg.style.cssText = 'padding-bottom: 0;'; + close = $('a', msg); + $.on(close, 'click', function() { + $.rm(msg); + return $.set('passMessageClosed', true); + }); + return $.ready(function() { + var hd; + if ((hd = $.id('hd'))) { + return $.after(hd, msg); + } else { + return $.prepend(d.body, msg); + } + }); + } + }; + + return PassMessage; + +}).call(this); + +PostJumper = (function() { + var PostJumper; + + PostJumper = { + init: function() { + var ref; + if (!(Conf['Unique ID and Capcode Navigation'] && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { + return; } - return PSAHiding.sync(UTC); + this.buttons = this.makeButtons(); + return Callbacks.Post.push({ + name: 'Post Jumper', + cb: this.node + }); }, - sync: function(UTC) { - var psa, ref; - psa = PSAHiding.psa; - PSAHiding.hidden = PSAHiding.btn.hidden = (UTC != null) && UTC >= +psa.dataset.utc; - if (PSAHiding.hidden) { - $.rm(psa); - } else { - $.after($.id('globalToggle'), psa); + node: function() { + var buttons, i, len, ref; + if (this.isClone) { + ref = $$('.postJumper', this.nodes.info); + for (i = 0, len = ref.length; i < len; i++) { + buttons = ref[i]; + PostJumper.addListeners(buttons); + } + return; + } + if (this.nodes.uniqueIDRoot) { + PostJumper.addButtons(this, 'uniqueID'); + } + if (this.nodes.capcode) { + return PostJumper.addButtons(this, 'capcode'); + } + }, + addButtons: function(post, type) { + var buttons, value; + value = post.info[type]; + buttons = PostJumper.buttons.cloneNode(true); + $.extend(buttons.dataset, { + type: type, + value: value + }); + $.after(post.nodes[type + (type === 'capcode' ? '' : 'Root')], buttons); + return PostJumper.addListeners(buttons); + }, + addListeners: function(buttons) { + $.on(buttons.firstChild, 'click', PostJumper.buttonClick); + return $.on(buttons.lastChild, 'click', PostJumper.buttonClick); + }, + buttonClick: function() { + var dir, toJumper; + dir = $.hasClass(this, 'prev') ? -1 : 1; + if ((toJumper = PostJumper.find(this.parentNode, dir))) { + return PostJumper.scroll(this.parentNode, toJumper); + } + }, + find: function(jumper, dir) { + var axis, jumper2, ref, type, value, xpath; + ref = jumper.dataset, type = ref.type, value = ref.value; + xpath = "span[contains(@class,\"postJumper\") and @data-value=\"" + value + "\" and @data-type=\"" + type + "\"]"; + axis = dir < 0 ? 'preceding' : 'following'; + jumper2 = jumper; + while ((jumper2 = $.x(axis + "::" + xpath, jumper2))) { + if (jumper2.getBoundingClientRect().height) { + return jumper2; + } } - if ((ref = PSAHiding.hr) != null) { - ref.hidden = PSAHiding.hidden; + if ((jumper2 = $.x("(//" + xpath + ")[" + (dir < 0 ? 'last()' : '1') + "]"))) { + if (jumper2.getBoundingClientRect().height) { + return jumper2; + } + } + while ((jumper2 = $.x(axis + "::" + xpath, jumper2)) && jumper2 !== jumper) { + if (jumper2.getBoundingClientRect().height) { + return jumper2; + } } + return null; + }, + makeButtons: function() { + var charNext, charPrev, classNext, classPrev, span; + charPrev = '\u23EB'; + charNext = '\u23EC'; + classPrev = 'prev'; + classNext = 'next'; + span = $.el('span', { + className: 'postJumper' + }); + $.extend(span, {innerHTML: "" + E(charPrev) + "" + E(charNext) + ""}); + return span; + }, + scroll: function(fromJumper, toJumper) { + var destPos, prevPos; + prevPos = fromJumper.getBoundingClientRect().top; + destPos = toJumper.getBoundingClientRect().top; + return window.scrollBy(0, destPos - prevPos); } }; - return PSAHiding; + return PostJumper; }).call(this); @@ -15928,9 +20110,9 @@ RelativeDates = (function() { INTERVAL: $.MINUTE / 2, init: function() { var ref; - if (((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Relative Post Dates'] && !Conf['Relative Date Title'] || g.VIEW === 'index' && Conf['JSON Index'] && g.BOARD.ID !== 'f') { + if (((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Relative Post Dates'] && !Conf['Relative Date Title'] || Index.enabled) { this.flush(); - $.on(d, 'visibilitychange ThreadUpdate', this.flush); + $.on(d, 'visibilitychange PostsInserted', this.flush); } if (Conf['Relative Post Dates']) { return Callbacks.Post.push({ @@ -15941,6 +20123,9 @@ RelativeDates = (function() { }, node: function() { var dateEl; + if (!this.info.date) { + return; + } dateEl = this.nodes.date; if (Conf['Relative Date Title']) { $.on(dateEl, 'mouseover', (function(_this) { @@ -15956,14 +20141,22 @@ RelativeDates = (function() { dateEl.title = dateEl.textContent; return RelativeDates.update(this); }, - relative: function(diff, now, date) { + relative: function(diff, now, date, abbrev) { var days, months, number, rounded, unit, years; - unit = (number = diff / $.DAY) >= 1 ? (years = now.getYear() - date.getYear(), months = now.getMonth() - date.getMonth(), days = now.getDate() - date.getDate(), years > 1 ? (number = years - (months < 0 || months === 0 && days < 0), 'year') : years === 1 && (months > 0 || months === 0 && days >= 0) ? (number = years, 'year') : (months = months + 12 * years) > 1 ? (number = months - (days < 0), 'month') : months === 1 && days >= 0 ? (number = months, 'month') : 'day') : (number = diff / $.HOUR) >= 1 ? 'hour' : (number = diff / $.MINUTE) >= 1 ? 'minute' : (number = Math.max(0, diff) / $.SECOND, 'second'); + unit = (number = diff / $.DAY) >= 1 ? (years = now.getFullYear() - date.getFullYear(), months = now.getMonth() - date.getMonth(), days = now.getDate() - date.getDate(), years > 1 ? (number = years - (months < 0 || months === 0 && days < 0), 'year') : years === 1 && (months > 0 || months === 0 && days >= 0) ? (number = years, 'year') : (months = months + 12 * years) > 1 ? (number = months - (days < 0), 'month') : months === 1 && days >= 0 ? (number = months, 'month') : 'day') : (number = diff / $.HOUR) >= 1 ? 'hour' : (number = diff / $.MINUTE) >= 1 ? 'minute' : (number = Math.max(0, diff) / $.SECOND, 'second'); rounded = Math.round(number); - if (rounded !== 1) { - unit += 's'; + if (abbrev) { + unit = unit === 'month' ? 'mo' : unit[0]; + } else { + if (rounded !== 1) { + unit += 's'; + } + } + if (abbrev) { + return "" + rounded + unit; + } else { + return rounded + " " + unit + " ago"; } - return rounded + " " + unit + " ago"; }, stale: [], flush: function() { @@ -15989,12 +20182,18 @@ RelativeDates = (function() { return post.nodes.date.title = RelativeDates.relative(diff, now, date); }, update: function(data, now) { - var date, diff, i, isPost, len, ref, relative, singlePost; + var abbrev, date, diff, i, isPost, len, ref, relative, singlePost; isPost = data instanceof Post; - date = isPost ? data.info.date : new Date(+data.dataset.utc); + if (isPost) { + date = data.info.date; + abbrev = false; + } else { + date = new Date(+data.dataset.utc); + abbrev = !!data.dataset.abbrev; + } now || (now = new Date()); diff = now - date; - relative = RelativeDates.relative(diff, now, date); + relative = RelativeDates.relative(diff, now, date, abbrev); if (isPost) { ref = [data].concat(data.clones); for (i = 0, len = ref.length; i < len; i++) { @@ -16015,7 +20214,10 @@ RelativeDates = (function() { if (indexOf.call(RelativeDates.stale, data) >= 0) { return; } - if (data instanceof Post && !g.posts[data.fullID]) { + if (data instanceof Post && !g.posts.get(data.fullID)) { + return; + } + if (data instanceof Element && !doc.contains(data)) { return; } return RelativeDates.stale.push(data); @@ -16042,10 +20244,6 @@ RemoveSpoilers = (function() { name: 'Reveal Spoilers', cb: this.node }); - Callbacks.CatalogThread.push({ - name: 'Reveal Spoilers', - cb: this.node - }); if (g.VIEW === 'archive') { return $.ready(function() { return RemoveSpoilers.unspoiler($.id('arc-list')); @@ -16057,7 +20255,7 @@ RemoveSpoilers = (function() { }, unspoiler: function(el) { var i, len, span, spoiler, spoilers; - spoilers = $$('s', el); + spoilers = $$(g.SITE.selectors.spoiler, el); for (i = 0, len = spoilers.length; i < len; i++) { spoiler = spoilers[i]; span = $.el('span', { @@ -16088,18 +20286,18 @@ Report = (function() { }, ready: function() { $.addStyle(CSS.report); - if (!Conf['Use Recaptcha v1 in Reports'] && !Conf['Force Noscript Captcha'] && Main.jsEnabled) { - return new MutationObserver(function() { - Report.fit('iframe[src^="https://www.google.com/recaptcha/api2/frame"]'); - return Report.fit('body'); - }).observe(d.body, { - childList: true, - attributes: true, - subtree: true - }); - } else { - return Report.fit('body'); + if (Conf['Archive Report']) { + Report.archive(); } + new MutationObserver(function() { + Report.fit('iframe[src^="https://www.google.com/recaptcha/api2/frame"]'); + return Report.fit('body'); + }).observe(d.body, { + childList: true, + attributes: true, + subtree: true + }); + return Report.fit('body'); }, fit: function(selector) { var dy, el; @@ -16110,6 +20308,109 @@ Report = (function() { if (dy > 0) { return window.resizeBy(0, dy); } + }, + archive: function() { + var enabled, fieldset, form, match, message, reason, submit, types, urls; + if (!(urls = Redirect.report(g.BOARD.ID)).length) { + return; + } + form = $('form'); + types = $.id('reportTypes'); + message = $('h3'); + fieldset = $.el('fieldset', { + id: 'archive-report', + hidden: true + }, {innerHTML: ""}); + enabled = $('#archive-report-enabled', fieldset); + reason = $('#archive-report-reason', fieldset); + submit = $('#archive-report-submit', fieldset); + $.on(enabled, 'change', function() { + return reason.disabled = !this.checked; + }); + if (form && types) { + fieldset.hidden = !$('[value="31"]', types).checked; + $.on(types, 'change', function(e) { + fieldset.hidden = e.target.value !== '31'; + return Report.fit('body'); + }); + $.after(types, fieldset); + Report.fit('body'); + $.one(form, 'submit', function(e) { + if (!fieldset.hidden && enabled.checked) { + e.preventDefault(); + return Report.archiveSubmit(urls, reason.value, (function(_this) { + return function(results) { + _this.action = '#archiveresults=' + encodeURIComponent(JSON.stringify(results)); + return _this.submit(); + }; + })(this)); + } + }); + } else if (message) { + fieldset.hidden = /Report submitted!/.test(message.textContent); + $.on(enabled, 'change', function() { + return submit.hidden = !this.checked; + }); + $.after(message, fieldset); + $.on(submit, 'click', function() { + return Report.archiveSubmit(urls, reason.value, Report.archiveResults); + }); + } + if ((match = location.hash.match(/^#archiveresults=(.*)$/))) { + try { + return Report.archiveResults(JSON.parse(decodeURIComponent(match[1]))); + } catch (error) {} + } + }, + archiveSubmit: function(urls, reason, cb) { + var fn, form, i, len, name, ref, results, url; + form = $.formData({ + board: g.BOARD.ID, + num: Report.postID, + reason: reason + }); + results = []; + fn = function(name, url) { + return $.ajax(url, { + onloadend: function() { + results.push([ + name, this.response || { + error: '' + } + ]); + if (results.length === urls.length) { + return cb(results); + } + }, + form: form + }); + }; + for (i = 0, len = urls.length; i < len; i++) { + ref = urls[i], name = ref[0], url = ref[1]; + fn(name, url); + } + }, + archiveResults: function(results) { + var fieldset, i, len, line, name, ref, response; + fieldset = $.id('archive-report'); + for (i = 0, len = results.length; i < len; i++) { + ref = results[i], name = ref[0], response = ref[1]; + line = $.el('h3', { + className: 'archive-report-response' + }); + if ('success' in response) { + $.addClass(line, 'archive-report-success'); + line.textContent = name + ": " + response.success; + } else { + $.addClass(line, 'archive-report-error'); + line.textContent = name + ": " + (response.error || 'Error reporting post.'); + } + if (fieldset) { + $.before(fieldset, line); + } else { + $.add(d.body, line); + } + } } }; @@ -16158,7 +20459,7 @@ Time = (function() { Time = { init: function() { var ref; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Time Formatting'])) { + if (!(((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Time Formatting'])) { return; } return Callbacks.Post.push({ @@ -16167,14 +20468,16 @@ Time = (function() { }); }, node: function() { - if (this.isClone) { + var textContent; + if (!this.info.date || this.isClone) { return; } - return this.nodes.date.textContent = Time.format(Conf['time'], this.info.date); + textContent = this.nodes.date.textContent; + return this.nodes.date.textContent = textContent.match(/^\s*/)[0] + Time.format(Conf['time'], this.info.date) + textContent.match(/\s*$/)[0]; }, format: function(formatString, date) { return formatString.replace(/%(.)/g, function(s, c) { - if (c in Time.formatters) { + if ($.hasOwn(Time.formatters, c)) { return Time.formatters[c].call(date); } else { return s; @@ -16183,6 +20486,30 @@ Time = (function() { }, day: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], month: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + localeFormat: function(date, options, defaultValue) { + if (Conf['timeLocale']) { + try { + return Intl.DateTimeFormat(Conf['timeLocale'], options).format(date); + } catch (error) {} + } + return defaultValue; + }, + localeFormatPart: function(date, options, part, defaultValue) { + var parts; + if (Conf['timeLocale']) { + try { + parts = Intl.DateTimeFormat(Conf['timeLocale'], options).formatToParts(date); + return parts.map(function(x) { + if (x.type === part) { + return x.value; + } else { + return ''; + } + }).join(''); + } catch (error) {} + } + return defaultValue; + }, zeroPad: function(n) { if (n < 10) { return "0" + n; @@ -16192,16 +20519,24 @@ Time = (function() { }, formatters: { a: function() { - return Time.day[this.getDay()].slice(0, 3); + return Time.localeFormat(this, { + weekday: 'short' + }, Time.day[this.getDay()].slice(0, 3)); }, A: function() { - return Time.day[this.getDay()]; + return Time.localeFormat(this, { + weekday: 'long' + }, Time.day[this.getDay()]); }, b: function() { - return Time.month[this.getMonth()].slice(0, 3); + return Time.localeFormat(this, { + month: 'short' + }, Time.month[this.getMonth()].slice(0, 3)); }, B: function() { - return Time.month[this.getMonth()]; + return Time.localeFormat(this, { + month: 'long' + }, Time.month[this.getMonth()]); }, d: function() { return Time.zeroPad(this.getDate()); @@ -16228,18 +20563,13 @@ Time = (function() { return Time.zeroPad(this.getMinutes()); }, p: function() { - if (this.getHours() < 12) { - return 'AM'; - } else { - return 'PM'; - } + return Time.localeFormatPart(this, { + hour: 'numeric', + hour12: true + }, 'dayperiod', (this.getHours() < 12 ? 'AM' : 'PM')); }, P: function() { - if (this.getHours() < 12) { - return 'am'; - } else { - return 'pm'; - } + return Time.formatters.p.call(this).toLowerCase(); }, S: function() { return Time.zeroPad(this.getSeconds()); @@ -16260,6 +20590,61 @@ Time = (function() { }).call(this); +Tinyboard = (function() { + var Tinyboard; + + Tinyboard = { + init: function() { + if (g.SITE.software !== 'tinyboard') { + return; + } + if (g.VIEW === 'thread') { + return Main.ready(function() { + return $.global(function() { + var base, boardID, form, originalNoko, ref, ref1, ref2, threadID; + ref = document.currentScript.dataset, boardID = ref.boardID, threadID = ref.threadID; + threadID = +threadID; + form = document.querySelector('form[name="post"]'); + window.$(document).ajaxComplete(function(event, request, settings) { + var detail, noko, postID, redirect, ref1, ref2; + if (settings.url !== form.action) { + return; + } + if (!(postID = +((ref1 = request.responseJSON) != null ? ref1.id : void 0))) { + return; + } + detail = { + boardID: boardID, + threadID: threadID, + postID: postID + }; + try { + ref2 = request.responseJSON, redirect = ref2.redirect, noko = ref2.noko; + if (redirect && (typeof originalNoko !== "undefined" && originalNoko !== null) && !originalNoko && !noko) { + detail.redirect = redirect; + } + } catch (error) {} + event = new CustomEvent('QRPostSuccessful', { + bubbles: true, + detail: detail + }); + return document.dispatchEvent(event); + }); + originalNoko = (ref1 = window.tb_settings) != null ? (ref2 = ref1.ajax) != null ? ref2.always_noko_replies : void 0 : void 0; + return ((base = (window.tb_settings || (window.tb_settings = {}))).ajax || (base.ajax = {})).always_noko_replies = true; + }, { + boardID: g.BOARD.ID, + threadID: g.THREADID + }); + }); + } + } + }; + + return Tinyboard; + +}).call(this); + Favicon = (function() { var Favicon; @@ -16269,24 +20654,35 @@ Favicon = (function() { return d.head && (Favicon.el = $('link[rel="shortcut icon"]', d.head)); }), Favicon.initAsap); }, + set: function(status) { + Favicon.status = status; + if (Favicon.el) { + Favicon.el.href = Favicon[status]; + return $.add(d.head, Favicon.el); + } + }, initAsap: function() { var href; Favicon.el.type = 'image/x-icon'; href = Favicon.el.href; - Favicon.SFW = /ws\.ico$/.test(href); + Favicon.isSFW = /ws\.ico$/.test(href); Favicon["default"] = href; - return Favicon["switch"](); + Favicon["switch"](); + if (Favicon.status) { + return Favicon.set(Favicon.status); + } }, "switch": function() { var f, i, items, t; items = { ferongr: ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///9zBQC/AADpDAP/gID/q6voCwJJTwpOAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxUlEQVR42q1TOwrCQBB9s0FRtJI0WoqFtSLYegoP4gVSeJsUHsHSI3iFeIqRXXgwrhlXwYHHhLwPTB7B36abBCV+0pA4DUBQUNZYQptGtW3jtoKyxgoe0yrBCoyZfL/5ioQ3URZOXW9I341l3oo+NXEZiW4CEuIzvPECopED4OaZ3RNmeAm4u+a8Jr5f17VyVoL8fr8qcltzwlyyj2iqcgPOQ9ExkHAITgD75bYBe0A5S4H/P9htuWMF3QXoQpwaKeT+lnsC6JE5I6aq6fEAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///8AcH4AtswA2PJ55fKi6fIA1/FtpPADAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxElEQVQ4y2NgoBq4/vE/HJOsBiRQUIfA2AzBqQYqUfn00/9FLz+BaQxDCKqBmX7jExijKEDSDJPHrnnbGQhGV4RmOFwdVkNwhQMheYwQxhaIi7b9Z9A3gWAQm2BUoQOgRhgA8o7j1ozLC4LCyAZcx6kZI5qg4kLKqggDFFWxJySsUQVzlb4pwgAJaTRvokcVNgOqOv8zcHBCsL07DgNg8YsczzA5MxtUL+DMD8g0slxI/H8GQ/P/DJKyeKIRpglXZsIiBwBhP5O+VbI/JgAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///8oeQBJ3ABV/wHM/7Lu/+ZU/gAqUP3dAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAx0lEQVQ4y2NgoBYI+cfwH4ZJVgMS0KhEYGyG4FQDkzjzf9P/d/+fgWl0QwiqgSkI/c8IxsgKkDXD5LFq9rwDweiK0A2HqcNqCK5wICSPEcLYAtH+AMN/IXMIBrEJRie6OEgjDAC5x3FqxuUFNiEUA67j1IweTTBxBQ1puAG86jgSEraogskJWSBcwCGF5k30qMJmgMFEhv/MXBAs5oLDAFj8IsczTE7UEeECbhU8+QGZRpaTi2b4L2zF8J9TGk80wjThykzY5AAW/2O1C2mIbgAAAABJRU5ErkJggg=='], - 'xat-': ['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'], + 'xat-': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAG1BMVEX+AACLkZFub2yfaF3zZGIAAAD/AAD/iYr/zs8IPcF6AAAABXRSTlMAeprJ7xzg6IEAAABZSURBVAjXY2DABKGBSkqioQwMrGmpxsZhaQEMDGFpIa5pqSCRtPDSNJBIaGh5eShQDYOye0V7iREKAyQFYoiCFAcyILQDGcGmEEZYkGoqiMHKysAQEICwGwAAjBmBqhYlagAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAPFBMVEUAAACEgoBva2ilamDxcG7IaWYgFBNOSEf//f0PDQwBAAA7LCwAAAD/AAD+hIX+m5z+zc5HAADPAAAGAADl032uAAAADHRSTlMAzNv0/vz+6v3+7ALrmfyXAAAAaUlEQVQY042PyxKAIAhFAc1eV7T6/3/N8VXOtAgWwBm4ANEPA8AswpySXHvvYZLlpBNrh9pDtcSqAQ1BUTVIjNUQY5icmwfglmXNgE0d6QBF9GigrU0A9LoM53U1kFzk6SBQuWfD/vHqDUCpBmVKTTM4AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAIVBMVEUAAACRjop4dXVpZ2tdcI9dfKdisfMAAAAumMN9xv+s2/+PADT2AAAAB3RSTlMAepGdv83v3HIc4QAAAFxJREFUCNdjYMAE5YXKRuLlDAzsHe2uIRUdBQwMFR1l6R3tIJGOyukdIJHy8lkry4FqGEwzV62aFozMUAFJOQEZ4iDFhQwI7UBGaTiEUVFs3g5isLMzMBQUIOwGAJRlIu9hk08QAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEUAAACAgYVlc4ljsu4AAAAAAAAAAAAumMODyP6b1P6e1f/g8v89msgSIiwNFxwbPU3tQYj5AAAABnRSTlMAxej+9VTmD9ciAAAAZElEQVQI12NgwARpiUKKYmkMDGzlZUpK6eUJDAzp5clm5WUgkfKMtnKQSFpa54o0oBoGJYvZO88+gjJu7wMyhIBS2SCGGFDxaxADpP32NjAjSe0bSFd6epIaWISNjYEhJRVhNwAGlyJpYtcvcAAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAHlBMVEUfJSCRi5Frbm9dn19082KR/30AAABmzDOq/5vZ/9Gt/vt2AAAABnRSTlMAe5rJ7/4vxEp4AAAAWUlEQVQI12NgwARpiUpKYmkMDGzlZcbG6eUJDAzp5Slu5WUgkfLUsHKQSFpaRGsaUA2DsmvnjBAjFAZICsQQAylOZEBoBzKSzSCM9CS1MhCDjY2BISEBYTcAtgAcKSK2vuIAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAM1BMVEUAAACBj39tfm1qj2RepFlu2VQAAQAAAAAAAABmyzOX/oSr/pus/pzk/98PGgtatC4CBAI1ENblAAAACHRSTlMA09/p9v77ig0SBcQAAABnSURBVBjTjY9LDsAgCEQRsR2xWu9/2hK/adJFYQG8wABEPwyAYzNnSatjjPAiviWLhPCqI1R7HBrQdCmGBrEETTmnUAq/QMm5dODHyAQOXXR1zLUGsIEI7lonMGfeHQTq9xw4P159AIxSBSC53km7AAAAAElFTkSuQmCC'], Mayhem: ['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'], - '4chanJS': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAD/AABnZ2f///8nFk05AAAAAXRSTlMAQObYZgAAAEFJREFUeNqNjgEKACAMAjvX/98cAkkxgmSgO8Bt/Ai4ApJ6KKhzF3OiEMDASrGB/QWgPEHsUpN+Ng9xAETMYhDrWmeHAMcmvycWAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAD/AAD///9nZ2f77Y6hAAAAAXRSTlMAQObYZgAAAEBJREFUeF6NjQEKACAMAnfW/98cAxFiBIngOsTqR8B1IGkeG9p5i7XabgAGZNigXgA8aoCUxvzWAIcBItGiSEwdccYA3BuRAWkAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAAul8NnZ2f////82iC9AAAAAXRSTlMAQObYZgAAAEFJREFUeNqNjgEKACAMAjvX/98cAkkxgmSgO8Bt/Ai4ApJ6KKhzF3OiEMDASrGB/QWgPEHsUpN+Ng9xAETMYhDrWmeHAMcmvycWAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAAul8P///9nZ2cgIeMlAAAAAXRSTlMAQObYZgAAAEBJREFUeF6NjQEKACAMAnfW/98cAxFiBIngOsTqR8B1IGkeG9p5i7XabgAGZNigXgA8aoCUxvzWAIcBItGiSEwdccYA3BuRAWkAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAElBMVEUBAAAAAABmzDNlyjJnZ2f///+6o7dfAAAAAXRSTlMAQObYZgAAAERJREFUeF6NjkEKADEIA51o///lJZfQxUsHITogWi8AvwZJuxmYa25xDooBLEwOWFTYAsYVhdorLZt9Ng9xCUTCUCQ2H3F4ANrZ2WNiAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAElBMVEUBAAAAAABmzDP///9lyjJnZ2cIHys9AAAAAXRSTlMAQObYZgAAAENJREFUeF6NjUEKwEAMAjNm9/9fLkEslFwqgjoEUn8EfAqSdrkwzj6ieyyTkQEVGWRvANfO1iEX620AjgBEwqR4Y+sBeGAA6d+vQ4IAAAAASUVORK5CYII='], + '4chanJS': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAD/AABnZ2f///8nFk05AAAAAXRSTlMAQObYZgAAAEFJREFUeNqNjgEKACAMAjvX/98cAkkxgmSgO8Bt/Ai4ApJ6KKhzF3OiEMDASrGB/QWgPEHsUpN+Ng9xAETMYhDrWmeHAMcmvycWAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAD1BMVEUAAAAAAAD/AABmZmYA/wBD99DBAAAAAXRSTlMAQObYZgAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAE9JREFUCNdljcsRACEIQ5MOiNKAdGAJ9N/Uiu7nsMzABHgB4B8ygFoZA2hhVWavhhGeURPJU9q45+17hGbfGxa82Ndex3hEM44SJGD2/b4AzDgGlHbl388AAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAAul8NnZ2f////82iC9AAAAAXRSTlMAQObYZgAAAEFJREFUeNqNjgEKACAMAjvX/98cAkkxgmSgO8Bt/Ai4ApJ6KKhzF3OiEMDASrGB/QWgPEHsUpN+Ng9xAETMYhDrWmeHAMcmvycWAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAD1BMVEUAAAAAAAAul8NnZ2f/AAD7B+mqAAAAAXRSTlMAQObYZgAAAAlwSFlzAAALEgAACxIB0t1+/AAAAE9JREFUCNdljcsRACEIQ5MOiNKAdGAJ9N/Uiu7nsMzABHgB4B8ygFoZA2hhVWavhhGeURPJU9q45+17hGbfGxa82Ndex3hEM44SJGD2/b4AzDgGlHbl388AAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAElBMVEUBAAAAAABmzDNlyjJnZ2f///+6o7dfAAAAAXRSTlMAQObYZgAAAERJREFUeF6NjkEKADEIA51o///lJZfQxUsHITogWi8AvwZJuxmYa25xDooBLEwOWFTYAsYVhdorLZt9Ng9xCUTCUCQ2H3F4ANrZ2WNiAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAD1BMVEUAAAAAAABmzDNmZmb/AAC8/wCMAAAAAXRSTlMAQObYZgAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAE9JREFUCNdljcsRACEIQ5MOiNKAdGAJ9N/Uiu7nsMzABHgB4B8ygFoZA2hhVWavhhGeURPJU9q45+17hGbfGxa82Ndex3hEM44SJGD2/b4AzDgGlHbl388AAAAASUVORK5CYII='], Original: ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEX/////AAD///8AAABBZmS3AAAAAXRSTlMAQObYZgAAAExJREFUeF4tyrENgDAMAMFXKuQswQLBG3mOlBnFS1gwDfIYLpEivvjq2MlqjmYvYg5jWEzCwtDSQlwcXKCVLrpFbvLvvSf9uZJ2HusDtJAY7Tkn1oYAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAhElEQVR42q1RwQnAMAjMu5M4guAKXa4j5dUROo5tipSDcrFChUONd0di2m/hEGVOHDyIPufgwAFASDkpoSzmBrkJ2UMyR9LsJ3rvrqo3Rt1YMIMhhNnOxLMnoMFBxHyJAr2IOBFzA8U+6pLBdmEJTA0aMVjpDd6Loks0s5HZNwYx8tfZCZ0kll7ORffZAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEX///8ul8P///8AAACaqgkzAAAAAXRSTlMAQObYZgAAAExJREFUeF4tyrENgDAMAMFXKuQswQLBG3mOlBnFS1gwDfIYLpEivvjq2MlqjmYvYg5jWEzCwtDSQlwcXKCVLrpFbvLvvSf9uZJ2HusDtJAY7Tkn1oYAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAALVBMVEUAAAAAAAAAAAAAAAABBQcHFx4KISoNLToaVW4oKCgul8M4ODg7OzvBwcH///8uS/CdAAAAA3RSTlMAx9dmesIgAAAAV0lEQVR42m2NWw6AIBAD1eILZO5/XI0UAgm7H9tOsu0yGWAQSOoFijHOxOANGqm/LczpOaXs4gISrPZ+gc2+hO5w2xdwgOjBFUIF+sEJrhUl9JFr+badFwR+BfqlmGUJAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEX///9mzDP///8AAACT0n1lAAAAAXRSTlMAQObYZgAAAExJREFUeF4tyrENgDAMAMFXKuQswQLBG3mOlBnFS1gwDfIYLpEivvjq2MlqjmYvYg5jWEzCwtDSQlwcXKCVLrpFbvLvvSf9uZJ2HusDtJAY7Tkn1oYAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAALVBMVEUAAAAAAAAAAAAAAAAECAIQIAgWLAsePA8oKCg4ODg6dB07OztmzDPBwcH///+rsf3XAAAAA3RSTlMAx9dmesIgAAAAV0lEQVR42m2NWw6AIBAD1eIDhbn/cTVSCCTsfmw7ybbLZIBBIKkXKKU0E4M3aKT+tjCn5xiziwuIsNr7BTb7ErrDZV/AAaIHdwgV6AcnuFaU0Eeu5dt2XiUyBjCQ2bIrAAAAAElFTkSuQmCC'], - 'Metro': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAC/AABrZQDiAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAAHAAAdAAApAAAsAAA4AABsAACQAAC/AAD///9SVhtjAAAAA3RSTlMAPse+s4iwAAAAM0lEQVQIW2NggAGuVasWgDBpDDAQUoSaob0Jao73lgVojOitUEazBZRRvR3KmJa5AO4KAGBtLuMAuhIIAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAAA1/GhpCidAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAAACAkAISUALzQAMTcAQEcAeokAorYA1/H///8BrzTFAAAAA3RSTlMAPse+s4iwAAAAM0lEQVQIW2NggAGuVasWgDBpDDAQUoSaob0Jao73lgVojOitUEazBZRRvR3KmJa5AO4KAGBtLuMAuhIIAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAABV/wErM5hwAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAADCgANKAASOAATOwAZTAAwkQBAwQBV/wH////+Fmy4AAAAA3RSTlMAPse+s4iwAAAAM0lEQVQIW2NggAGuVasWgDBpDDAQUoSaob0Jao73lgVojOitUEazBZRRvR3KmJa5AO4KAGBtLuMAuhIIAAAAAElFTkSuQmCC'] - }[Conf['favicon']]; + 'Metro': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAC/AABrZQDiAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAC/AAD///8dAAApAABsAAAHAAA4AACQAAAsAABMCpCvAAAAA3RSTlMAPse+s4iwAAAAMklEQVQI12NggAFmY2MDECaNAQZCilAzVJyg5oS4GqAxUtygjIp2KGOKJ5SxepcB3BUAcdYRqxAtgFoAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAAA1/GhpCidAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAAA1/H///8AISUALzQAeokACAkAQEcAorYAMTcE9WFNAAAAA3RSTlMAPse+s4iwAAAAMklEQVQI12NggAFmY2MDECaNAQZCilAzVJyg5oS4GqAxUtygjIp2KGOKJ5SxepcB3BUAcdYRqxAtgFoAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAABV/wErM5hwAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAABV/wH///8NKAASOAAwkQADCgAZTABAwQATOwC5e3VGAAAAA3RSTlMAPse+s4iwAAAAMklEQVQI12NggAFmY2MDECaNAQZCilAzVJyg5oS4GqAxUtygjIp2KGOKJ5SxepcB3BUAcdYRqxAtgFoAAAAASUVORK5CYII='] + }; + items = $.getOwn(items, Conf['favicon']); f = Favicon; t = 'data:image/png;base64,'; i = 0; @@ -16297,7 +20693,7 @@ Favicon = (function() { return f.update(); }, update: function() { - if (this.SFW) { + if (this.isSFW) { this.unread = this.unreadSFW; return this.unreadY = this.unreadSFWY; } else { @@ -16305,6 +20701,8 @@ Favicon = (function() { return this.unreadY = this.unreadNSFWY; } }, + SFW: '//s.4cdn.org/image/favicon-ws.ico', + NSFW: '//s.4cdn.org/image/favicon.ico', dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==', logo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAgMAAAC+UIlYAAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAGlJREFUWMPtlkEKADEIA/tJP9lXLttQto2yHxgDHozTi0ToGK2WKZZ+HAQQMZc+xBwI4EZ+wAC2IfPuSIDOZJrSZQEAX9eVJhhwIuUYAnQe8rhAEMAZlTI2MID9f5Clyh0JeE1V1ZEAvB4qDfwuJTSGRAAAAABJRU5ErkJggg==' }; @@ -16318,7 +20716,7 @@ MarkNewIPs = (function() { MarkNewIPs = { init: function() { - if (g.VIEW !== 'thread' || !Conf['Mark New IPs']) { + if (!(g.SITE.software === 'yotsuba' && g.VIEW === 'thread' && Conf['Mark New IPs'])) { return; } return Callbacks.Thread.push({ @@ -16342,13 +20740,13 @@ MarkNewIPs = (function() { i = MarkNewIPs.ipCount; for (j = 0, len = newPosts.length; j < len; j++) { fullID = newPosts[j]; - MarkNewIPs.markNew(g.posts[fullID], ++i); + MarkNewIPs.markNew(g.posts.get(fullID), ++i); } break; case -deletedPosts.length: for (k = 0, len1 = newPosts.length; k < len1; k++) { fullID = newPosts[k]; - MarkNewIPs.markOld(g.posts[fullID]); + MarkNewIPs.markOld(g.posts.get(fullID)); } } MarkNewIPs.ipCount = ipCount; @@ -16384,7 +20782,6 @@ ReplyPruning = (function() { if (!(g.VIEW === 'thread' && Conf['Reply Pruning'])) { return; } - this.active = !(Conf['Quote Threading'] && Conf['Thread Quotes']); this.container = $.frag(); this.summary = $.el('span', { hidden: true, @@ -16397,17 +20794,16 @@ ReplyPruning = (function() { return $.event('change', null, _this.inputs.enabled); }; })(this)); - label = UI.checkbox('Prune Replies', 'Show Last', this.active); + label = UI.checkbox('Prune Replies', 'Show Last', Conf['Prune All Threads']); el = $.el('span', { title: 'Maximum number of replies to show.' - }, { - innerHTML: " " - }); + }, {innerHTML: " "}); $.prepend(el, label); this.inputs = { enabled: label.firstElementChild, replies: el.lastElementChild }; + this.setEnabled.call(this.inputs.enabled); $.on(this.inputs.enabled, 'change', this.setEnabled); $.on(this.inputs.replies, 'change', $.cb.value); Header.menu.addEntry({ @@ -16442,6 +20838,12 @@ ReplyPruning = (function() { node: function() { var ref; ReplyPruning.thread = this; + if (this.isSticky) { + ReplyPruning.active = ReplyPruning.inputs.enabled.checked = true; + if (QuoteThreading.input) { + Conf['Thread Quotes'] = QuoteThreading.input.checked = false; + } + } this.posts.forEach(function(post) { if (post.isReply) { ReplyPruning.total++; @@ -16450,7 +20852,7 @@ ReplyPruning = (function() { } } }); - if (ReplyPruning.active && /^#p\d+$/.test(location.hash) && (0 <= (ref = this.posts.keys.indexOf(location.hash.slice(2))) && ref < 1 + Math.max(ReplyPruning.total - +Conf["Max Replies"], 0))) { + if (ReplyPruning.active && /^#p\d+$/.test(location.hash) && (1 <= (ref = this.posts.keys.indexOf(location.hash.slice(2))) && ref < 1 + Math.max(ReplyPruning.total - +Conf["Max Replies"], 0))) { ReplyPruning.active = ReplyPruning.inputs.enabled.checked = false; } $.after(this.OP.nodes.root, ReplyPruning.summary); @@ -16469,21 +20871,24 @@ ReplyPruning = (function() { for (i = 0, len = ref.length; i < len; i++) { fullID = ref[i]; ReplyPruning.total++; - if (g.posts[fullID].file) { + if (g.posts.get(fullID).file) { ReplyPruning.totalFiles++; } } }, update: function() { - var boardTop, frag, hidden1, hidden2, oldPos, post, posts; + var boardTop, frag, hidden1, hidden2, node, oldPos, post, posts; hidden1 = ReplyPruning.hidden; hidden2 = ReplyPruning.active ? Math.max(ReplyPruning.total - +Conf["Max Replies"], 0) : 0; oldPos = d.body.clientHeight - window.scrollY; posts = ReplyPruning.thread.posts; if (ReplyPruning.hidden < hidden2) { while (ReplyPruning.hidden < hidden2 && ReplyPruning.position < posts.keys.length) { - post = posts[posts.keys[ReplyPruning.position++]]; + post = posts.get(posts.keys[ReplyPruning.position++]); if (post.isReply && !post.isFetchedQuote) { + while ((node = ReplyPruning.summary.nextSibling) && node !== post.nodes.root) { + $.add(ReplyPruning.container, node); + } $.add(ReplyPruning.container, post.nodes.root); ReplyPruning.hidden++; if (post.file) { @@ -16494,8 +20899,11 @@ ReplyPruning = (function() { } else if (ReplyPruning.hidden > hidden2) { frag = $.frag(); while (ReplyPruning.hidden > hidden2 && ReplyPruning.position > 0) { - post = posts[posts.keys[--ReplyPruning.position]]; + post = posts.get(posts.keys[--ReplyPruning.position]); if (post.isReply && !post.isFetchedQuote) { + while ((node = ReplyPruning.container.lastChild) && node !== post.nodes.root) { + $.prepend(frag, node); + } $.prepend(frag, post.nodes.root); ReplyPruning.hidden--; if (post.file) { @@ -16504,9 +20912,9 @@ ReplyPruning = (function() { } } $.after(ReplyPruning.summary, frag); - $.event('PostsInserted'); + $.event('PostsInserted', null, ReplyPruning.summary.parentNode); } - ReplyPruning.summary.textContent = ReplyPruning.active ? Build.summaryText('+', ReplyPruning.hidden, ReplyPruning.hiddenFiles) : Build.summaryText('-', ReplyPruning.total, ReplyPruning.totalFiles); + ReplyPruning.summary.textContent = ReplyPruning.active ? g.SITE.Build.summaryText('+', ReplyPruning.hidden, ReplyPruning.hiddenFiles) : g.SITE.Build.summaryText('-', ReplyPruning.total, ReplyPruning.totalFiles); ReplyPruning.summary.hidden = ReplyPruning.total <= +Conf["Max Replies"]; if (hidden1 !== hidden2 && (boardTop = Header.getTopOf($('.board'))) < 0) { return window.scrollBy(0, Math.max(d.body.clientHeight - oldPos, window.scrollY + boardTop) - window.scrollY); @@ -16522,20 +20930,24 @@ ThreadStats = (function() { var ThreadStats; ThreadStats = { + postCount: 0, + fileCount: 0, + postIndex: 0, init: function() { - var sc, statsHTML, statsTitle; + var base, sc, statsHTML, statsTitle; if (g.VIEW !== 'thread' || !Conf['Thread Stats']) { return; } - statsHTML = { - innerHTML: "? / ?" + ((Conf["IP Count in Stats"]) ? " / ?" : "") + ((Conf["Page Count in Stats"]) ? " / ?" : "") - }; + if (Conf['Page Count in Stats']) { + this[(typeof (base = g.SITE).isPrunedByAge === "function" ? base.isPrunedByAge(g.BOARD) : void 0) ? 'showPurgePos' : 'showPage'] = true; + } + statsHTML = {innerHTML: "? / ?" + ((Conf["IP Count in Stats"] && g.SITE.hasIPCount) ? " / ?" : "") + ((Conf["Page Count in Stats"]) ? " / ?" : "")}; statsTitle = 'Posts / Files'; - if (Conf['IP Count in Stats']) { + if (Conf['IP Count in Stats'] && g.SITE.hasIPCount) { statsTitle += ' / IPs'; } if (Conf['Page Count in Stats']) { - statsTitle += (g.BOARD.ID === 'f' ? ' / Purge Position' : ' / Page'); + statsTitle += (this.showPurgePos ? ' / Purge Position' : ' / Page'); } if (Conf['Updater and Stats in Header']) { this.dialog = sc = $.el('span', { @@ -16545,9 +20957,7 @@ ThreadStats = (function() { $.extend(sc, statsHTML); Header.addShortcut('stats', sc, 200); } else { - this.dialog = sc = UI.dialog('thread-stats', 'bottom: 0px; right: 0px;', { - innerHTML: "
      " + (statsHTML).innerHTML + "
      " - }); + this.dialog = sc = UI.dialog('thread-stats', {innerHTML: "
      " + (statsHTML).innerHTML + "
      "}); $.addClass(doc, 'float'); $.ready(function() { return $.add(d.body, sc); @@ -16566,50 +20976,64 @@ ThreadStats = (function() { }); }, node: function() { - var fileCount, postCount; - postCount = 0; - fileCount = 0; - this.posts.forEach(function(post) { - postCount++; - if (post.file) { - fileCount++; - } - if (ThreadStats.pageCountEl) { - return ThreadStats.lastPost = post.info.date; - } - }); ThreadStats.thread = this; + ThreadStats.count(); + ThreadStats.update(); ThreadStats.fetchPage(); - ThreadStats.update(postCount, fileCount, this.ipCount); + $.on(d, 'PostsInserted', function() { + return $.queueTask(ThreadStats.onPostsInserted); + }); return $.on(d, 'ThreadUpdate', ThreadStats.onUpdate); }, + count: function() { + var i, j, n, post, posts, ref, ref1; + posts = ThreadStats.thread.posts; + n = posts.keys.length; + for (i = j = ref = ThreadStats.postIndex, ref1 = n; j < ref1; i = j += 1) { + post = posts.get(posts.keys[i]); + if (!post.isFetchedQuote) { + ThreadStats.postCount++; + ThreadStats.fileCount += post.files.length; + } + } + return ThreadStats.postIndex = n; + }, onUpdate: function(e) { - var fileCount, ipCount, newPosts, postCount, ref, ref1; + var fileCount, postCount, ref; if (e.detail[404]) { return; } - ref = e.detail, postCount = ref.postCount, fileCount = ref.fileCount, ipCount = ref.ipCount, newPosts = ref.newPosts; - ThreadStats.update(postCount, fileCount, ipCount); - if (!ThreadStats.pageCountEl) { - return; + ref = e.detail, postCount = ref.postCount, fileCount = ref.fileCount; + $.extend(ThreadStats, { + postCount: postCount, + fileCount: fileCount + }); + ThreadStats.postIndex = ThreadStats.thread.posts.keys.length; + ThreadStats.update(); + if (ThreadStats.showPage && ThreadStats.pageCountEl.textContent !== '1') { + return ThreadStats.fetchPage(); } - if (newPosts.length) { - ThreadStats.lastPost = g.posts[newPosts[newPosts.length - 1]].info.date; + }, + onPostsInserted: function() { + if (!(ThreadStats.thread.posts.keys.length > ThreadStats.postIndex)) { + return; } - if (g.BOARD.ID !== 'f' && ((ref1 = ThreadStats.pageCountEl) != null ? ref1.textContent : void 0) !== '1') { + ThreadStats.count(); + ThreadStats.update(); + if (ThreadStats.showPage && ThreadStats.pageCountEl.textContent !== '1') { return ThreadStats.fetchPage(); } }, - update: function(postCount, fileCount, ipCount) { - var fileCountEl, ipCountEl, postCountEl, thread; + update: function() { + var fileCountEl, ipCountEl, postCountEl, ref, thread; thread = ThreadStats.thread, postCountEl = ThreadStats.postCountEl, fileCountEl = ThreadStats.fileCountEl, ipCountEl = ThreadStats.ipCountEl; - postCountEl.textContent = postCount; - fileCountEl.textContent = fileCount; - if ((ipCount != null) && ipCountEl) { - ipCountEl.textContent = ipCount; + postCountEl.textContent = ThreadStats.postCount; + fileCountEl.textContent = ThreadStats.fileCount; + if (ipCountEl != null) { + ipCountEl.textContent = (ref = thread.ipCount) != null ? ref : '?'; } - (thread.postLimit && !thread.isSticky ? $.addClass : $.rmClass)(postCountEl, 'warning'); - return (thread.fileLimit && !thread.isSticky ? $.addClass : $.rmClass)(fileCountEl, 'warning'); + postCountEl.classList.toggle('warning', thread.postLimit && !thread.isSticky); + return fileCountEl.classList.toggle('warning', thread.fileLimit && !thread.isSticky); }, fetchPage: function() { if (!ThreadStats.pageCountEl) { @@ -16622,40 +21046,47 @@ ThreadStats = (function() { return; } ThreadStats.timeout = setTimeout(ThreadStats.fetchPage, 2 * $.MINUTE); - return $.ajax("//a.4cdn.org/" + ThreadStats.thread.board + "/threads.json", { - onload: ThreadStats.onThreadsLoad - }, { - whenModified: 'ThreadStats' - }); + return $.whenModified(g.SITE.urls.threadsListJSON(ThreadStats.thread), 'ThreadStats', ThreadStats.onThreadsLoad); }, onThreadsLoad: function() { - var i, j, k, len, len1, len2, page, purgePos, ref, ref1, ref2, thread; + var i, j, k, l, len, len1, len2, len3, len4, m, nThreads, o, page, pageNum, purgePos, ref, ref1, ref2, ref3, ref4, thread; if (this.status === 200) { - ref = this.response; - for (i = 0, len = ref.length; i < len; i++) { - page = ref[i]; - if (g.BOARD.ID === 'f') { - purgePos = 1; + if (ThreadStats.showPurgePos) { + purgePos = 1; + ref = this.response; + for (j = 0, len = ref.length; j < len; j++) { + page = ref[j]; ref1 = page.threads; - for (j = 0, len1 = ref1.length; j < len1; j++) { - thread = ref1[j]; + for (k = 0, len1 = ref1.length; k < len1; k++) { + thread = ref1[k]; if (thread.no < ThreadStats.thread.ID) { purgePos++; } } - ThreadStats.pageCountEl.textContent = purgePos; - } else { - ref2 = page.threads; - for (k = 0, len2 = ref2.length; k < len2; k++) { - thread = ref2[k]; - if (!(thread.no === ThreadStats.thread.ID)) { - continue; + } + ThreadStats.pageCountEl.textContent = purgePos; + return ThreadStats.pageCountEl.classList.toggle('warning', purgePos === 1); + } else { + i = nThreads = 0; + ref2 = this.response; + for (l = 0, len2 = ref2.length; l < len2; l++) { + page = ref2[l]; + nThreads += page.threads.length; + } + ref3 = this.response; + for (pageNum = m = 0, len3 = ref3.length; m < len3; pageNum = ++m) { + page = ref3[pageNum]; + ref4 = page.threads; + for (o = 0, len4 = ref4.length; o < len4; o++) { + thread = ref4[o]; + if (thread.no === ThreadStats.thread.ID) { + ThreadStats.pageCountEl.textContent = pageNum + 1; + ThreadStats.pageCountEl.classList.toggle('warning', i >= nThreads - this.response[0].threads.length); + ThreadStats.lastPageUpdate = new Date(thread.last_modified * $.SECOND); + ThreadStats.retry(); + return; } - ThreadStats.pageCountEl.textContent = page.page; - (page.page === this.response.length ? $.addClass : $.rmClass)(ThreadStats.pageCountEl, 'warning'); - ThreadStats.lastPageUpdate = new Date(thread.last_modified * $.SECOND); - ThreadStats.retry(); - return; + i++; } } } @@ -16664,11 +21095,11 @@ ThreadStats = (function() { } }, retry: function() { - var ref; - if (g.BOARD.ID !== 'f' && ThreadStats.lastPost > ThreadStats.lastPageUpdate && ((ref = ThreadStats.pageCountEl) != null ? ref.textContent : void 0) !== '1') { - clearTimeout(ThreadStats.timeout); - return ThreadStats.timeout = setTimeout(ThreadStats.fetchPage, 5 * $.SECOND); + if (!(ThreadStats.showPage && ThreadStats.pageCountEl.textContent !== '1' && !g.SITE.threadModTimeIgnoresSage && ThreadStats.thread.posts.get(ThreadStats.thread.lastPost).info.date > ThreadStats.lastPageUpdate)) { + return; } + clearTimeout(ThreadStats.timeout); + return ThreadStats.timeout = setTimeout(ThreadStats.fetchPage, 5 * $.SECOND); } }; @@ -16686,6 +21117,7 @@ ThreadUpdater = (function() { if (g.VIEW !== 'thread' || !Conf['Thread Updater']) { return; } + this.enabled = true; this.audio = $.el('audio'); if ($.engine !== 'gecko') { this.audio.src = this.beep; @@ -16694,14 +21126,10 @@ ThreadUpdater = (function() { this.dialog = sc = $.el('span', { id: 'updater' }); - $.extend(sc, { - innerHTML: "" - }); + $.extend(sc, {innerHTML: ""}); Header.addShortcut('updater', sc, 100); } else { - this.dialog = sc = UI.dialog('updater', 'bottom: 0px; left: 0px;', { - innerHTML: "
      " - }); + this.dialog = sc = UI.dialog('updater', {innerHTML: "
      "}); $.addClass(doc, 'float'); $.ready(function() { return $.add(d.body, sc); @@ -16715,9 +21143,7 @@ ThreadUpdater = (function() { updateLink = $.el('span', { className: 'brackets-wrap updatelink' }); - $.extend(updateLink, { - innerHTML: "Update" - }); + $.extend(updateLink, {innerHTML: "Update"}); Main.ready(function() { var navLinksBot; if ((navLinksBot = $('.navLinksBot'))) { @@ -16743,9 +21169,7 @@ ThreadUpdater = (function() { el: el }); } - this.settings = $.el('span', { - innerHTML: "Interval" - }); + this.settings = $.el('span', {innerHTML: "Interval"}); $.on(this.settings, 'click', this.intervalShortcut); subEntries.push({ el: this.settings @@ -16764,7 +21188,7 @@ ThreadUpdater = (function() { }, node: function() { ThreadUpdater.thread = this; - ThreadUpdater.root = this.OP.nodes.root.parentNode; + ThreadUpdater.root = this.nodes.root; ThreadUpdater.outdateCount = 0; ThreadUpdater.postIDs = []; ThreadUpdater.fileIDs = []; @@ -16835,11 +21259,12 @@ ThreadUpdater = (function() { } }, load: function() { - var req; - req = ThreadUpdater.req; - switch (req.status) { + if (this !== ThreadUpdater.req) { + return; + } + switch (this.status) { case 200: - ThreadUpdater.parse(req); + ThreadUpdater.parse(this); if (ThreadUpdater.thread.isArchived) { return ThreadUpdater.kill(); } else { @@ -16847,7 +21272,9 @@ ThreadUpdater = (function() { } break; case 404: - return $.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/catalog.json", { + return $.ajax(g.SITE.urls.catalogJSON({ + boardID: ThreadUpdater.thread.board.ID + }), { onloadend: function() { var confirmed, i, k, len, len1, page, ref, ref1, thread; if (this.status === 200) { @@ -16870,12 +21297,12 @@ ThreadUpdater = (function() { if (confirmed) { return ThreadUpdater.kill(); } else { - return ThreadUpdater.error(req); + return ThreadUpdater.error(this); } } }); default: - return ThreadUpdater.error(req); + return ThreadUpdater.error(this); } } }, @@ -16953,17 +21380,18 @@ ThreadUpdater = (function() { return ThreadUpdater.seconds--; }, update: function() { - var ref; + var oldReq; clearTimeout(ThreadUpdater.timeoutID); ThreadUpdater.set('timer', '...', 'loading'); - if ((ref = ThreadUpdater.req) != null) { - ref.abort(); - } - return ThreadUpdater.req = $.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/thread/" + ThreadUpdater.thread + ".json", { - onloadend: ThreadUpdater.cb.load, + if ((oldReq = ThreadUpdater.req)) { + delete ThreadUpdater.req; + oldReq.abort(); + } + return ThreadUpdater.req = $.whenModified(g.SITE.urls.threadJSON({ + boardID: ThreadUpdater.thread.board.ID, + threadID: ThreadUpdater.thread.ID + }), 'ThreadUpdater', ThreadUpdater.cb.load, { timeout: $.MINUTE - }, { - whenModified: 'ThreadUpdater' }); }, updateThreadStatus: function(type, status) { @@ -16985,10 +21413,10 @@ ThreadUpdater = (function() { thread = ThreadUpdater.thread; board = thread.board; ref = ThreadUpdater.postIDs, lastPost = ref[ref.length - 1]; - if (postObjects[postObjects.length - 1].no < lastPost && new Date(req.getResponseHeader('Last-Modified')) - thread.posts[lastPost].info.date < 30 * $.SECOND) { + if (postObjects[postObjects.length - 1].no < lastPost && new Date(req.getResponseHeader('Last-Modified')) - thread.posts.get(lastPost).info.date < 30 * $.SECOND) { return; } - Build.spoilerRange[board] = OP.custom_spoiler; + g.SITE.Build.spoilerRange[board] = OP.custom_spoiler; thread.setStatus('Archived', !!OP.archived); ThreadUpdater.updateThreadStatus('Sticky', !!OP.sticky); ThreadUpdater.updateThreadStatus('Closed', !!OP.closed); @@ -17011,12 +21439,12 @@ ThreadUpdater = (function() { if (ID <= lastPost) { continue; } - if ((post = thread.posts[ID]) && !post.isFetchedQuote) { + if ((post = thread.posts.get(ID)) && !post.isFetchedQuote) { post.resurrect(); continue; } newPosts.push(board + "." + ID); - node = Build.postFromObject(postObject, board.ID); + node = g.SITE.Build.postFromObject(postObject, board.ID); posts.push(new Post(node, thread, board)); if (ThreadUpdater.postID === ID) { delete ThreadUpdater.postID; @@ -17029,7 +21457,7 @@ ThreadUpdater = (function() { if (!(indexOf.call(index, ID) < 0)) { continue; } - thread.posts[ID].kill(); + thread.posts.get(ID).kill(); deletedPosts.push(board + "." + ID); } ThreadUpdater.postIDs = index; @@ -17040,7 +21468,7 @@ ThreadUpdater = (function() { if (!(!(indexOf.call(files, ID) >= 0 || (ref3 = board + "." + ID, indexOf.call(deletedPosts, ref3) >= 0)))) { continue; } - thread.posts[ID].kill(true); + thread.posts.get(ID).kill(true); deletedFiles.push(board + "." + ID); } ThreadUpdater.fileIDs = files; @@ -17071,7 +21499,7 @@ ThreadUpdater = (function() { $.add(ThreadUpdater.root, post.nodes.root); } } - $.event('PostsInserted'); + $.event('PostsInserted', null, ThreadUpdater.root); if (scroll) { if (Conf['Bottom Scroll']) { window.scrollTo(0, d.body.clientHeight); @@ -17106,11 +21534,12 @@ ThreadUpdater = (function() { ThreadWatcher = (function() { var ThreadWatcher, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, slice = [].slice; ThreadWatcher = { init: function() { - var sc; + var ref, sc; if (!(this.enabled = Conf['Thread Watcher'])) { return; } @@ -17119,26 +21548,26 @@ ThreadWatcher = (function() { textContent: 'Watcher', title: 'Thread Watcher', href: 'javascript:;', - className: 'disabled fa fa-eye' + className: 'fa fa-eye' }); this.db = new DataBoard('watchedThreads', this.refresh, true); - this.dialog = UI.dialog('thread-watcher', 'top: 50px; left: 0px;', { - innerHTML: "
      Thread Watcher ×
      " - }); + this.dbLM = new DataBoard('watcherLastModified', null, true); + this.dialog = UI.dialog('thread-watcher', {innerHTML: "
      Thread Watcher ×
      "}); this.status = $('#watcher-status', this.dialog); this.list = this.dialog.lastElementChild; this.refreshButton = $('.refresh', this.dialog); this.closeButton = $('.move > .close', this.dialog); - this.unreaddb = Unread.db || new DataBoard('lastReadPosts'); + this.unreaddb = Unread.db || UnreadIndex.db || new DataBoard('lastReadPosts'); this.unreadEnabled = Conf['Remember Last Read Post']; $.on(d, 'QRPostSuccessful', this.cb.post); $.on(sc, 'click', this.toggleWatcher); $.on(this.refreshButton, 'click', this.buttonFetchAll); $.on(this.closeButton, 'click', this.toggleWatcher); - $.on(d, '4chanXInitFinished', this.ready); + this.menu.addHeaderMenuEntry(); + $.onExists(doc, 'body', this.addDialog); switch (g.VIEW) { case 'index': - $.on(d, 'IndexRefresh', this.cb.onIndexRefresh); + $.on(d, 'IndexUpdate', this.cb.onIndexUpdate); break; case 'thread': $.on(d, 'ThreadUpdate', this.cb.onThreadRefresh); @@ -17146,20 +21575,22 @@ ThreadWatcher = (function() { if (Conf['Fixed Thread Watcher']) { $.addClass(doc, 'fixed-watcher'); } - if (Conf['Toggleable Thread Watcher']) { + if (!Conf['Persistent Thread Watcher']) { + $.addClass(ThreadWatcher.shortcut, 'disabled'); this.dialog.hidden = true; - Header.addShortcut('watcher', sc, 510); - $.addClass(doc, 'toggleable-watcher'); } + Header.addShortcut('watcher', sc, 510); + ThreadWatcher.initLastModified(); ThreadWatcher.fetchAuto(); - if (g.VIEW === 'index' && Conf['JSON Index'] && Conf['Menu'] && g.BOARD.ID !== 'f') { + $.on(window, 'visibilitychange focus', function() { + return $.queueTask(ThreadWatcher.fetchAuto); + }); + if (Conf['Menu'] && Index.enabled) { Menu.menu.addEntry({ el: $.el('a', { href: 'javascript:;', className: 'has-shortcut-text' - }, { - innerHTML: "Alt+click" - }), + }, {innerHTML: "Alt+click"}), order: 6, open: function(arg) { var thread; @@ -17173,13 +21604,16 @@ ThreadWatcher = (function() { } this.cb = function() { $.event('CloseMenu'); - return ThreadWatcher.toggle(thread); + return ThreadWatcher.toggle(thread, true); }; $.on(this.el, 'click', this.cb); return true; } }); } + if ((ref = g.VIEW) !== 'index' && ref !== 'thread') { + return; + } Callbacks.Post.push({ name: 'Thread Watcher', cb: this.node @@ -17191,65 +21625,78 @@ ThreadWatcher = (function() { }, isWatched: function(thread) { var ref; - return (ref = ThreadWatcher.db) != null ? ref.get({ + return !!((ref = ThreadWatcher.db) != null ? ref.get({ boardID: thread.board.ID, threadID: thread.ID - }) : void 0; + }) : void 0); + }, + isWatchedRaw: function(boardID, threadID) { + var ref; + return !!((ref = ThreadWatcher.db) != null ? ref.get({ + boardID: boardID, + threadID: threadID + }) : void 0); + }, + setToggler: function(toggler, isWatched) { + toggler.classList.toggle('watched', isWatched); + return toggler.title = (isWatched ? 'Unwatch' : 'Watch') + " Thread"; }, node: function() { - var toggler; + var boardID, data, siteID, threadID, toggler; if (this.isReply) { return; } if (this.isClone) { - toggler = $('.watch-thread-link', this.nodes.post); + toggler = $('.watch-thread-link', this.nodes.info); } else { toggler = $.el('a', { href: 'javascript:;', className: 'watch-thread-link' }); - $.before($('input', this.nodes.post), toggler); + $.before($('input', this.nodes.info), toggler); + } + siteID = g.SITE.ID; + boardID = this.board.ID; + threadID = this.thread.ID; + data = ThreadWatcher.db.get({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }); + ThreadWatcher.setToggler(toggler, !!data); + $.on(toggler, 'click', ThreadWatcher.cb.toggle); + if (data && (data.excerpt == null)) { + return $.queueTask((function(_this) { + return function() { + return ThreadWatcher.update(siteID, boardID, threadID, { + excerpt: Get.threadExcerpt(_this.thread) + }); + }; + })(this)); } - return $.on(toggler, 'click', ThreadWatcher.cb.toggle); }, catalogNode: function() { if (ThreadWatcher.isWatched(this.thread)) { $.addClass(this.nodes.root, 'watched'); } - $.on(this.nodes.thumb.parentNode, 'click', (function(_this) { + return $.on(this.nodes.root, 'mousedown click', (function(_this) { return function(e) { if (!(e.button === 0 && e.altKey)) { return; } - ThreadWatcher.toggle(_this.thread); + if (e.type === 'click') { + ThreadWatcher.toggle(_this.thread, true); + } return e.preventDefault(); }; })(this)); - return $.on(this.nodes.thumb.parentNode, 'mousedown', function(e) { - if (e.button === 0 && e.altKey) { - return e.preventDefault(); - } - }); }, - ready: function() { - $.off(d, '4chanXInitFinished', ThreadWatcher.ready); + addDialog: function() { if (!Main.isThisPageLegit()) { return; } - ThreadWatcher.refresh(); - $.add(d.body, ThreadWatcher.dialog); - if (!Conf['Auto Watch']) { - return; - } - return $.get('AutoWatch', 0, function(arg) { - var AutoWatch, thread; - AutoWatch = arg.AutoWatch; - if (!(thread = g.BOARD.threads[AutoWatch])) { - return; - } - ThreadWatcher.add(thread); - return $["delete"]('AutoWatch'); - }); + ThreadWatcher.build(); + return $.prepend(d.body, ThreadWatcher.dialog); }, toggleWatcher: function() { $.toggleClass(ThreadWatcher.shortcut, 'disabled'); @@ -17257,101 +21704,153 @@ ThreadWatcher = (function() { }, cb: { openAll: function() { - var a, i, len, ref; + var a, j, len1, ref; if ($.hasClass(this, 'disabled')) { return; } - ref = $$('a[title]', ThreadWatcher.list); - for (i = 0, len = ref.length; i < len; i++) { - a = ref[i]; + ref = $$('a.watcher-link', ThreadWatcher.list); + for (j = 0, len1 = ref.length; j < len1; j++) { + a = ref[j]; + $.open(a.href); + } + return $.event('CloseMenu'); + }, + openUnread: function() { + var a, j, len1, ref; + if ($.hasClass(this, 'disabled')) { + return; + } + ref = $$('.replies-unread > a.watcher-link', ThreadWatcher.list); + for (j = 0, len1 = ref.length; j < len1; j++) { + a = ref[j]; + $.open(a.href); + } + return $.event('CloseMenu'); + }, + openDeads: function() { + var a, j, len1, ref; + if ($.hasClass(this, 'disabled')) { + return; + } + ref = $$('.dead-thread > a.watcher-link', ThreadWatcher.list); + for (j = 0, len1 = ref.length; j < len1; j++) { + a = ref[j]; $.open(a.href); } return $.event('CloseMenu'); }, + clear: function() { + var boardID, j, len1, ref, ref1, siteID, threadID; + if (!confirm("Delete ALL threads from watcher?")) { + return; + } + ref = ThreadWatcher.getAll(); + for (j = 0, len1 = ref.length; j < len1; j++) { + ref1 = ref[j], siteID = ref1.siteID, boardID = ref1.boardID, threadID = ref1.threadID; + ThreadWatcher.db["delete"]({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }); + } + ThreadWatcher.refresh(true); + return $.event('CloseMenu'); + }, pruneDeads: function() { - var boardID, data, i, len, ref, ref1, threadID; + var boardID, data, j, len1, ref, ref1, siteID, threadID; if ($.hasClass(this, 'disabled')) { return; } - ThreadWatcher.db.forceSync(); ref = ThreadWatcher.getAll(); - for (i = 0, len = ref.length; i < len; i++) { - ref1 = ref[i], boardID = ref1.boardID, threadID = ref1.threadID, data = ref1.data; - if (!data.isDead) { - continue; + for (j = 0, len1 = ref.length; j < len1; j++) { + ref1 = ref[j], siteID = ref1.siteID, boardID = ref1.boardID, threadID = ref1.threadID, data = ref1.data; + if (data.isDead) { + ThreadWatcher.db["delete"]({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }); + } + } + ThreadWatcher.refresh(true); + return $.event('CloseMenu'); + }, + dismiss: function() { + var boardID, data, j, len1, ref, ref1, siteID, threadID; + ref = ThreadWatcher.getAll(); + for (j = 0, len1 = ref.length; j < len1; j++) { + ref1 = ref[j], siteID = ref1.siteID, boardID = ref1.boardID, threadID = ref1.threadID, data = ref1.data; + if (data.quotingYou) { + ThreadWatcher.update(siteID, boardID, threadID, { + dismiss: data.quotingYou || 0 + }); } - delete ThreadWatcher.db.data.boards[boardID][threadID]; - ThreadWatcher.db.deleteIfEmpty({ - boardID: boardID - }); } - ThreadWatcher.db.save(); - ThreadWatcher.refresh(); return $.event('CloseMenu'); }, toggle: function() { var thread; thread = Get.postFromNode(this).thread; - Index.followedThreadID = thread.ID; - ThreadWatcher.toggle(thread); - return delete Index.followedThreadID; + return ThreadWatcher.toggle(thread, true); }, rm: function() { - var boardID, ref, threadID; + var boardID, ref, siteID, threadID; + siteID = this.parentNode.dataset.siteID; ref = this.parentNode.dataset.fullID.split('.'), boardID = ref[0], threadID = ref[1]; - return ThreadWatcher.rm(boardID, +threadID); + return ThreadWatcher.rm(siteID, boardID, +threadID, void 0, true); }, post: function(e) { - var boardID, postID, ref, threadID; + var boardID, cb, postID, ref, threadID; ref = e.detail, boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID; + cb = PostRedirect.delay(); if (postID === threadID) { if (Conf['Auto Watch']) { - return $.set('AutoWatch', threadID); + return ThreadWatcher.addRaw(boardID, threadID, {}, cb, true); } } else if (Conf['Auto Watch Reply']) { - return ThreadWatcher.add(g.threads[boardID + '.' + threadID]); + return ThreadWatcher.add(g.threads.get(boardID + '.' + threadID) || new Thread(threadID, g.boards[boardID] || new Board(boardID)), cb, true); } }, - onIndexRefresh: function() { - var boardID, data, db, ref, threadID; + onIndexUpdate: function(e) { + var boardID, data, db, nKilled, ref, ref1, siteID, threadID; db = ThreadWatcher.db; + siteID = g.SITE.ID; boardID = g.BOARD.ID; - db.forceSync(); - ref = db.data.boards[boardID]; + nKilled = 0; + ref = db.data[siteID].boards[boardID]; for (threadID in ref) { data = ref[threadID]; - if (!(data != null ? data.isDead : void 0) && !(threadID in g.BOARD.threads)) { - if (Conf['Auto Prune'] || !(data && typeof data === 'object')) { - db["delete"]({ - boardID: boardID, - threadID: threadID - }); - } else { - if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) { - ThreadWatcher.fetchStatus({ - boardID: boardID, - threadID: threadID, - data: data - }); - } - data.isDead = true; - db.set({ - boardID: boardID, - threadID: threadID, - val: data - }); - } + if (!(!(data != null ? data.isDead : void 0) && (ref1 = boardID + "." + threadID, indexOf.call(e.detail.threads, ref1) < 0))) { + continue; + } + if (!e.detail.threads.some(function(fullID) { + return +fullID.split('.')[1] > threadID; + })) { + continue; + } + if (Conf['Auto Prune'] || !(data && typeof data === 'object')) { + db["delete"]({ + boardID: boardID, + threadID: threadID + }); + nKilled++; + } else { + ThreadWatcher.fetchStatus({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + data: data + }); } } - return ThreadWatcher.refresh(); + if (nKilled) { + return ThreadWatcher.refresh(); + } }, onThreadRefresh: function(e) { var thread; - thread = g.threads[e.detail.threadID]; - if (!(e.detail[404] && ThreadWatcher.db.get({ - boardID: thread.board.ID, - threadID: thread.ID - }))) { + thread = g.threads.get(e.detail.threadID); + if (!(e.detail[404] && ThreadWatcher.isWatched(thread))) { return; } return ThreadWatcher.add(thread); @@ -17359,6 +21858,38 @@ ThreadWatcher = (function() { }, requests: [], fetched: 0, + fetch: function(url, arg, args, cb) { + var ajax, force, onloadend, ref, req, siteID; + siteID = arg.siteID, force = arg.force; + if (ThreadWatcher.requests.length === 0) { + ThreadWatcher.status.textContent = '...'; + $.addClass(ThreadWatcher.refreshButton, 'fa-spin'); + } + onloadend = function() { + if (this.finished) { + return; + } + this.finished = true; + ThreadWatcher.fetched++; + if (ThreadWatcher.fetched === ThreadWatcher.requests.length) { + ThreadWatcher.clearRequests(); + } else { + ThreadWatcher.status.textContent = (Math.round(ThreadWatcher.fetched / ThreadWatcher.requests.length * 100)) + "%"; + } + return cb.apply(this, args); + }; + ajax = siteID === g.SITE.ID ? $.ajax : CrossOrigin.ajax; + if (force) { + if ((ref = $.lastModified.ThreadWatcher) != null) { + delete ref[url]; + } + } + req = $.whenModified(url, 'ThreadWatcher', onloadend, { + timeout: $.MINUTE, + ajax: ajax + }); + return ThreadWatcher.requests.push(req); + }, clearRequests: function() { ThreadWatcher.requests = []; ThreadWatcher.fetched = 0; @@ -17366,196 +21897,402 @@ ThreadWatcher = (function() { return $.rmClass(ThreadWatcher.refreshButton, 'fa-spin'); }, abort: function() { - var i, len, ref, req; + var j, len1, ref, req; + delete ThreadWatcher.syncing; ref = ThreadWatcher.requests; - for (i = 0, len = ref.length; i < len; i++) { - req = ref[i]; - if (req.readyState !== 4) { - req.abort(); + for (j = 0, len1 = ref.length; j < len1; j++) { + req = ref[j]; + if (!(!req.finished)) { + continue; } + req.finished = true; + req.abort(); } return ThreadWatcher.clearRequests(); }, + initLastModified: function() { + var base, boardID, boards, data, date, lm, ref, ref1, siteID, url; + lm = ((base = $.lastModified)['ThreadWatcher'] || (base['ThreadWatcher'] = $.dict())); + ref = ThreadWatcher.dbLM.data; + for (siteID in ref) { + boards = ref[siteID]; + ref1 = boards.boards; + for (boardID in ref1) { + data = ref1[boardID]; + if (ThreadWatcher.db.get({ + siteID: siteID, + boardID: boardID + })) { + for (url in data) { + date = data[url]; + lm[url] = date; + } + } else { + ThreadWatcher.dbLM["delete"]({ + siteID: siteID, + boardID: boardID + }); + } + } + } + }, fetchAuto: function() { - var db, interval, now; + var db, interval, now, ref; clearTimeout(ThreadWatcher.timeout); if (!Conf['Auto Update Thread Watcher']) { return; } db = ThreadWatcher.db; - interval = ThreadWatcher.unreadEnabled && Conf['Show Unread Count'] ? 5 * $.MINUTE : 2 * $.HOUR; + interval = Conf['Show Page'] || (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) ? 5 * $.MINUTE : 2 * $.HOUR; now = Date.now(); - if (now >= (db.data.lastChecked || 0) + interval) { - db.data.lastChecked = now; - ThreadWatcher.fetchAllStatus(); - db.save(); + if (!((now - interval < (ref = db.data.lastChecked || 0) && ref <= now) || d.hidden || !d.hasFocus())) { + ThreadWatcher.fetchAllStatus(interval); } return ThreadWatcher.timeout = setTimeout(ThreadWatcher.fetchAuto, interval); }, buttonFetchAll: function() { - if (ThreadWatcher.requests.length) { + if (ThreadWatcher.syncing || ThreadWatcher.requests.length) { return ThreadWatcher.abort(); } else { return ThreadWatcher.fetchAllStatus(); } }, - fetchAllStatus: function() { - var i, len, ref, thread, threads; - ThreadWatcher.db.forceSync(); - ThreadWatcher.unreaddb.forceSync(); - if ((ref = QuoteYou.db) != null) { - ref.forceSync(); + fetchAllStatus: function(interval) { + var dbi, dbs, j, len1, n, results; + if (interval == null) { + interval = 0; + } + ThreadWatcher.status.textContent = '...'; + $.addClass(ThreadWatcher.refreshButton, 'fa-spin'); + ThreadWatcher.syncing = true; + dbs = [ThreadWatcher.db, ThreadWatcher.unreaddb, QuoteYou.db].filter(function(x) { + return x; + }); + n = 0; + results = []; + for (j = 0, len1 = dbs.length; j < len1; j++) { + dbi = dbs[j]; + results.push(dbi.forceSync(function() { + var board, boards, db, deep, k, len2, now, ref, ref1; + if ((++n) === dbs.length) { + if (!ThreadWatcher.syncing) { + return; + } + delete ThreadWatcher.syncing; + if (!((0 <= (ref = Date.now() - (ThreadWatcher.db.data.lastChecked || 0)) && ref < interval))) { + db = ThreadWatcher.db; + now = Date.now(); + deep = !((now - 2 * $.HOUR < (ref1 = db.data.lastChecked2 || 0) && ref1 <= now)); + boards = ThreadWatcher.getAll(true); + for (k = 0, len2 = boards.length; k < len2; k++) { + board = boards[k]; + ThreadWatcher.fetchBoard(board, deep); + } + db.setLastChecked(); + if (deep) { + db.setLastChecked('lastChecked2'); + } + } + if (ThreadWatcher.fetched === ThreadWatcher.requests.length) { + return ThreadWatcher.clearRequests(); + } + } + })); } - if (!(threads = ThreadWatcher.getAll()).length) { + return results; + }, + fetchBoard: function(board, deep) { + var base, boardID, data, force, j, len1, ref, site, siteID, thread, url, urlF; + if (!board.some(function(thread) { + return !thread.data.isDead; + })) { return; } - for (i = 0, len = threads.length; i < len; i++) { - thread = threads[i]; - ThreadWatcher.fetchStatus(thread); + force = false; + for (j = 0, len1 = board.length; j < len1; j++) { + thread = board[j]; + data = thread.data; + if (!data.isDead && data.last !== -1) { + if (Conf['Show Page'] && (data.page == null)) { + force = true; + } + if (data.modified == null) { + force = thread.force = true; + } + } } - }, - fetchStatus: function(thread, force) { - var boardID, data, req, threadID; - boardID = thread.boardID, threadID = thread.threadID, data = thread.data; - if (data.isDead && !force) { + ref = board[0], siteID = ref.siteID, boardID = ref.boardID; + site = g.sites[siteID]; + if (!site) { return; } - if (ThreadWatcher.requests.length === 0) { - ThreadWatcher.status.textContent = '...'; - $.addClass(ThreadWatcher.refreshButton, 'fa-spin'); + urlF = deep && site.threadModTimeIgnoresSage ? 'catalogJSON' : 'threadsListJSON'; + url = typeof (base = site.urls)[urlF] === "function" ? base[urlF]({ + siteID: siteID, + boardID: boardID + }) : void 0; + if (!url) { + return; } - req = $.ajax("//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", { - onloadend: function() { - return ThreadWatcher.parseStatus.call(this, thread); - }, - timeout: $.MINUTE - }, { - whenModified: force ? false : 'ThreadWatcher' + return ThreadWatcher.fetch(url, { + siteID: siteID, + force: force + }, [board, url], ThreadWatcher.parseBoard); + }, + parseBoard: function(board, url) { + var base, boardID, data, i, index, item, j, k, l, lastPage, len1, len2, len3, len4, lmDate, m, modified, nThreads, oldest, page, pageLength, ref, ref1, ref2, ref3, ref4, replies, siteID, thread, threadID, threads; + if (this.status !== 200) { + return; + } + ref = board[0], siteID = ref.siteID, boardID = ref.boardID; + lmDate = this.getResponseHeader('Last-Modified'); + ThreadWatcher.dbLM.extend({ + siteID: siteID, + boardID: boardID, + val: $.item(url, lmDate) }); - return ThreadWatcher.requests.push(req); + threads = $.dict(); + pageLength = 0; + nThreads = 0; + oldest = null; + try { + pageLength = ((ref1 = this.response[0]) != null ? ref1.threads.length : void 0) || 0; + ref2 = this.response; + for (i = j = 0, len1 = ref2.length; j < len1; i = ++j) { + page = ref2[i]; + ref3 = page.threads; + for (k = 0, len2 = ref3.length; k < len2; k++) { + item = ref3[k]; + threads[item.no] = { + page: i + 1, + index: nThreads, + modified: item.last_modified, + replies: item.replies + }; + nThreads++; + if ((oldest == null) || item.no < oldest) { + oldest = item.no; + } + } + } + } catch (error) { + for (l = 0, len3 = board.length; l < len3; l++) { + thread = board[l]; + ThreadWatcher.fetchStatus(thread); + } + } + for (m = 0, len4 = board.length; m < len4; m++) { + thread = board[m]; + threadID = thread.threadID, data = thread.data; + if (threads[threadID]) { + ref4 = threads[threadID], page = ref4.page, index = ref4.index, modified = ref4.modified, replies = ref4.replies; + if (Conf['Show Page']) { + lastPage = (typeof (base = g.sites[siteID]).isPrunedByAge === "function" ? base.isPrunedByAge({ + siteID: siteID, + boardID: boardID + }) : void 0) ? threadID === oldest : index >= nThreads - pageLength; + ThreadWatcher.update(siteID, boardID, threadID, { + page: page, + lastPage: lastPage + }); + } + if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) { + if (modified !== data.modified || ((replies != null) && replies !== data.replies)) { + (thread.newData || (thread.newData = {})).modified = modified; + ThreadWatcher.fetchStatus(thread); + } + } + } else { + ThreadWatcher.fetchStatus(thread); + } + } }, - parseStatus: function(arg) { - var boardID, data, i, isDead, lastReadPost, len, match, postObj, quotesYou, quotingYou, ref, ref1, regexp, threadID, unread; - boardID = arg.boardID, threadID = arg.threadID, data = arg.data; - ThreadWatcher.fetched++; - if (ThreadWatcher.fetched === ThreadWatcher.requests.length) { - ThreadWatcher.clearRequests(); - } else { - ThreadWatcher.status.textContent = (Math.round(ThreadWatcher.fetched / ThreadWatcher.requests.length * 100)) + "%"; + fetchStatus: function(thread) { + var base, boardID, data, force, ref, siteID, threadID, url; + siteID = thread.siteID, boardID = thread.boardID, threadID = thread.threadID, data = thread.data, force = thread.force; + url = (ref = g.sites[siteID]) != null ? typeof (base = ref.urls).threadJSON === "function" ? base.threadJSON({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }) : void 0 : void 0; + if (!url) { + return; + } + if (data.isDead && !force) { + return; + } + if (data.last === -1) { + return; } + return ThreadWatcher.fetch(url, { + siteID: siteID, + force: force + }, [thread], ThreadWatcher.parseStatus); + }, + parseStatus: function(thread, isArchiveURL) { + var archiveURL, base, boardID, data, force, isArchived, isDead, j, last, lastReadPost, len1, match, newData, postObj, quotesYou, quotingYou, ref, ref1, ref2, ref3, regexp, replies, site, siteID, threadID, unread, youOP; + siteID = thread.siteID, boardID = thread.boardID, threadID = thread.threadID, data = thread.data, newData = thread.newData, force = thread.force; + site = g.sites[siteID]; if (this.status === 200 && this.response) { - isDead = !!this.response.posts[0].archived; + last = this.response.posts[this.response.posts.length - 1].no; + replies = this.response.posts.length - 1; + isDead = isArchived = !!(this.response.posts[0].archived || isArchiveURL); if (isDead && Conf['Auto Prune']) { - ThreadWatcher.db["delete"]({ - boardID: boardID, - threadID: threadID - }); - ThreadWatcher.refresh(); + ThreadWatcher.rm(siteID, boardID, threadID); + return; + } + if (last === data.last && isDead === data.isDead && isArchived === data.isArchived) { return; } lastReadPost = ThreadWatcher.unreaddb.get({ + siteID: siteID, boardID: boardID, threadID: threadID, defaultValue: 0 }); - unread = quotingYou = 0; - ref = this.response.posts; - for (i = 0, len = ref.length; i < len; i++) { - postObj = ref[i]; - if (!(postObj.no > lastReadPost)) { + unread = data.unread || 0; + quotingYou = data.quotingYou || 0; + youOP = !!((ref = QuoteYou.db) != null ? ref.get({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + postID: threadID + }) : void 0); + ref1 = this.response.posts; + for (j = 0, len1 = ref1.length; j < len1; j++) { + postObj = ref1[j]; + if (!(postObj.no > (data.last || 0) && postObj.no > lastReadPost)) { continue; } - if ((ref1 = QuoteYou.db) != null ? ref1.get({ + if ((ref2 = QuoteYou.db) != null ? ref2.get({ + siteID: siteID, boardID: boardID, threadID: threadID, postID: postObj.no }) : void 0) { continue; } - unread++; - if (!(QuoteYou.db && postObj.com)) { - continue; - } quotesYou = false; - regexp = /]*\bhref="(?:\/([^\/]+)\/thread\/)?(\d+)?(?:#p(\d+))?"/g; - while (match = regexp.exec(postObj.com)) { - if (QuoteYou.db.get({ - boardID: match[1] || boardID, - threadID: match[2] || threadID, - postID: match[3] || match[2] || threadID - })) { - quotesYou = true; - break; + if (!Conf['Require OP Quote Link'] && youOP) { + quotesYou = true; + } else if (QuoteYou.db && postObj.com) { + regexp = site.regexp.quotelinkHTML; + regexp.lastIndex = 0; + while ((match = regexp.exec(postObj.com))) { + if (QuoteYou.db.get({ + siteID: siteID, + boardID: match[1] ? encodeURIComponent(match[1]) : boardID, + threadID: match[2] || threadID, + postID: match[3] || match[2] || threadID + })) { + quotesYou = true; + break; + } } } - if (quotesYou && !Filter.isHidden(Build.parseJSON(postObj, boardID))) { - quotingYou++; + if (!unread || (!quotingYou && quotesYou)) { + if (Filter.isHidden(site.Build.parseJSON(postObj, { + siteID: siteID, + boardID: boardID + }))) { + continue; + } } - } - if (isDead !== data.isDead || unread !== data.unread || quotingYou !== data.quotingYou) { - data.isDead = isDead; - data.unread = unread; - data.quotingYou = quotingYou; - ThreadWatcher.db.set({ - boardID: boardID, - threadID: threadID, - val: data - }); - return ThreadWatcher.refresh(); - } + unread++; + if (quotesYou) { + quotingYou = postObj.no; + } + } + newData || (newData = {}); + $.extend(newData, { + last: last, + replies: replies, + isDead: isDead, + isArchived: isArchived, + unread: unread, + quotingYou: quotingYou + }); + return ThreadWatcher.update(siteID, boardID, threadID, newData); } else if (this.status === 404) { - if (Conf['Auto Prune']) { - ThreadWatcher.db["delete"]({ - boardID: boardID, - threadID: threadID + archiveURL = (ref3 = g.sites[siteID]) != null ? typeof (base = ref3.urls).archivedThreadJSON === "function" ? base.archivedThreadJSON({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }) : void 0 : void 0; + if (!isArchiveURL && archiveURL) { + return ThreadWatcher.fetch(archiveURL, { + siteID: siteID, + force: force + }, [thread, true], ThreadWatcher.parseStatus); + } else if (site.mayLackJSON && (data.last == null)) { + return ThreadWatcher.update(siteID, boardID, threadID, { + last: -1 }); } else { - data.isDead = true; - delete data.unread; - delete data.quotingYou; - ThreadWatcher.db.set({ - boardID: boardID, - threadID: threadID, - val: data + return ThreadWatcher.update(siteID, boardID, threadID, { + isDead: true }); } - return ThreadWatcher.refresh(); } }, - getAll: function() { - var all, boardID, data, ref, threadID, threads; + getAll: function(groupByBoard) { + var all, boardID, boards, cont, data, ref, ref1, siteID, threadID, threads; all = []; - ref = ThreadWatcher.db.data.boards; - for (boardID in ref) { - threads = ref[boardID]; - if (Conf['Current Board'] && boardID !== g.BOARD.ID) { - continue; - } - for (threadID in threads) { - data = threads[threadID]; - if (data && typeof data === 'object') { - all.push({ - boardID: boardID, - threadID: threadID, - data: data - }); + ref = ThreadWatcher.db.data; + for (siteID in ref) { + boards = ref[siteID]; + ref1 = boards.boards; + for (boardID in ref1) { + threads = ref1[boardID]; + if (Conf['Current Board'] && (siteID !== g.SITE.ID || boardID !== g.BOARD.ID)) { + continue; + } + if (groupByBoard) { + all.push((cont = [])); + } + for (threadID in threads) { + data = threads[threadID]; + if (data && typeof data === 'object') { + (groupByBoard ? cont : all).push({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + data: data + }); + } } } } return all; }, - makeLine: function(boardID, threadID, data) { - var count, div, fullID, link, title, x; + makeLine: function(siteID, boardID, threadID, data) { + var count, div, excerpt, fullID, isArchived, link, page, ref, title, x; x = $.el('a', { className: 'fa fa-times', href: 'javascript:;' }); $.on(x, 'click', ThreadWatcher.cb.rm); + excerpt = data.excerpt, isArchived = data.isArchived; + excerpt || (excerpt = "/" + boardID + "/ - No." + threadID); + if (Conf['Show Site Prefix']) { + excerpt = ThreadWatcher.prefixes[siteID] + excerpt; + } link = $.el('a', { - href: "/" + boardID + "/thread/" + threadID, - title: data.excerpt, + href: ((ref = g.sites[siteID]) != null ? ref.urls.thread({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }, isArchived) : void 0) || '', + title: excerpt, className: 'watcher-link' }); + if (Conf['Show Page'] && (data.page != null)) { + page = $.el('span', { + textContent: "[" + data.page + "]", + className: 'watcher-page' + }); + $.add(link, page); + } if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count'] && (data.unread != null)) { count = $.el('span', { textContent: "(" + data.unread + ")", @@ -17564,19 +22301,28 @@ ThreadWatcher = (function() { $.add(link, count); } title = $.el('span', { - textContent: data.excerpt, + textContent: excerpt, className: 'watcher-title' }); $.add(link, title); div = $.el('div'); fullID = boardID + "." + threadID; div.dataset.fullID = fullID; + div.dataset.siteID = siteID; if (g.VIEW === 'thread' && fullID === (g.BOARD + "." + g.THREADID)) { $.addClass(div, 'current'); } if (data.isDead) { $.addClass(div, 'dead-thread'); } + if (Conf['Show Page']) { + if (data.lastPage) { + $.addClass(div, 'last-page'); + } + if (data.page != null) { + div.dataset.page = data.page; + } + } if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) { if (data.unread === 0) { $.addClass(div, 'replies-read'); @@ -17584,75 +22330,122 @@ ThreadWatcher = (function() { if (data.unread) { $.addClass(div, 'replies-unread'); } - if (data.quotingYou) { + if ((data.quotingYou || 0) > (data.dismiss || 0)) { $.addClass(div, 'replies-quoting-you'); } } $.add(div, [x, $.tn(' '), link]); return div; }, - refresh: function() { - var boardID, data, i, j, len, len1, list, nodes, ref, ref1, ref2, refresher, threadID; + setPrefixes: function(threads) { + var conflicts, conflicts2, j, k, len, len1, len2, prefix, prefixes, siteID, siteID2; + prefixes = $.dict(); + for (j = 0, len1 = threads.length; j < len1; j++) { + siteID = threads[j].siteID; + if (siteID in prefixes) { + continue; + } + len = 0; + prefix = ''; + conflicts = Object.keys(prefixes); + while (conflicts.length > 0) { + len++; + prefix = siteID.slice(0, len); + conflicts2 = []; + for (k = 0, len2 = conflicts.length; k < len2; k++) { + siteID2 = conflicts[k]; + if (siteID2.slice(0, len) === prefix) { + conflicts2.push(siteID2); + } else if (prefixes[siteID2].length < len) { + prefixes[siteID2] = siteID2.slice(0, len); + } + } + conflicts = conflicts2; + } + prefixes[siteID] = prefix; + } + return ThreadWatcher.prefixes = prefixes; + }, + build: function() { + var boardID, data, j, len1, list, nodes, ref, siteID, thread, threadID, threads; nodes = []; - ref = ThreadWatcher.getAll(); - for (i = 0, len = ref.length; i < len; i++) { - ref1 = ref[i], boardID = ref1.boardID, threadID = ref1.threadID, data = ref1.data; - nodes.push(ThreadWatcher.makeLine(boardID, threadID, data)); + threads = ThreadWatcher.getAll(); + ThreadWatcher.setPrefixes(threads); + for (j = 0, len1 = threads.length; j < len1; j++) { + ref = threads[j], siteID = ref.siteID, boardID = ref.boardID, threadID = ref.threadID, data = ref.data; + if ((data.excerpt == null) && siteID === g.SITE.ID && (thread = g.threads.get(boardID + "." + threadID)) && thread.OP) { + ThreadWatcher.db.extend({ + boardID: boardID, + threadID: threadID, + val: { + excerpt: Get.threadExcerpt(thread) + } + }); + } + nodes.push(ThreadWatcher.makeLine(siteID, boardID, threadID, data)); } list = ThreadWatcher.list; $.rmAll(list); $.add(list, nodes); + return ThreadWatcher.refreshIcon(); + }, + refresh: function(manual) { + ThreadWatcher.build(); g.threads.forEach(function(thread) { - var helper, j, len1, post, ref2, toggler; - helper = ThreadWatcher.isWatched(thread) ? ['addClass', 'Unwatch'] : ['rmClass', 'Watch']; + var isWatched, j, len1, post, ref, toggler; + isWatched = ThreadWatcher.isWatched(thread); if (thread.OP) { - ref2 = [thread.OP].concat(slice.call(thread.OP.clones)); - for (j = 0, len1 = ref2.length; j < len1; j++) { - post = ref2[j]; - toggler = $('.watch-thread-link', post.nodes.post); - $[helper[0]](toggler, 'watched'); - toggler.title = helper[1] + " Thread"; + ref = [thread.OP].concat(slice.call(thread.OP.clones)); + for (j = 0, len1 = ref.length; j < len1; j++) { + post = ref[j]; + if ((toggler = $('.watch-thread-link', post.nodes.info))) { + ThreadWatcher.setToggler(toggler, isWatched); + } } } if (thread.catalogView) { - return $[helper[0]](thread.catalogView.nodes.root, 'watched'); + return thread.catalogView.nodes.root.classList.toggle('watched', isWatched); } }); - ThreadWatcher.refreshIcon(); - ref2 = ThreadWatcher.menu.refreshers; - for (j = 0, len1 = ref2.length; j < len1; j++) { - refresher = ref2[j]; - refresher(); - } - if (Index.nodes && Conf['Pin Watched Threads']) { - Index.sort(); - return Index.buildIndex(); + if (Conf['Pin Watched Threads']) { + return $.event('SortIndex', { + deferred: !(manual && Conf['Index Mode'] === 'catalog') + }); } }, refreshIcon: function() { - var className, i, len, ref; + var className, j, len1, ref; ref = ['replies-unread', 'replies-quoting-you']; - for (i = 0, len = ref.length; i < len; i++) { - className = ref[i]; + for (j = 0, len1 = ref.length; j < len1; j++) { + className = ref[j]; ThreadWatcher.shortcut.classList.toggle(className, !!$("." + className, ThreadWatcher.dialog)); } }, - update: function(boardID, threadID, newData) { - var data, key, line, n, newLine, ref, val; + update: function(siteID, boardID, threadID, newData) { + var data, j, key, len1, line, n, newLine, ref, ref1, val; if (!(data = (ref = ThreadWatcher.db) != null ? ref.get({ + siteID: siteID, boardID: boardID, threadID: threadID }) : void 0)) { return; } if (newData.isDead && Conf['Auto Prune']) { - ThreadWatcher.db["delete"]({ - boardID: boardID, - threadID: threadID - }); - ThreadWatcher.refresh(); + ThreadWatcher.rm(siteID, boardID, threadID); return; } + if (newData.isDead || newData.last === -1) { + ref1 = ['isArchived', 'page', 'lastPage', 'unread', 'quotingyou']; + for (j = 0, len1 = ref1.length; j < len1; j++) { + key = ref1[j]; + if (!(key in newData)) { + newData[key] = void 0; + } + } + } + if ((newData.last != null) && newData.last < data.last) { + newData.modified = void 0; + } n = 0; for (key in newData) { val = newData[key]; @@ -17663,21 +22456,14 @@ ThreadWatcher = (function() { if (!n) { return; } - ThreadWatcher.db.forceSync(); - if (!(data = ThreadWatcher.db.get({ - boardID: boardID, - threadID: threadID - }))) { - return; - } - $.extend(data, newData); - ThreadWatcher.db.set({ + ThreadWatcher.db.extend({ + siteID: siteID, boardID: boardID, threadID: threadID, - val: data + val: newData }); - if (line = $("#watched-threads > [data-full-i-d='" + boardID + "." + threadID + "']", ThreadWatcher.dialog)) { - newLine = ThreadWatcher.makeLine(boardID, threadID, data); + if ((line = $("#watched-threads > [data-site-i-d='" + siteID + "'][data-full-i-d='" + boardID + "." + threadID + "']", ThreadWatcher.dialog))) { + newLine = ThreadWatcher.makeLine(siteID, boardID, threadID, data); $.replace(line, newLine); return ThreadWatcher.refreshIcon(); } else { @@ -17699,34 +22485,40 @@ ThreadWatcher = (function() { }); return cb(); } - if (data.isDead && !((data.unread != null) || (data.quotingYou != null))) { + if (data.isDead && !((data.isArchived != null) || (data.page != null) || (data.lastPage != null) || (data.unread != null) || (data.quotingYou != null))) { return cb(); } - data.isDead = true; - delete data.unread; - delete data.quotingYou; - return ThreadWatcher.db.set({ + return ThreadWatcher.db.extend({ boardID: boardID, threadID: threadID, - val: data + val: { + isDead: true, + isArchived: void 0, + page: void 0, + lastPage: void 0, + unread: void 0, + quotingYou: void 0 + } }, cb); }, - toggle: function(thread) { - var boardID, threadID; + toggle: function(thread, manual) { + var boardID, siteID, threadID; + siteID = g.SITE.ID; boardID = thread.board.ID; threadID = thread.ID; if (ThreadWatcher.db.get({ boardID: boardID, threadID: threadID })) { - return ThreadWatcher.rm(boardID, threadID); + return ThreadWatcher.rm(siteID, boardID, threadID, void 0, manual); } else { - return ThreadWatcher.add(thread); + return ThreadWatcher.add(thread, void 0, manual); } }, - add: function(thread) { - var boardID, data, threadID; + add: function(thread, cb, manual) { + var boardID, data, siteID, threadID; data = {}; + siteID = g.SITE.ID; boardID = thread.board.ID; threadID = thread.ID; if (thread.isDead) { @@ -17734,35 +22526,54 @@ ThreadWatcher = (function() { boardID: boardID, threadID: threadID })) { - ThreadWatcher.rm(boardID, threadID); + ThreadWatcher.rm(siteID, boardID, threadID, cb); return; } data.isDead = true; } - data.excerpt = Get.threadExcerpt(thread); - ThreadWatcher.db.set({ + if (thread.OP) { + data.excerpt = Get.threadExcerpt(thread); + } + return ThreadWatcher.addRaw(boardID, threadID, data, cb, manual); + }, + addRaw: function(boardID, threadID, data, cb, manual) { + var oldData, thread; + oldData = ThreadWatcher.db.get({ boardID: boardID, threadID: threadID, - val: data + defaultValue: $.dict() }); - ThreadWatcher.refresh(); - if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) { - return ThreadWatcher.fetchStatus({ - boardID: boardID, - threadID: threadID, - data: data - }, true); + delete oldData.last; + delete oldData.modified; + $.extend(oldData, data); + ThreadWatcher.db.set({ + boardID: boardID, + threadID: threadID, + val: oldData + }, cb); + ThreadWatcher.refresh(manual); + thread = { + siteID: g.SITE.ID, + boardID: boardID, + threadID: threadID, + data: data, + force: true + }; + if (Conf['Show Page'] && !data.isDead) { + return ThreadWatcher.fetchBoard([thread]); + } else if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) { + return ThreadWatcher.fetchStatus(thread); } }, - rm: function(boardID, threadID) { + rm: function(siteID, boardID, threadID, cb, manual) { ThreadWatcher.db["delete"]({ + siteID: siteID, boardID: boardID, threadID: threadID - }); - return ThreadWatcher.refresh(); + }, cb); + return ThreadWatcher.refresh(manual); }, menu: { - refreshers: [], init: function() { var menu; if (!Conf['Thread Watcher']) { @@ -17772,7 +22583,6 @@ ThreadWatcher = (function() { $.on($('.menu-button', ThreadWatcher.dialog), 'click', function(e) { return menu.toggle(e, this, ThreadWatcher); }); - this.addHeaderMenuEntry(); return this.addMenuEntries(); }, addHeaderMenuEntry: function() { @@ -17785,73 +22595,97 @@ ThreadWatcher = (function() { }); Header.menu.addEntry({ el: entryEl, - order: 60 - }); - $.on(entryEl, 'click', function() { - return ThreadWatcher.toggle(g.threads[g.BOARD + "." + g.THREADID]); + order: 60, + open: function() { + var addClass, ref, rmClass, text; + ref = !!ThreadWatcher.db.get({ + boardID: g.BOARD.ID, + threadID: g.THREADID + }) ? ['unwatch-thread', 'watch-thread', 'Unwatch thread'] : ['watch-thread', 'unwatch-thread', 'Watch thread'], addClass = ref[0], rmClass = ref[1], text = ref[2]; + $.addClass(entryEl, addClass); + $.rmClass(entryEl, rmClass); + entryEl.textContent = text; + return true; + } }); - return this.refreshers.push(function() { - var addClass, ref, rmClass, text; - ref = $('.current', ThreadWatcher.list) ? ['unwatch-thread', 'watch-thread', 'Unwatch thread'] : ['watch-thread', 'unwatch-thread', 'Watch thread'], addClass = ref[0], rmClass = ref[1], text = ref[2]; - $.addClass(entryEl, addClass); - $.rmClass(entryEl, rmClass); - return entryEl.textContent = text; + return $.on(entryEl, 'click', function() { + return ThreadWatcher.toggle(g.threads.get(g.BOARD + "." + g.THREADID), true); }); }, addMenuEntries: function() { - var cb, conf, entries, entry, i, len, name, ref, ref1, refresh, subEntries; + var cb, conf, entries, entry, j, len1, name, open, ref, ref1, text, title; entries = []; entries.push({ + text: 'Open all threads', cb: ThreadWatcher.cb.openAll, - entry: { - el: $.el('a', { - textContent: 'Open all threads' - }) - }, - refresh: function() { - return (ThreadWatcher.list.firstElementChild ? $.rmClass : $.addClass)(this.el, 'disabled'); + open: function() { + this.el.classList.toggle('disabled', !ThreadWatcher.list.firstElementChild); + return true; } }); entries.push({ - cb: ThreadWatcher.cb.pruneDeads, - entry: { - el: $.el('a', { - textContent: 'Prune dead threads' - }) - }, - refresh: function() { - return ($('.dead-thread', ThreadWatcher.list) ? $.rmClass : $.addClass)(this.el, 'disabled'); + text: 'Open unread threads', + cb: ThreadWatcher.cb.openUnread, + open: function() { + this.el.classList.toggle('disabled', !$('.replies-unread', ThreadWatcher.list)); + return true; + } + }); + entries.push({ + text: 'Open dead threads', + cb: ThreadWatcher.cb.openDeads, + open: function() { + this.el.classList.toggle('disabled', !$('.dead-thread', ThreadWatcher.list)); + return true; } }); - subEntries = []; - ref = Config.threadWatcher; - for (name in ref) { - conf = ref[name]; - subEntries.push(this.createSubEntry(name, conf[1])); - } entries.push({ - entry: { - el: $.el('span', { - textContent: 'Settings' - }), - subEntries: subEntries + text: 'Clear all threads', + cb: ThreadWatcher.cb.clear, + open: function() { + this.el.classList.toggle('disabled', !ThreadWatcher.list.firstElementChild); + return true; } }); - for (i = 0, len = entries.length; i < len; i++) { - ref1 = entries[i], entry = ref1.entry, cb = ref1.cb, refresh = ref1.refresh; - if (entry.el.nodeName === 'A') { - entry.el.href = 'javascript:;'; + entries.push({ + text: 'Prune dead threads', + cb: ThreadWatcher.cb.pruneDeads, + open: function() { + this.el.classList.toggle('disabled', !$('.dead-thread', ThreadWatcher.list)); + return true; } - if (cb) { - $.on(entry.el, 'click', cb); + }); + entries.push({ + text: 'Dismiss posts quoting you', + title: 'Unhighlight the thread watcher icon and threads until there are new replies quoting you.', + cb: ThreadWatcher.cb.dismiss, + open: function() { + this.el.classList.toggle('disabled', !$.hasClass(ThreadWatcher.shortcut, 'replies-quoting-you')); + return true; } - if (refresh) { - this.refreshers.push(refresh.bind(entry)); + }); + for (j = 0, len1 = entries.length; j < len1; j++) { + ref = entries[j], text = ref.text, title = ref.title, cb = ref.cb, open = ref.open; + entry = { + el: $.el('a', { + textContent: text, + href: 'javascript:;' + }) + }; + if (title) { + entry.el.title = title; } + $.on(entry.el, 'click', cb); + entry.open = open.bind(entry); this.menu.addEntry(entry); } + ref1 = Config.threadWatcher; + for (name in ref1) { + conf = ref1[name]; + this.addCheckbox(name, conf[1]); + } }, - createSubEntry: function(name, desc) { + addCheckbox: function(name, desc) { var entry, input; entry = { type: 'thread watcher', @@ -17865,13 +22699,15 @@ ThreadWatcher = (function() { entry.el.title += '\n[Remember Last Read Post is disabled.]'; } $.on(input, 'change', $.cb.checked); - if (name === 'Current Board' || name === 'Show Unread Count') { - $.on(input, 'change', ThreadWatcher.refresh); - } - if (name === 'Show Unread Count' || name === 'Auto Update Thread Watcher') { + $.on(input, 'change', function() { + if (name === 'Current Board' || name === 'Show Page' || name === 'Show Unread Count' || name === 'Show Site Prefix') { + return ThreadWatcher.refresh(); + } + }); + if (name === 'Show Page' || name === 'Show Unread Count' || name === 'Auto Update Thread Watcher') { $.on(input, 'change', ThreadWatcher.fetchAuto); } - return entry; + return this.menu.addEntry(entry); } } }; @@ -17895,7 +22731,8 @@ Unread = (function() { this.db = new DataBoard('lastReadPosts', this.sync); } this.hr = $.el('hr', { - id: 'unread-line' + id: 'unread-line', + className: 'unread-line' }); this.posts = new Set(); this.postsQuotingYou = new Set(); @@ -17911,7 +22748,7 @@ Unread = (function() { }); }, node: function() { - var ID, j, len, ref, ref1; + var ID, j, len, ref, ref1, resetLink; Unread.thread = this; Unread.title = d.title; Unread.lastReadPost = ((ref = Unread.db) != null ? ref.get({ @@ -17927,7 +22764,22 @@ Unread = (function() { } } $.one(d, '4chanXInitFinished', Unread.ready); - return $.on(d, 'ThreadUpdate', Unread.onUpdate); + $.on(d, 'PostsInserted', Unread.onUpdate); + $.on(d, 'ThreadUpdate', function(e) { + if (e.detail[404]) { + return Unread.update(); + } + }); + resetLink = $.el('a', { + href: 'javascript:;', + className: 'unread-reset', + textContent: 'Mark all unread' + }); + $.on(resetLink, 'click', Unread.reset); + return Header.menu.addEntry({ + el: resetLink, + order: 70 + }); }, ready: function() { if (Conf['Remember Last Read Post'] && Conf['Scroll to Last Read Post']) { @@ -17949,22 +22801,46 @@ Unread = (function() { } }, scroll: function() { - var hash, position, ref, root; + var bottom, hash, position; if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) { return; } - ReplyPruning.showIfHidden((ref = Unread.position) != null ? ref.data.nodes.root.id : void 0); position = Unread.positionPrev(); while (position) { - root = position.data.nodes.root; - if (!root.getBoundingClientRect().height) { + bottom = position.data.nodes.bottom; + if (!bottom.getBoundingClientRect().height) { position = position.prev; } else { - Header.scrollToIfNeeded(root, true); + Header.scrollToIfNeeded(bottom, true); break; } } }, + reset: function() { + if (Unread.lastReadPost == null) { + return; + } + Unread.posts = new Set(); + Unread.postsQuotingYou = new Set(); + Unread.order = new RandomAccessList(); + Unread.position = null; + Unread.lastReadPost = 0; + Unread.readCount = 0; + Unread.thread.posts.forEach(function(post) { + return Unread.addPost.call(post); + }); + $.forceSync('Remember Last Read Post'); + if (Conf['Remember Last Read Post'] && (!Unread.thread.isDead || Unread.thread.isArchived)) { + Unread.db.set({ + boardID: Unread.thread.board.ID, + threadID: Unread.thread.ID, + val: 0 + }); + } + Unread.updatePosition(); + Unread.setLine(); + return Unread.update(); + }, sync: function() { var ID, i, j, lastReadPost, postIDs, ref, ref1; if (Unread.lastReadPost == null) { @@ -17982,7 +22858,7 @@ Unread = (function() { postIDs = Unread.thread.posts.keys; for (i = j = ref = Unread.readCount, ref1 = postIDs.length; j < ref1; i = j += 1) { ID = +postIDs[i]; - if (!Unread.thread.posts[ID].isFetchedQuote) { + if (!Unread.thread.posts.get(ID).isFetchedQuote) { if (ID > Unread.lastReadPost) { break; } @@ -17996,19 +22872,14 @@ Unread = (function() { return Unread.update(); }, addPost: function() { - var ref; if (this.isFetchedQuote || this.isClone) { return; - } - Unread.order.push(this); - if (this.ID <= Unread.lastReadPost || this.isHidden || ((ref = QuoteYou.db) != null ? ref.get({ - boardID: this.board.ID, - threadID: this.thread.ID, - postID: this.ID - }) : void 0)) { + } + Unread.order.push(this); + if (this.ID <= Unread.lastReadPost || this.isHidden || QuoteYou.isYou(this)) { return; } - Unread.posts.add(this.ID); + Unread.posts.add((Unread.posts.last = this.ID)); Unread.addPostQuotingYou(this); return Unread.position != null ? Unread.position : Unread.position = Unread.order[this.ID]; }, @@ -18020,22 +22891,25 @@ Unread = (function() { if (!((ref1 = QuoteYou.db) != null ? ref1.get(Get.postDataFromLink(quotelink)) : void 0)) { continue; } - Unread.postsQuotingYou.add(post.ID); + Unread.postsQuotingYou.add((Unread.postsQuotingYou.last = post.ID)); Unread.openNotification(post); return; } }, - openNotification: function(post) { + openNotification: function(post, predicate) { var notif; + if (predicate == null) { + predicate = ' replied to you'; + } if (!Header.areNotificationsEnabled) { return; } - notif = new Notification(post.info.nameBlock + " replied to you", { - body: post.info.commentDisplay, + notif = new Notification("" + post.info.nameBlock + predicate, { + body: post.commentDisplay(), icon: Favicon.logo }); notif.onclick = function() { - Header.scrollToIfNeeded(post.nodes.root, true); + Header.scrollToIfNeeded(post.nodes.bottom, true); return window.focus(); }; return notif.onshow = function() { @@ -18044,12 +22918,12 @@ Unread = (function() { }, 7 * $.SECOND); }; }, - onUpdate: function(e) { - if (!e.detail[404]) { + onUpdate: function() { + return $.queueTask(function() { Unread.setLine(); Unread.read(); - } - return Unread.update(); + return Unread.update(); + }); }, readSinglePost: function(post) { var ID; @@ -18064,7 +22938,7 @@ Unread = (function() { return Unread.update(); }, read: $.debounce(100, function(e) { - var ID, count, data, ref, ref1, root; + var ID, bottom, count, data, ref; if (!Unread.posts.size && Unread.readCount !== Unread.thread.posts.keys.length) { Unread.saveLastReadPost(); } @@ -18074,20 +22948,13 @@ Unread = (function() { count = 0; while (Unread.position) { ref = Unread.position, ID = ref.ID, data = ref.data; - root = data.nodes.root; - if (!(!root.getBoundingClientRect().height || Header.getBottomOf(root) > -1)) { + bottom = data.nodes.bottom; + if (!(!bottom.getBoundingClientRect().height || Header.getBottomOf(bottom) > -1)) { break; } count++; Unread.posts["delete"](ID); Unread.postsQuotingYou["delete"](ID); - if ((ref1 = QuoteYou.db) != null ? ref1.get({ - boardID: data.board.ID, - threadID: data.thread.ID, - postID: ID - }) : void 0) { - QuoteYou.lastRead = root; - } Unread.position = Unread.position.next; } if (!count) { @@ -18113,7 +22980,7 @@ Unread = (function() { postIDs = Unread.thread.posts.keys; for (i = j = ref = Unread.readCount, ref1 = postIDs.length; j < ref1; i = j += 1) { ID = +postIDs[i]; - if (!Unread.thread.posts[ID].isFetchedQuote) { + if (!Unread.thread.posts.get(ID).isFetchedQuote) { if (Unread.posts.has(ID)) { break; } @@ -18124,7 +22991,6 @@ Unread = (function() { if (Unread.thread.isDead && !Unread.thread.isArchived) { return; } - Unread.db.forceSync(); return Unread.db.set({ boardID: Unread.thread.board.ID, threadID: Unread.thread.ID, @@ -18132,12 +22998,20 @@ Unread = (function() { }); }), setLine: function(force) { + var node, oldPosition, ref; if (!Conf['Unread Line']) { return; } if (Unread.hr.hidden || d.hidden || (force === true)) { + oldPosition = Unread.linePosition; if ((Unread.linePosition = Unread.positionPrev())) { - $.after(Unread.linePosition.data.nodes.root, Unread.hr); + if (Unread.linePosition !== oldPosition) { + node = Unread.linePosition.data.nodes.bottom; + if (((ref = node.nextSibling) != null ? ref.tagName : void 0) === 'BR') { + node = node.nextSibling; + } + $.after(node, Unread.hr); + } } else { $.rm(Unread.hr); } @@ -18154,211 +23028,331 @@ Unread = (function() { titleDead = Unread.thread.isDead ? Unread.title.replace('-', (Unread.thread.isArchived ? '- Archived -' : '- 404 -')) : Unread.title; d.title = "" + titleQuotingYou + titleCount + titleDead; } + Unread.saveThreadWatcherCount(); + if (Conf['Unread Favicon'] && g.SITE.software === 'yotsuba') { + isDead = Unread.thread.isDead; + return Favicon.set((countQuotingYou ? (isDead ? 'unreadDeadY' : 'unreadY') : count ? (isDead ? 'unreadDead' : 'unread') : (isDead ? 'dead' : 'default'))); + } + }, + saveThreadWatcherCount: $.debounce(2 * $.SECOND, function() { + var i, j, posts, quotingYou, ref; $.forceSync('Remember Last Read Post'); if (Conf['Remember Last Read Post'] && (!Unread.thread.isDead || Unread.thread.isArchived)) { - ThreadWatcher.update(Unread.thread.board.ID, Unread.thread.ID, { + quotingYou = !Conf['Require OP Quote Link'] && QuoteYou.isYou(Unread.thread.OP) ? Unread.posts : Unread.postsQuotingYou; + if (!quotingYou.size) { + quotingYou.last = 0; + } else if (!quotingYou.has(quotingYou.last)) { + quotingYou.last = 0; + posts = Unread.thread.posts.keys; + for (i = j = ref = posts.length - 1; j >= 0; i = j += -1) { + if (quotingYou.has(+posts[i])) { + quotingYou.last = posts[i]; + break; + } + } + } + return ThreadWatcher.update(g.SITE.ID, Unread.thread.board.ID, Unread.thread.ID, { + last: Unread.thread.lastPost, isDead: Unread.thread.isDead, - unread: count, - quotingYou: countQuotingYou + isArchived: Unread.thread.isArchived, + unread: Unread.posts.size, + quotingYou: quotingYou.last || 0 }); } - if (Conf['Unread Favicon']) { - isDead = Unread.thread.isDead; - Favicon.el.href = countQuotingYou ? Favicon[isDead ? 'unreadDeadY' : 'unreadY'] : count ? Favicon[isDead ? 'unreadDead' : 'unread'] : Favicon[isDead ? 'dead' : 'default']; - return $.add(d.head, Favicon.el); + }) + }; + + return Unread; + +}).call(this); + +UnreadIndex = (function() { + var UnreadIndex; + + UnreadIndex = { + lastReadPost: $.dict(), + hr: $.dict(), + markReadLink: $.dict(), + init: function() { + if (!(g.VIEW === 'index' && Conf['Remember Last Read Post'] && Conf['Unread Line in Index'])) { + return; + } + this.enabled = true; + this.db = new DataBoard('lastReadPosts', this.sync); + Callbacks.Thread.push({ + name: 'Unread Line in Index', + cb: this.node + }); + $.on(d, 'IndexRefreshInternal', this.onIndexRefresh); + return $.on(d, 'PostsInserted PostsRemoved', this.onPostsInserted); + }, + node: function() { + UnreadIndex.lastReadPost[this.fullID] = UnreadIndex.db.get({ + boardID: this.board.ID, + threadID: this.ID + }) || 0; + if (!Index.enabled) { + return UnreadIndex.update(this); + } + }, + onIndexRefresh: function(e) { + var i, len, ref, results, thread, threadID; + if (e.detail.isCatalog) { + return; + } + ref = e.detail.threadIDs; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + threadID = ref[i]; + thread = g.threads.get(threadID); + results.push(UnreadIndex.update(thread)); + } + return results; + }, + onPostsInserted: function(e) { + var ref, ref1, thread, wasVisible; + if (e.target === Index.root) { + return; + } + thread = Get.threadFromNode(e.target); + if (!thread || thread.nodes.root !== e.target) { + return; + } + wasVisible = !!((ref = UnreadIndex.hr[thread.fullID]) != null ? ref.parentNode : void 0); + UnreadIndex.update(thread); + if (Conf['Scroll to Last Read Post'] && e.type === 'PostsInserted' && !wasVisible && !!((ref1 = UnreadIndex.hr[thread.fullID]) != null ? ref1.parentNode : void 0)) { + return Header.scrollToIfNeeded(UnreadIndex.hr[thread.fullID], true); + } + }, + sync: function() { + return g.threads.forEach(function(thread) { + var lastReadPost, ref; + lastReadPost = UnreadIndex.db.get({ + boardID: thread.board.ID, + threadID: thread.ID + }) || 0; + if (lastReadPost !== UnreadIndex.lastReadPost[thread.fullID]) { + UnreadIndex.lastReadPost[thread.fullID] = lastReadPost; + if ((ref = thread.nodes.root) != null ? ref.parentNode : void 0) { + return UnreadIndex.update(thread); + } + } + }); + }, + update: function(thread) { + var divider, firstUnread, hasUnread, hr, lastReadPost, link, repliesRead, repliesShown; + lastReadPost = UnreadIndex.lastReadPost[thread.fullID]; + repliesShown = 0; + repliesRead = 0; + firstUnread = null; + thread.posts.forEach(function(post) { + if (post.isReply && thread.nodes.root.contains(post.nodes.root)) { + repliesShown++; + if (post.ID <= lastReadPost) { + return repliesRead++; + } else if ((!firstUnread || post.ID < firstUnread.ID) && !post.isHidden && !QuoteYou.isYou(post)) { + return firstUnread = post; + } + } + }); + hr = UnreadIndex.hr[thread.fullID]; + if (firstUnread && (repliesRead || (lastReadPost === thread.OP.ID && (!$(g.SITE.selectors.summary, thread.nodes.root) || thread.ID in ExpandThread.statuses)))) { + if (!hr) { + hr = UnreadIndex.hr[thread.fullID] = $.el('hr', { + className: 'unread-line' + }); + } + $.before(firstUnread.nodes.root, hr); + } else { + $.rm(hr); + } + hasUnread = repliesShown ? firstUnread || !repliesRead : Index.enabled ? thread.lastPost > lastReadPost : thread.OP.ID > lastReadPost; + thread.nodes.root.classList.toggle('unread-thread', hasUnread); + link = UnreadIndex.markReadLink[thread.fullID]; + if (!link) { + link = UnreadIndex.markReadLink[thread.fullID] = $.el('a', { + className: 'unread-mark-read brackets-wrap', + href: 'javascript:;', + textContent: 'Mark Read' + }); + $.on(link, 'click', UnreadIndex.markRead); + } + if ((divider = $(g.SITE.selectors.threadDivider, thread.nodes.root))) { + return $.before(divider, link); + } else { + return $.add(thread.nodes.root, link); } + }, + markRead: function() { + var thread; + thread = Get.threadFromNode(this); + UnreadIndex.lastReadPost[thread.fullID] = thread.lastPost; + UnreadIndex.db.set({ + boardID: thread.board.ID, + threadID: thread.ID, + val: thread.lastPost + }); + $.rm(UnreadIndex.hr[thread.fullID]); + thread.nodes.root.classList.remove('unread-thread'); + return ThreadWatcher.update(g.SITE.ID, thread.board.ID, thread.ID, { + last: thread.lastPost, + unread: 0, + quotingYou: 0 + }); } }; - return Unread; + return UnreadIndex; }).call(this); Captcha = {}; (function() { - Captcha.fixes = { - imageKeys: '789456123uiojklm'.split('').concat(['Comma', 'Period']), - imageKeys16: '7890uiopjkl'.split('').concat(['Semicolon', 'm', 'Comma', 'Period', 'Slash']), - css: '.rc-imageselect-target > div:focus, .rc-image-tile-target:focus {\n outline: 2px solid #4a90e2;\n}\n.rc-imageselect-target td:focus {\n box-shadow: inset 0 0 0 2px #4a90e2;\n outline: none;\n}\n.rc-button-default:focus {\n box-shadow: inset 0 0 0 2px #0063d6;\n}', - cssNoscript: '.fbc-payload-imageselect {\n position: relative;\n}\n.fbc-payload-imageselect > label {\n position: absolute;\n display: block;\n height: 93.3px;\n width: 93.3px;\n}\nlabel[data-row="0"] {top: 0px;}\nlabel[data-row="1"] {top: 93.3px;}\nlabel[data-row="2"] {top: 186.6px;}\nlabel[data-col="0"] {left: 0px;}\nlabel[data-col="1"] {left: 93.3px;}\nlabel[data-col="2"] {left: 186.6px;}\n.fbc-payload-imageselect > input:focus + label {\n outline: 2px solid #4a90e2;\n}\n.fbc-button-verify input:focus {\n box-shadow: inset 0 0 0 2px #0063d6;\n}\nbody.focus .fbc {\n box-shadow: inset 0 0 0 2px #4a90e2;\n}', + Captcha.cache = { init: function() { - switch (location.pathname.split('/')[3]) { - case 'anchor': - return this.initMain(); - case 'frame': - return this.initPopup(); - case 'fallback': - return this.initNoscript(); - } - }, - initMain: function() { - var a, j, len, ref; - $.onExists(d.body, '#recaptcha-anchor', function(checkbox) { - var focus; - focus = function() { - var ref; - if (d.hasFocus() && ((ref = d.activeElement) === d.documentElement || ref === d.body)) { - return checkbox.focus(); - } + $.on(d, 'SaveCaptcha', (function(_this) { + return function(e) { + return _this.saveAPI(e.detail); }; - focus(); - return $.on(window, 'focus', function() { - return $.queueTask(focus); - }); - }); - ref = $$('.rc-anchor-pt a'); - for (j = 0, len = ref.length; j < len; j++) { - a = ref[j]; - a.tabIndex = -1; - } + })(this)); + return $.on(d, 'NoCaptcha', (function(_this) { + return function(e) { + return _this.noCaptcha(e.detail); + }; + })(this)); }, - initPopup: function() { - $.addStyle(this.css); - this.fixImages(); - new MutationObserver((function(_this) { + captchas: [], + getCount: function() { + return this.captchas.length; + }, + neededRaw: function() { + return !(this.haveCookie() || this.captchas.length || QR.req || this.submitCB) && (QR.posts.length > 1 || Conf['Auto-load captcha'] || !QR.posts[0].isOnlyQuotes() || QR.posts[0].file); + }, + needed: function() { + return this.neededRaw() && $.event('LoadCaptcha'); + }, + prerequest: function() { + if (!Conf['Prerequest Captcha']) { + return; + } + return $.queueTask((function(_this) { return function() { - return _this.fixImages(); + var isReply; + if (!_this.prerequested && _this.neededRaw() && !$.event('LoadCaptcha') && !QR.captcha.occupied() && QR.cooldown.seconds <= 60 && QR.selected === QR.posts[QR.posts.length - 1] && !QR.selected.isOnlyQuotes()) { + isReply = QR.selected.thread !== 'new'; + if (!$.event('RequestCaptcha', { + isReply: isReply + })) { + _this.prerequested = true; + _this.submitCB = function(captcha) { + if (captcha) { + return _this.save(captcha); + } + }; + return _this.updateCount(); + } + } }; - })(this)).observe(d.body, { - childList: true, - subtree: true - }); - return $.on(d, 'keydown', this.keybinds.bind(this)); + })(this)); }, - initNoscript: function() { - var data, ref, token; - this.noscript = true; - data = (token = (ref = $('.fbc-verification-token > textarea')) != null ? ref.value : void 0) ? { - token: token - } : { - working: true - }; - new Connection(window.parent, '*').send(data); - d.body.classList.toggle('focus', d.hasFocus()); - $.on(window, 'focus blur', function() { - return d.body.classList.toggle('focus', d.hasFocus()); - }); - this.images = $$('.fbc-payload-imageselect > input'); - this.width = 3; - if (this.images.length !== 9) { - return; + haveCookie: function() { + return /\b_ct=/.test(d.cookie) && QR.posts[0].thread !== 'new'; + }, + getOne: function() { + var captcha; + delete this.prerequested; + this.clear(); + if ((captcha = this.captchas.shift())) { + this.count(); + return captcha; + } else { + return null; } - $.addStyle(this.cssNoscript); - this.addLabels(); - $.on(d, 'keydown', this.keybinds.bind(this)); - return $.on($('.fbc-imageselect-challenge > form'), 'submit', this.checkForm.bind(this)); }, - fixImages: function() { - var img, j, len, ref; - this.images = $$('.rc-image-tile-target'); - if (!this.images.length) { - this.images = $$('.rc-imageselect-target > div, .rc-imageselect-target td'); + request: function(isReply) { + if (!this.submitCB) { + if ($.event('RequestCaptcha', { + isReply: isReply + })) { + return; + } } - this.width = $$('.rc-imageselect-target tr:first-of-type td').length || Math.round(Math.sqrt(this.images.length)); - ref = this.images; - for (j = 0, len = ref.length; j < len; j++) { - img = ref[j]; - img.tabIndex = 0; + return (function(_this) { + return function(cb) { + _this.submitCB = cb; + return _this.updateCount(); + }; + })(this); + }, + abort: function() { + if (this.submitCB) { + delete this.submitCB; + $.event('AbortCaptcha'); + return this.updateCount(); } - if (this.images.length === 9) { - return this.addTooltips(this.images); + }, + saveAPI: function(captcha) { + var cb; + if ((cb = this.submitCB)) { + delete this.submitCB; + cb(captcha); + return this.updateCount(); } else { - return this.addTooltips16(this.images); + return this.save(captcha); } }, - addLabels: function() { - var checkbox, i, imageSelect, label, labels; - imageSelect = $('.fbc-payload-imageselect'); - labels = (function() { - var j, len, ref, results; - ref = this.images; - results = []; - for (i = j = 0, len = ref.length; j < len; i = ++j) { - checkbox = ref[i]; - checkbox.id = "checkbox-" + i; - label = $.el('label', { - htmlFor: checkbox.id - }); - label.dataset.row = Math.floor(i / 3); - label.dataset.col = i % 3; - $.after(checkbox, label); - results.push(label); + noCaptcha: function(detail) { + var cb; + if ((cb = this.submitCB)) { + if (!this.haveCookie() || (detail != null ? detail.error : void 0)) { + QR.error((detail != null ? detail.error : void 0) || 'Failed to retrieve captcha.'); + QR.captcha.setup(d.activeElement === QR.nodes.status); } - return results; - }).call(this); - return this.addTooltips(labels); - }, - addTooltips: function(nodes) { - var i, j, len, node; - for (i = j = 0, len = nodes.length; j < len; i = ++j) { - node = nodes[i]; - node.title = this.imageKeys[i] + " or " + (this.imageKeys[i + 9][0].toUpperCase()) + this.imageKeys[i + 9].slice(1); + delete this.submitCB; + cb(); + return this.updateCount(); } }, - addTooltips16: function(nodes) { - var i, j, key, len, node, ref; - ref = this.imageKeys16; - for (i = j = 0, len = ref.length; j < len; i = ++j) { - key = ref[i]; - if (i % 4 < this.width && (node = nodes[nodes.length - (4 - Math.floor(i / 4)) * this.width + (i % 4)])) { - node.title = "" + (key[0].toUpperCase()) + key.slice(1); - } + save: function(captcha) { + var cb; + if ((cb = this.submitCB)) { + this.abort(); + cb(captcha); + return; } + this.captchas.push(captcha); + this.captchas.sort(function(a, b) { + return a.timeout - b.timeout; + }); + return this.count(); }, - checkForm: function(e) { - var checkbox, j, len, n, ref; - n = 0; - ref = this.images; - for (j = 0, len = ref.length; j < len; j++) { - checkbox = ref[j]; - if (checkbox.checked) { - n++; + clear: function() { + var captcha, i, j, len, now, ref; + if (this.captchas.length) { + now = Date.now(); + ref = this.captchas; + for (i = j = 0, len = ref.length; j < len; i = ++j) { + captcha = ref[i]; + if (captcha.timeout > now) { + break; + } + } + if (i) { + this.captchas = this.captchas.slice(i); + return this.count(); } } - if (n === 0) { - return e.preventDefault(); - } - }, - keybinds: function(e) { - var dx, i, img, key, last, n, reload, verify, w, x; - if (!(this.images && doc.contains(this.images[0]))) { - return; - } - n = this.images.length; - w = this.width; - last = n + w - 1; - reload = $('#recaptcha-reload-button, .fbc-button-reload'); - verify = $('#recaptcha-verify-button, .fbc-button-verify > input'); - x = this.images.indexOf(d.activeElement); - if (x < 0) { - x = d.activeElement === verify ? last : n; - } - key = Keybinds.keyCode(e); - if (!this.noscript && key === 'Space' && x < n) { - this.images[x].click(); - } else if (n === 9 && (i = this.imageKeys.indexOf(key)) >= 0) { - this.images[i % 9].click(); - verify.focus(); - } else if (n !== 9 && (i = this.imageKeys16.indexOf(key)) >= 0 && i % 4 < w && (img = this.images[n - (4 - Math.floor(i / 4)) * w + (i % 4)])) { - img.click(); - verify.focus(); - } else if (dx = { - 'Up': n, - 'Down': w, - 'Left': last, - 'Right': 1 - }[key]) { - x = (x + dx) % (n + w); - if ((n < x && x < last)) { - x = dx === last ? n : last; - } - (this.images[x] || (x === n ? reload : void 0) || (x === last ? verify : void 0)).focus(); - } else { - return; + }, + count: function() { + clearTimeout(this.timer); + if (this.captchas.length) { + this.timer = setTimeout(this.clear.bind(this), this.captchas[0].timeout - Date.now()); } - e.preventDefault(); - return e.stopPropagation(); + return this.updateCount(); + }, + updateCount: function() { + return $.event('CaptchaCount', this.captchas.length); } }; @@ -18367,39 +23361,27 @@ Captcha = {}; (function() { Captcha.replace = { init: function() { - if (!(d.cookie.indexOf('pass_enabled=1') < 0)) { - return; - } - if (location.hostname === 'sys.4chan.org' && /[?&]altc\b/.test(location.search) && Main.jsEnabled) { - $.onExists(doc, 'script[src="//www.google.com/recaptcha/api/js/recaptcha_ajax.js"]', function() { - $.global(function() { - return window.el.onload = null; - }); - return Captcha.v1.create(); - }); - return; - } - if (((Conf['Use Recaptcha v1'] && location.hostname === 'boards.4chan.org') || (Conf['Use Recaptcha v1 in Reports'] && location.hostname === 'sys.4chan.org')) && Main.jsEnabled) { - $.ready(Captcha.replace.v1); + var ref; + if (!(g.SITE.software === 'yotsuba' && d.cookie.indexOf('pass_enabled=1') < 0)) { return; } if (Conf['Force Noscript Captcha'] && Main.jsEnabled) { $.ready(Captcha.replace.noscript); return; } - if (Conf['captchaLanguage'].trim() || Conf['Captcha Fixes']) { - if (location.hostname === 'boards.4chan.org') { + if (Conf['captchaLanguage'].trim()) { + if ((ref = location.hostname) === 'boards.4chan.org' || ref === 'boards.4channel.org') { return $.onExists(doc, '#captchaFormPart', function(node) { - return $.onExists(node, 'iframe', Captcha.replace.iframe); + return $.onExists(node, 'iframe[src^="https://www.google.com/recaptcha/"]', Captcha.replace.iframe); }); } else { - return $.onExists(doc, 'iframe', Captcha.replace.iframe); + return $.onExists(doc, 'iframe[src^="https://www.google.com/recaptcha/"]', Captcha.replace.iframe); } } }, noscript: function() { var insert, noscript, original, span, toggle; - if (!((original = $('#g-recaptcha, #captchaContainerAlt')) && (noscript = $('noscript')))) { + if (!((original = $('#g-recaptcha')) && (noscript = $('noscript', original.parentNode)))) { return; } span = $.el('span', { @@ -18409,7 +23391,7 @@ Captcha = {}; $.rm(original); insert = function() { span.innerHTML = noscript.textContent; - return Captcha.replace.iframe($('iframe', span)); + return Captcha.replace.iframe($('iframe[src^="https://www.google.com/recaptcha/"]', span)); }; if ((toggle = $('#togglePostFormLink a, #form-link'))) { return $.on(toggle, 'click', insert); @@ -18417,25 +23399,6 @@ Captcha = {}; return insert(); } }, - v1: function() { - var form, link; - if (!$.id('g-recaptcha')) { - return; - } - Captcha.v1.replace(); - if ((link = $.id('form-link'))) { - return $.on(link, 'click', function() { - return Captcha.v1.create(); - }); - } else if (location.hostname === 'boards.4chan.org') { - form = $.id('postForm'); - return form.addEventListener('focus', (function() { - return Captcha.v1.create(); - }), true); - } else { - return Captcha.v1.create(); - } - }, iframe: function(iframe) { var lang, src; if ((lang = Conf['captchaLanguage'].trim())) { @@ -18444,385 +23407,129 @@ Captcha = {}; iframe.src = src; } } - return Captcha.replace.autocopy(iframe); - }, - autocopy: function(iframe) { - if (!(Conf['Captcha Fixes'] && /^https:\/\/www\.google\.com\/recaptcha\/api\/fallback\?/.test(iframe.src))) { - return; - } - return new Connection(iframe, 'https://www.google.com', { - working: function() { - var ref, ref1; - if ((ref = $.id('qr')) != null ? ref.contains(iframe) : void 0) { - return (ref1 = $('#qr .captcha-container textarea')) != null ? ref1.parentNode.hidden = true : void 0; - } - }, - token: function(token) { - var node, textarea; - node = iframe; - while ((node = node.parentNode)) { - if ((textarea = $('textarea', node))) { - break; - } - } - textarea.value = token; - return $.event('input', null, textarea); - } - }); } }; }).call(this); (function() { - Captcha.v1 = { - blank: "data:image/svg+xml,", + Captcha.t = { init: function() { - var imgContainer, input; - if (d.cookie.indexOf('pass_enabled=1') >= 0) { - return; - } - if (!(this.isEnabled = !!$('#g-recaptcha, #captchaContainerAlt'))) { - return; - } - imgContainer = $.el('div', { - className: 'captcha-img', - title: 'Reload reCAPTCHA' - }); - $.extend(imgContainer, { - innerHTML: "" - }); - input = $.el('input', { - className: 'captcha-input field', - title: 'Verification', - autocomplete: 'off', - spellcheck: false - }); - this.nodes = { - img: imgContainer.firstChild, - input: input - }; - $.on(input, 'blur', QR.focusout); - $.on(input, 'focus', QR.focusin); - $.on(input, 'keydown', QR.captcha.keydown.bind(QR.captcha)); - $.on(this.nodes.img.parentNode, 'click', QR.captcha.reload.bind(QR.captcha)); - $.addClass(QR.nodes.el, 'has-captcha', 'captcha-v1'); - $.after(QR.nodes.com.parentNode, [imgContainer, input]); - this.captchas = []; - $.get('captchas', [], function(arg) { - var captchas; - captchas = arg.captchas; - QR.captcha.sync(captchas); - return QR.captcha.clear(); - }); - $.sync('captchas', this.sync); - this.replace(); - this.beforeSetup(); - if (Conf['Auto-load captcha']) { - this.setup(); - } - new MutationObserver(this.afterSetup).observe($.id('captchaContainerAlt'), { - childList: true - }); - return this.afterSetup(); - }, - replace: function() { - var container, old; - if (this.script) { - return; - } - if (!(this.script = $('script[src="//www.google.com/recaptcha/api/js/recaptcha_ajax.js"]', d.head))) { - this.script = $.el('script', { - src: '//www.google.com/recaptcha/api/js/recaptcha_ajax.js' - }); - $.add(d.head, this.script); - } - if (old = $.id('g-recaptcha')) { - container = $.el('div', { - id: 'captchaContainerAlt' - }); - return $.replace(old, container); - } - }, - create: function() { - var cont, lang; - cont = $.id('captchaContainerAlt'); - if (this.occupied) { - return; - } - this.occupied = true; - if ((lang = Conf['captchaLanguage'].trim())) { - cont.dataset.lang = lang; - } - $.onExists(cont, '#recaptcha_image', function(image) { - return $.on(image, 'click', function() { - if ($.id('recaptcha_challenge_image')) { - return $.global(function() { - return window.Recaptcha.reload(); - }); - } - }); - }); - $.onExists(cont, '#recaptcha_response_field', function(field) { - $.on(field, 'keydown', function(e) { - if (e.keyCode === 8 && !field.value) { - return $.global(function() { - return window.Recaptcha.reload(); - }); - } - }); - if (location.hostname === 'sys.4chan.org') { - return field.focus(); - } - }); - return $.global(function() { - var container, options, script; - container = document.getElementById('captchaContainerAlt'); - options = { - theme: 'clean', - tabindex: { - "boards.4chan.org": 5 - }[location.hostname], - lang: container.dataset.lang - }; - if (window.Recaptcha) { - return window.Recaptcha.create('6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc', container, options); - } else { - script = document.head.querySelector('script[src="//www.google.com/recaptcha/api/js/recaptcha_ajax.js"]'); - return script.addEventListener('load', function() { - return window.Recaptcha.create('6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc', container, options); - }, false); - } - }); - }, - cb: { - focus: function() { - return QR.captcha.setup(false, true); - } - }, - beforeSetup: function() { - var img, input, ref; - ref = this.nodes, img = ref.img, input = ref.input; - img.parentNode.hidden = true; - img.src = this.blank; - input.value = ''; - input.placeholder = 'Focus to load reCAPTCHA'; - this.count(); - return $.on(input, 'focus click', this.cb.focus); - }, - needed: function() { - var captchaCount, postsCount; - captchaCount = this.captchas.length; - if (QR.req) { - captchaCount++; - } - postsCount = QR.posts.length; - if (postsCount === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) { - postsCount = 0; - } - return captchaCount < postsCount; - }, - onNewPost: function() {}, - onPostChange: function() {}, - setup: function(focus, force) { - if (!(this.isEnabled && (force || this.needed()))) { - return; - } - this.create(); - if (focus) { - $.addClass(QR.nodes.el, 'focus'); - return this.nodes.input.focus(); - } - }, - afterSetup: function() { - var challenge, img, input, ref, setLifetime; - if (!(challenge = $.id('recaptcha_challenge_field_holder'))) { - return; - } - if (challenge === QR.captcha.nodes.challenge) { - return; - } - setLifetime = function(e) { - return QR.captcha.lifetime = e.detail; - }; - $.on(window, 'captcha:timeout', setLifetime); - $.global(function() { - return window.dispatchEvent(new CustomEvent('captcha:timeout', { - detail: window.RecaptchaState.timeout - })); - }); - $.off(window, 'captcha:timeout', setLifetime); - ref = QR.captcha.nodes, img = ref.img, input = ref.input; - img.parentNode.hidden = false; - input.placeholder = 'Verification'; - QR.captcha.count(); - $.off(input, 'focus click', QR.captcha.cb.focus); - QR.captcha.nodes.challenge = challenge; - new MutationObserver(QR.captcha.load.bind(QR.captcha)).observe(challenge, { - childList: true, - subtree: true, - attributes: true - }); - QR.captcha.load(); - if (QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight) { - QR.nodes.el.style.top = null; - return QR.nodes.el.style.bottom = '0px'; + var root; + if (d.cookie.indexOf('pass_enabled=1') >= 0) { + return; } - }, - destroy: function() { - if (!this.script) { + if (!(this.isEnabled = !!$('#t-root') || !$.id('postForm'))) { return; } - $.global(function() { - return window.Recaptcha.destroy(); + root = $.el('div', { + className: 'captcha-root' }); - delete this.occupied; - if (this.nodes) { - return this.beforeSetup(); - } - }, - sync: function(captchas) { - if (captchas == null) { - captchas = []; - } - QR.captcha.captchas = captchas; - return QR.captcha.count(); + this.nodes = { + root: root + }; + $.addClass(QR.nodes.el, 'has-captcha', 'captcha-t'); + return $.after(QR.nodes.com.parentNode, root); }, - getOne: function() { - var captcha, challenge, response, timeout; - this.clear(); - if (captcha = this.captchas.shift()) { - this.count(); - $.set('captchas', this.captchas); - return captcha; + moreNeeded: function() {}, + getThread: function() { + var boardID, threadID; + boardID = g.BOARD.ID; + if (QR.posts[0].thread === 'new') { + threadID = '0'; } else { - challenge = this.nodes.img.alt; - timeout = this.timeout; - if (/\S/.test(response = this.nodes.input.value)) { - this.destroy(); - return { - challenge: challenge, - response: response, - timeout: timeout - }; - } else { - return null; - } - } - }, - save: function() { - var response; - if (!/\S/.test(response = this.nodes.input.value)) { - return; + threadID = '' + QR.posts[0].thread; } - this.nodes.input.value = ''; - this.captchas.push({ - challenge: this.nodes.img.alt, - response: response, - timeout: this.timeout - }); - this.captchas.sort(function(a, b) { - return a.timeout - b.timeout; - }); - this.count(); - this.destroy(); - this.setup(false, true); - return $.set('captchas', this.captchas); + return { + boardID: boardID, + threadID: threadID + }; }, - clear: function() { - var captcha, i, j, len, now, ref; - if (!this.captchas.length) { + setup: function(focus) { + if (!this.isEnabled) { return; } - $.forceSync('captchas'); - now = Date.now(); - ref = this.captchas; - for (i = j = 0, len = ref.length; j < len; i = ++j) { - captcha = ref[i]; - if (captcha.timeout > now) { - break; - } + if (!this.nodes.container) { + this.nodes.container = $.el('div', { + className: 'captcha-container' + }); + $.prepend(this.nodes.root, this.nodes.container); + Captcha.t.currentThread = Captcha.t.getThread(); + $.global(function() { + var el; + el = document.querySelector('#qr .captcha-container'); + window.TCaptcha.init(el, this.boardID, +this.threadID); + return window.TCaptcha.setErrorCb(function(err) { + return window.dispatchEvent(new CustomEvent('CreateNotification', { + detail: { + type: 'warning', + content: '' + err + } + })); + }); + }, Captcha.t.currentThread); } - if (!i) { - return; + if (focus) { + return $('#t-resp').focus(); } - this.captchas = this.captchas.slice(i); - this.count(); - return $.set('captchas', this.captchas); }, - load: function() { - var challenge, challenge_image; - if ($('#captchaContainerAlt[class~="recaptcha_is_showing_audio"]')) { - this.nodes.img.src = this.blank; + destroy: function() { + if (!(this.isEnabled && this.nodes.container)) { return; } - if (!this.nodes.challenge.firstChild) { + $.global(function() { + return window.TCaptcha.destroy(); + }); + $.rm(this.nodes.container); + return delete this.nodes.container; + }, + updateThread: function() { + var boardID, newThread, ref, threadID; + if (!this.isEnabled) { return; } - if (!(challenge_image = $.id('recaptcha_challenge_image'))) { - return; + ref = Captcha.t.currentThread || {}, boardID = ref.boardID, threadID = ref.threadID; + newThread = Captcha.t.getThread(); + if (!(newThread.boardID === boardID && newThread.threadID === threadID)) { + Captcha.t.destroy(); + return Captcha.t.setup(); } - this.timeout = Date.now() + this.lifetime * $.SECOND - $.MINUTE; - challenge = this.nodes.challenge.firstChild.value; - this.nodes.img.alt = challenge; - this.nodes.img.src = challenge_image.src; - this.nodes.input.value = ''; - return this.clear(); }, - count: function() { - var count, placeholder; - count = this.captchas ? this.captchas.length : 0; - placeholder = this.nodes.input.placeholder.replace(/\ \(.*\)$/, ''); - placeholder += (function() { - switch (count) { - case 0: - if (placeholder === 'Verification') { - return ' (Shift + Enter to cache)'; - } else { - return ''; - } - break; - case 1: - return ' (1 cached captcha)'; - default: - return " (" + count + " cached captchas)"; + getOne: function() { + var el, i, key, len, ref, response; + response = {}; + if (this.nodes.container) { + ref = ['t-response', 't-challenge']; + for (i = 0, len = ref.length; i < len; i++) { + key = ref[i]; + response[key] = $("[name='" + key + "']", this.nodes.container).value; } - })(); - this.nodes.input.placeholder = placeholder; - this.nodes.input.alt = count; - clearTimeout(this.timer); - if (count) { - return this.timer = setTimeout(this.clear.bind(this), this.captchas[0].timeout - Date.now()); } - }, - reload: function(focus) { - $.global(function() { - if (window.Recaptcha.type === 'image') { - window.Recaptcha.reload(); - } else { - window.Recaptcha.switch_type('image'); - } - return window.Recaptcha.should_focus = false; - }); - if (focus) { - return this.nodes.input.focus(); + if (!response['t-response'] && !((el = $('#t-msg')) && /Verification not required/i.test(el.textContent))) { + response = null; } + return response; }, - keydown: function(e) { - if (e.keyCode === 8 && !this.nodes.input.value) { - this.reload(); - } else if (e.keyCode === 13 && e.shiftKey) { - this.save(); - } else { + setUsed: function() { + if (!this.isEnabled) { return; } - return e.preventDefault(); + if (this.nodes.container) { + return $.global(function() { + return window.TCaptcha.clearChallenge(); + }); + } + }, + occupied: function() { + return !!this.nodes.container; } }; }).call(this); (function() { + var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + Captcha.v2 = { lifetime: 2 * $.MINUTE, init: function() { @@ -18830,25 +23537,18 @@ Captcha = {}; if (d.cookie.indexOf('pass_enabled=1') >= 0) { return; } - if (!(this.isEnabled = !!$('#g-recaptcha, #captchaContainerAlt, #captcha-forced-noscript'))) { + if (!(this.isEnabled = !!$('#g-recaptcha, #captcha-forced-noscript') || !$.id('postForm'))) { return; } if ((this.noscript = Conf['Force Noscript Captcha'] || !Main.jsEnabled)) { $.addClass(QR.nodes.el, 'noscript-captcha'); } - this.captchas = []; - $.get('captchas', [], function(arg) { - var captchas; - captchas = arg.captchas; - return QR.captcha.sync(captchas); - }); - $.sync('captchas', this.sync.bind(this)); + Captcha.cache.init(); + $.on(d, 'CaptchaCount', this.count.bind(this)); root = $.el('div', { className: 'captcha-root' }); - $.extend(root, { - innerHTML: "
      " - }); + $.extend(root, {innerHTML: "
      "}); counter = $('.captcha-counter > a', root); this.nodes = { root: root, @@ -18877,7 +23577,7 @@ Captcha = {}; })(this)); }, timeouts: {}, - postsCount: 0, + prevNeeded: 0, noscriptURL: function() { var lang, url; url = 'https://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc'; @@ -18886,28 +23586,17 @@ Captcha = {}; } return url; }, - needed: function() { - var captchaCount; - captchaCount = this.captchas.length; - if (QR.req) { - captchaCount++; - } - this.postsCount = QR.posts.length; - if (this.postsCount === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) { - this.postsCount = 0; - } - return captchaCount < this.postsCount; - }, - onNewPost: function() { - return this.setup(); - }, - onPostChange: function() { - if (this.postsCount === 0) { - this.setup(); - } - if (QR.posts.length === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) { - return this.postsCount = 0; - } + moreNeeded: function() { + return $.queueTask((function(_this) { + return function() { + var needed; + needed = Captcha.cache.needed(); + if (needed && !_this.prevNeeded) { + _this.setup(QR.cooldown.auto && d.activeElement === QR.nodes.status); + } + return _this.prevNeeded = needed; + }; + })(this)); }, toggle: function() { if (this.nodes.container && !this.timeouts.destroy) { @@ -18917,7 +23606,7 @@ Captcha = {}; } }, setup: function(focus, force) { - if (!(this.isEnabled && (this.needed() || force))) { + if (!(this.isEnabled && (Captcha.cache.needed() || force))) { return; } if (focus) { @@ -18933,7 +23622,7 @@ Captcha = {}; $.queueTask((function(_this) { return function() { var iframe; - if (_this.nodes.container && d.activeElement === _this.nodes.counter && (iframe = $('iframe', _this.nodes.container))) { + if (_this.nodes.container && d.activeElement === _this.nodes.counter && (iframe = $('iframe[src^="https://www.google.com/recaptcha/"]', _this.nodes.container))) { iframe.focus(); return QR.focus(); } @@ -18959,6 +23648,7 @@ Captcha = {}; var div, iframe, textarea; iframe = $.el('iframe', { id: 'qr-captcha-iframe', + scrolling: 'no', src: this.noscriptURL() }); div = $.el('div'); @@ -18968,14 +23658,14 @@ Captcha = {}; }, setupJS: function() { return $.global(function() { - var cbNative, render; + var cbNative, render, script; render = function() { var classList, container; classList = document.documentElement.classList; container = document.querySelector('#qr .captcha-container'); return container.dataset.widgetID = window.grecaptcha.render(container, { sitekey: '6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc', - theme: classList.contains('tomorrow') || classList.contains('dark-captcha') ? 'dark' : 'light', + theme: classList.contains('tomorrow') || classList.contains('spooky') || classList.contains('dark-captcha') ? 'dark' : 'light', callback: function(response) { return window.dispatchEvent(new CustomEvent('captcha:success', { detail: response @@ -18987,21 +23677,26 @@ Captcha = {}; return render(); } else { cbNative = window.onRecaptchaLoaded; - return window.onRecaptchaLoaded = function() { + window.onRecaptchaLoaded = function() { render(); return cbNative(); }; + if (!document.head.querySelector('script[src^="https://www.google.com/recaptcha/api.js"]')) { + script = document.createElement('script'); + script.src = 'https://www.google.com/recaptcha/api.js?onload=onRecaptchaLoaded&render=explicit'; + return document.head.appendChild(script); + } } }); }, afterSetup: function(mutations) { - var iframe, j, k, len, len1, mutation, node, ref, textarea; - for (j = 0, len = mutations.length; j < len; j++) { - mutation = mutations[j]; + var i, iframe, j, len, len1, mutation, node, ref, textarea; + for (i = 0, len = mutations.length; i < len; i++) { + mutation = mutations[i]; ref = mutation.addedNodes; - for (k = 0, len1 = ref.length; k < len1; k++) { - node = ref[k]; - if ((iframe = $.x('./descendant-or-self::iframe', node))) { + for (j = 0, len1 = ref.length; j < len1; j++) { + node = ref[j]; + if ((iframe = $.x('./descendant-or-self::iframe[starts-with(@src, "https://www.google.com/recaptcha/")]', node))) { this.setupIFrame(iframe); } if ((textarea = $.x('./descendant-or-self::textarea', node))) { @@ -19011,6 +23706,7 @@ Captcha = {}; } }, setupIFrame: function(iframe) { + var ref, ref1; if (!doc.contains(iframe)) { return; } @@ -19021,15 +23717,15 @@ Captcha = {}; if (d.activeElement === this.nodes.counter) { iframe.focus(); } - return $.global(function() { - var f; - f = document.querySelector('#qr iframe'); - return f.focus = f.blur = function() {}; - }); + if (((ref = $.engine) === 'blink' || ref === 'edge') && (ref1 = iframe.parentNode, indexOf.call($$('#qr .captcha-container > div > div:first-of-type'), ref1) >= 0)) { + return $.on(iframe.parentNode, 'scroll', function() { + return this.scrollTop = 0; + }); + } }, fixQRPosition: function() { if (QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight) { - QR.nodes.el.style.top = null; + QR.nodes.el.style.top = ''; return QR.nodes.el.style.bottom = '0px'; } }, @@ -19041,58 +23737,32 @@ Captcha = {}; })(this)); }, destroy: function() { - var garbage, i, ins, node, ref; if (!this.isEnabled) { return; } delete this.timeouts.destroy; $.rmClass(QR.nodes.el, 'captcha-open'); if (this.nodes.container) { + $.global(function() { + var container; + container = document.querySelector('#qr .captcha-container'); + return window.grecaptcha.reset(container.dataset.widgetID); + }); $.rm(this.nodes.container); + return delete this.nodes.container; } - delete this.nodes.container; - garbage = $.X('//iframe[starts-with(@src, "https://www.google.com/recaptcha/api2/frame")]/ancestor-or-self::*[parent::body]'); - i = 0; - while (node = garbage.snapshotItem(i++)) { - if (((ref = (ins = node.nextSibling)) != null ? ref.nodeName : void 0) === 'INS') { - $.rm(ins); - } - $.rm(node); - } - }, - sync: function(captchas) { - if (captchas == null) { - captchas = []; - } - this.captchas = captchas; - this.clear(); - return this.count(); }, - getOne: function() { - var captcha; - this.clear(); - if ((captcha = this.captchas.shift())) { - $.set('captchas', this.captchas); - this.count(); - return captcha; - } else { - return null; - } + getOne: function(isReply) { + return Captcha.cache.getOne(isReply); }, save: function(pasted, token) { var base, focus, ref; - $.forceSync('captchas'); - this.captchas.push({ + Captcha.cache.save({ response: token || $('textarea', this.nodes.container).value, timeout: Date.now() + this.lifetime }); - this.captchas.sort(function(a, b) { - return a.timeout - b.timeout; - }); - $.set('captchas', this.captchas); - this.count(); focus = ((ref = d.activeElement) != null ? ref.nodeName : void 0) === 'IFRAME' && /https?:\/\/www\.google\.com\/recaptcha\//.test(d.activeElement.src); - if (this.needed()) { + if (Captcha.cache.needed()) { if (focus) { if (QR.cooldown.auto || Conf['Post on Captcha Completion']) { this.nodes.counter.focus(); @@ -19117,34 +23787,12 @@ Captcha = {}; return QR.submit(); } }, - clear: function() { - var captcha, i, j, len, now, ref; - if (!this.captchas.length) { - return; - } - $.forceSync('captchas'); - now = Date.now(); - ref = this.captchas; - for (i = j = 0, len = ref.length; j < len; i = ++j) { - captcha = ref[i]; - if (captcha.timeout > now) { - break; - } - } - if (!i) { - return; - } - this.captchas = this.captchas.slice(i); - this.count(); - $.set('captchas', this.captchas); - return this.setup(d.activeElement === QR.nodes.status); - }, count: function() { - this.nodes.counter.textContent = "Captchas: " + this.captchas.length; - clearTimeout(this.timeouts.clear); - if (this.captchas.length) { - return this.timeouts.clear = setTimeout(this.clear.bind(this), this.captchas[0].timeout - Date.now()); - } + var count, loading; + count = Captcha.cache.getCount(); + loading = Captcha.cache.submitCB ? '...' : ''; + this.nodes.counter.textContent = "Captchas: " + count + loading; + return this.moreNeeded(); }, reload: function() { if ($('iframe[src^="https://www.google.com/recaptcha/api/fallback?"]', this.nodes.container)) { @@ -19157,6 +23805,9 @@ Captcha = {}; return window.grecaptcha.reset(container.dataset.widgetID); }); } + }, + occupied: function() { + return !!this.nodes.container && !this.timeouts.destroy; } }; @@ -19167,7 +23818,7 @@ PassLink = (function() { PassLink = { init: function() { - if (!Conf['Pass Link']) { + if (!(g.SITE.software === 'yotsuba' && Conf['Pass Link'])) { return; } return Main.ready(this.ready); @@ -19180,11 +23831,9 @@ PassLink = (function() { passLink = $.el('span', { className: 'brackets-wrap pass-link-container' }); - $.extend(passLink, { - innerHTML: "4chan Pass" - }); + $.extend(passLink, {innerHTML: "4chan Pass"}); $.on(passLink.firstElementChild, 'click', function() { - return window.open('//sys.4chan.org/auth', Date.now(), 'width=500,height=280,toolbar=0'); + return window.open("//sys." + (location.hostname.split('.')[1]) + ".org/auth", Date.now(), 'width=500,height=280,toolbar=0'); }); return $.before(styleSelector.previousSibling, [passLink, $.tn('\u00A0\u00A0')]); } @@ -19194,6 +23843,52 @@ PassLink = (function() { }).call(this); +PostRedirect = (function() { + var PostRedirect; + + PostRedirect = { + init: function() { + return $.on(d, 'QRPostSuccessful', (function(_this) { + return function(e) { + if (!e.detail.redirect) { + return; + } + _this.event = e; + _this.delays = 0; + return $.queueTask(function() { + if (e === _this.event && _this.delays === 0) { + return location.href = e.detail.redirect; + } + }); + }; + })(this)); + }, + delays: 0, + delay: function() { + var e; + if (!this.event) { + return null; + } + e = this.event; + this.delays++; + return (function(_this) { + return function() { + if (e !== _this.event) { + return; + } + _this.delays--; + if (_this.delays === 0) { + return location.href = e.detail.redirect; + } + }; + })(this); + } + }; + + return PostRedirect; + +}).call(this); + PostSuccessful = (function() { var PostSuccessful; @@ -19232,8 +23927,8 @@ QR = (function() { slice = [].slice; QR = { - mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/vnd.adobe.flash.movie', 'application/x-shockwave-flash', 'video/webm'], - validExtension: /\.(jpe?g|png|gif|pdf|swf|webm)$/i, + mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/vnd.adobe.flash.movie', 'application/x-shockwave-flash', 'video/webm', 'video/mp4'], + validExtension: /\.(jpe?g|png|gif|pdf|swf|webm|mp4)$/i, typeFromExtension: { 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', @@ -19241,7 +23936,8 @@ QR = (function() { 'gif': 'image/gif', 'pdf': 'application/pdf', 'swf': 'application/vnd.adobe.flash.movie', - 'webm': 'video/webm' + 'webm': 'video/webm', + 'mp4': 'video/mp4' }, extensionFromType: { 'image/jpeg': 'jpg', @@ -19250,20 +23946,18 @@ QR = (function() { 'application/pdf': 'pdf', 'application/vnd.adobe.flash.movie': 'swf', 'application/x-shockwave-flash': 'swf', - 'video/webm': 'webm' + 'video/webm': 'webm', + 'video/mp4': 'mp4' }, init: function() { - var sc, version; + var sc; if (!Conf['Quick Reply']) { return; } this.posts = []; - if (g.VIEW === 'archive') { - return; - } - version = Conf['Use Recaptcha v1'] && Main.jsEnabled ? 'v1' : 'v2'; - this.captcha = Captcha[version]; - $.on(d, '4chanXInitFinished', this.initReady); + $.on(d, '4chanXInitFinished', function() { + return BoardConfig.ready(QR.initReady); + }); Callbacks.Post.push({ name: 'Quick Reply', cb: this.node @@ -19288,30 +23982,43 @@ QR = (function() { return Header.addShortcut('qr', sc, 540); }, initReady: function() { - var link, linkBot, navLinksBot, origToggle; - $.off(d, '4chanXInitFinished', this.initReady); - QR.postingIsEnabled = !!$.id('postForm'); - if (!QR.postingIsEnabled) { - return; + var captchaVersion, config, link, linkBot, navLinksBot, origToggle, prop; + captchaVersion = $('#g-recaptcha, #captcha-forced-noscript') ? 'v2' : 't'; + QR.captcha = Captcha[captchaVersion]; + QR.postingIsEnabled = true; + config = g.BOARD.config; + prop = function(key, def) { + var ref; + return +((ref = config[key]) != null ? ref : def); + }; + QR.min_width = prop('min_image_width', 1); + QR.min_height = prop('min_image_height', 1); + QR.max_width = QR.max_height = 10000; + QR.max_size = prop('max_filesize', 4194304); + QR.max_size_video = prop('max_webm_filesize', QR.max_size); + QR.max_comment = prop('max_comment_chars', 2000); + QR.max_width_video = QR.max_height_video = 2048; + QR.max_duration_video = prop('max_webm_duration', 120); + QR.forcedAnon = !!config.forced_anon; + QR.spoiler = !!config.spoilers; + if ((origToggle = $.id('togglePostFormLink'))) { + link = $.el('h1', { + className: "qr-link-container" + }); + $.extend(link, {innerHTML: "" + ((g.VIEW === "thread") ? "Reply to Thread" : "Start a Thread") + ""}); + QR.link = link.firstElementChild; + $.on(link.firstChild, 'click', function() { + QR.open(); + return QR.nodes.com.focus(); + }); + $.before(origToggle, link); + origToggle.firstElementChild.textContent = 'Original Form'; } - link = $.el('h1', { - className: "qr-link-container" - }); - $.extend(link, { - innerHTML: "" + ((g.VIEW === "thread") ? "Reply to Thread" : "Start a Thread") + "" - }); - QR.link = link.firstElementChild; - $.on(link.firstChild, 'click', function() { - QR.open(); - return QR.nodes.com.focus(); - }); if (g.VIEW === 'thread') { linkBot = $.el('div', { className: "brackets-wrap qr-link-container-bottom" }); - $.extend(linkBot, { - innerHTML: "Reply to Thread" - }); + $.extend(linkBot, {innerHTML: "Reply to Thread"}); $.on(linkBot.firstElementChild, 'click', function() { QR.open(); return QR.nodes.com.focus(); @@ -19320,16 +24027,14 @@ QR = (function() { $.prepend(navLinksBot, linkBot); } } - origToggle = $.id('togglePostFormLink'); - $.before(origToggle, link); - origToggle.firstElementChild.textContent = 'Original Form'; $.on(d, 'QRGetFile', QR.getFile); + $.on(d, 'QRDrawFile', QR.drawFile); $.on(d, 'QRSetFile', QR.setFile); $.on(d, 'paste', QR.paste); $.on(d, 'dragover', QR.dragOver); $.on(d, 'drop', QR.dropFile); $.on(d, 'dragstart dragend', QR.drag); - $.on(d, 'IndexRefresh', QR.generatePostableThreadsList); + $.on(d, 'IndexRefreshInternal', QR.generatePostableThreadsList); $.on(d, 'ThreadUpdate', QR.statusCheck); if (!Conf['Persistent QR']) { return; @@ -19345,7 +24050,7 @@ QR = (function() { return; } thread = QR.posts[0].thread; - if (thread !== 'new' && g.threads[g.BOARD + "." + thread].isDead) { + if (thread !== 'new' && g.threads.get(g.BOARD + "." + thread).isDead) { return QR.abort(); } else { return QR.status(); @@ -19368,8 +24073,8 @@ QR = (function() { } else { try { QR.dialog(); - } catch (_error) { - err = _error; + } catch (error) { + err = error; delete QR.nodes; Main.handleErrors({ message: 'Quick Reply dialog creation crashed.', @@ -19388,7 +24093,7 @@ QR = (function() { } QR.nodes.el.hidden = true; QR.cleanNotifications(); - d.activeElement.blur(); + QR.blur(); $.rmClass(QR.nodes.el, 'dump'); $.addClass(QR.shortcut, 'disabled'); new QR.post(true); @@ -19417,7 +24122,7 @@ QR = (function() { }); }, hide: function() { - d.activeElement.blur(); + QR.blur(); $.addClass(QR.nodes.el, 'autohide'); return QR.nodes.autohide.checked = true; }, @@ -19432,6 +24137,11 @@ QR = (function() { return QR.unhide(); } }, + blur: function() { + if (QR.nodes.el.contains(d.activeElement)) { + return d.activeElement.blur(); + } + }, toggleSJIS: function(e) { e.preventDefault(); Conf['sjisPreview'] = !Conf['sjisPreview']; @@ -19449,6 +24159,16 @@ QR = (function() { texPreviewHide: function() { return $.rmClass(QR.nodes.el, 'tex-preview'); }, + addPost: function() { + var wasOpen; + wasOpen = QR.nodes && !QR.nodes.el.hidden; + QR.open(); + if (wasOpen) { + $.addClass(QR.nodes.el, 'dump'); + new QR.post(true); + } + return QR.nodes.com.focus(); + }, setCustomCooldown: function(enabled) { Conf['customCooldownEnabled'] = enabled; QR.cooldown.customCooldown = enabled; @@ -19456,7 +24176,7 @@ QR = (function() { }, toggleCustomCooldown: function() { var enabled; - enabled = $.hasClass(this, 'disabled'); + enabled = $.hasClass(QR.nodes.customCooldown, 'disabled'); QR.setCustomCooldown(enabled); return $.set('customCooldownEnabled', enabled); }, @@ -19496,6 +24216,9 @@ QR = (function() { } } }, + connectionError: function() { + return $.el('span', {innerHTML: "Connection error while posting. [More info]"}); + }, notifications: [], cleanNotifications: function() { var j, len, notification, ref; @@ -19512,7 +24235,7 @@ QR = (function() { return; } thread = QR.posts[0].thread; - if (thread !== 'new' && g.threads[g.BOARD + "." + thread].isDead) { + if (thread !== 'new' && g.threads.get(g.BOARD + "." + thread).isDead) { value = 'Dead'; disabled = true; QR.cooldown.auto = false; @@ -19533,7 +24256,7 @@ QR = (function() { } }, quote: function(e) { - var ancestor, caretPos, com, frag, insideCode, j, k, l, len, len1, len2, len3, len4, len5, n, node, o, post, q, range, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, sel, text, thread; + var ancestor, base, caretPos, com, frag, i, insideCode, j, k, l, len, len1, len2, len3, n, node, o, post, postRange, range, ref, ref1, ref2, ref3, ref4, ref5, ref6, root, sel, text, thread, wasOnlyQuotes; if (e != null) { e.preventDefault(); } @@ -19542,81 +24265,146 @@ QR = (function() { } sel = d.getSelection(); post = Get.postFromNode(this); + root = post.nodes.root; + postRange = new Range(); + postRange.selectNode(root); text = post.board.ID === g.BOARD.ID ? ">>" + post + "\n" : ">>>/" + post.board + "/" + post + "\n"; - if (sel.toString().trim() && post === Get.postFromNode(sel.anchorNode)) { - range = sel.getRangeAt(0); - frag = range.cloneContents(); - ancestor = range.commonAncestorContainer; - if ($.x('ancestor-or-self::*[self::s or contains(@class,"removed-spoiler")]', ancestor)) { - $.prepend(frag, $.tn('[spoiler]')); - $.add(frag, $.tn('[/spoiler]')); - } - if (insideCode = $.x('ancestor-or-self::pre[contains(@class,"prettyprint")]', ancestor)) { - $.prepend(frag, $.tn('[code]')); - $.add(frag, $.tn('[/code]')); - } - ref = $$((insideCode ? 'br' : '.prettyprint br'), frag); - for (j = 0, len = ref.length; j < len; j++) { - node = ref[j]; - $.replace(node, $.tn('\n')); - } - ref1 = $$('br', frag); - for (k = 0, len1 = ref1.length; k < len1; k++) { - node = ref1[k]; - if (node !== frag.lastChild) { - $.replace(node, $.tn('\n>')); + for (i = j = 0, ref = sel.rangeCount; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) { + try { + range = sel.getRangeAt(i); + if (range.compareBoundaryPoints(Range.START_TO_START, postRange) < 0) { + range.setStartBefore(root); } - } - ref2 = $$('s, .removed-spoiler', frag); - for (l = 0, len2 = ref2.length; l < len2; l++) { - node = ref2[l]; - $.replace(node, [$.tn('[spoiler]')].concat(slice.call(node.childNodes), [$.tn('[/spoiler]')])); - } - ref3 = $$('.prettyprint', frag); - for (n = 0, len3 = ref3.length; n < len3; n++) { - node = ref3[n]; - $.replace(node, [$.tn('[code]')].concat(slice.call(node.childNodes), [$.tn('[/code]')])); - } - ref4 = $$('.linkify[data-original]', frag); - for (o = 0, len4 = ref4.length; o < len4; o++) { - node = ref4[o]; - $.replace(node, $.tn(node.dataset.original)); - } - ref5 = $$('.embedder', frag); - for (q = 0, len5 = ref5.length; q < len5; q++) { - node = ref5[q]; - if (((ref6 = node.previousSibling) != null ? ref6.nodeValue : void 0) === ' ') { - $.rm(node.previousSibling); + if (range.compareBoundaryPoints(Range.END_TO_END, postRange) > 0) { + range.setEndAfter(root); } - $.rm(node); - } - text += ">" + (frag.textContent.trim()) + "\n"; + if (!range.toString().trim()) { + continue; + } + frag = range.cloneContents(); + ancestor = range.commonAncestorContainer; + if ($.x('ancestor-or-self::*[self::s or contains(@class,"removed-spoiler")]', ancestor)) { + $.prepend(frag, $.tn('[spoiler]')); + $.add(frag, $.tn('[/spoiler]')); + } + if (insideCode = $.x('ancestor-or-self::pre[contains(@class,"prettyprint")]', ancestor)) { + $.prepend(frag, $.tn('[code]')); + $.add(frag, $.tn('[/code]')); + } + ref1 = $$((insideCode ? 'br' : '.prettyprint br'), frag); + for (k = 0, len = ref1.length; k < len; k++) { + node = ref1[k]; + $.replace(node, $.tn('\n')); + } + ref2 = $$('br', frag); + for (l = 0, len1 = ref2.length; l < len1; l++) { + node = ref2[l]; + if (node !== frag.lastChild) { + $.replace(node, $.tn('\n>')); + } + } + if (typeof (base = g.SITE).insertTags === "function") { + base.insertTags(frag); + } + ref3 = $$('.linkify[data-original]', frag); + for (n = 0, len2 = ref3.length; n < len2; n++) { + node = ref3[n]; + $.replace(node, $.tn(node.dataset.original)); + } + ref4 = $$('.embedder', frag); + for (o = 0, len3 = ref4.length; o < len3; o++) { + node = ref4[o]; + if (((ref5 = node.previousSibling) != null ? ref5.nodeValue : void 0) === ' ') { + $.rm(node.previousSibling); + } + $.rm(node); + } + text += ">" + (frag.textContent.trim()) + "\n"; + } catch (error) {} } QR.openPost(); - ref7 = QR.nodes, com = ref7.com, thread = ref7.thread; + ref6 = QR.nodes, com = ref6.com, thread = ref6.thread; if (!com.value) { thread.value = Get.threadFromNode(this); } + wasOnlyQuotes = QR.selected.isOnlyQuotes(); caretPos = com.selectionStart; com.value = com.value.slice(0, caretPos) + text + com.value.slice(com.selectionEnd); range = caretPos + text.length; com.setSelectionRange(range, range); com.focus(); + if (wasOnlyQuotes) { + QR.selected.quotedText = com.value; + } QR.selected.save(com); return QR.selected.save(thread); }, characterCount: function() { - var count, counter; + var count, counter, splitPost; counter = QR.nodes.charCount; count = QR.nodes.com.value.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '_').length; counter.textContent = count; counter.hidden = count < QR.max_comment / 2; - return (count > QR.max_comment ? $.addClass : $.rmClass)(counter, 'warning'); + (count > QR.max_comment ? $.addClass : $.rmClass)(counter, 'warning'); + splitPost = QR.nodes.splitPost; + return splitPost.hidden = count < QR.max_comment; + }, + splitPost: function() { + var count, currentLength, currentPost, j, lastPostLength, len, line, newComment, post, ref, text; + count = QR.nodes.com.value.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '_').length; + text = QR.nodes.com.value; + if (count < QR.max_comment || QR.selected.isLocked) { + return; + } + lastPostLength = 0; + QR.selected.setComment(""); + ref = text.split("\n"); + for (j = 0, len = ref.length; j < len; j++) { + line = ref[j]; + currentLength = line.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '_').length + 1; + if ((currentLength + lastPostLength) > QR.max_comment) { + post = new QR.post(true); + post.setComment(line); + lastPostLength = currentLength; + } else { + currentPost = QR.selected; + newComment = [currentPost.com, line].filter(function(el) { + return el !== null; + }).join("\n"); + currentPost.setComment(newComment); + lastPostLength += currentLength; + } + } + return QR.nodes.el.classList.add('dump'); }, getFile: function() { var ref; return $.event('QRFile', (ref = QR.selected) != null ? ref.file : void 0); }, + drawFile: function(e) { + var el, file, isVideo, ref; + file = (ref = QR.selected) != null ? ref.file : void 0; + if (!(file && /^(image|video)\//.test(file.type))) { + return; + } + isVideo = /^video\//.test(file); + el = $.el((isVideo ? 'video' : 'img')); + $.on(el, 'error', function() { + return QR.openError(); + }); + $.on(el, (isVideo ? 'loadeddata' : 'load'), function() { + e.target.getContext('2d').drawImage(el, 0, 0); + URL.revokeObjectURL(el.src); + return $.event('QRImageDrawn', null, e.target); + }); + return el.src = URL.createObjectURL(file); + }, + openError: function() { + var div; + div = $.el('div'); + $.extend(div, {innerHTML: "Could not open file. [More info]"}); + return QR.error(div); + }, setFile: function(e) { var file, name, ref, source; ref = e.detail, file = ref.file, name = ref.name, source = ref.source; @@ -19648,30 +24436,34 @@ QR = (function() { return QR.handleFiles(e.dataTransfer.files); }, paste: function(e) { - var blob, files, item, j, len, ref; + var blob, file, file2, item, j, len, ref, score, score2, type; if (!e.clipboardData.items) { return; } - files = []; + file = null; + score = -1; ref = e.clipboardData.items; for (j = 0, len = ref.length; j < len; j++) { item = ref[j]; - if (!(item.kind === 'file')) { + if (!(item.kind === 'file' && (file2 = item.getAsFile()))) { continue; } - blob = item.getAsFile(); - blob.name = 'file'; - if (blob.type) { - blob.name += '.' + blob.type.split('/')[1]; + score2 = 2 * (file2.size <= QR.max_size) + (file2.type === 'image/png'); + if (score2 > score) { + file = file2; + score = score2; } - files.push(blob); } - if (!files.length) { - return; + if (file) { + type = file.type; + blob = new Blob([file], { + type: type + }); + blob.name = Conf['pastedname'] + "." + ($.getOwn(QR.extensionFromType, type) || 'jpg'); + QR.open(); + QR.handleFiles([blob]); + $.addClass(QR.nodes.el, 'dump'); } - QR.open(); - QR.handleFiles(files); - return $.addClass(QR.nodes.el, 'dump'); }, pasteFF: function() { var arr, blob, bstr, i, images, img, j, k, len, m, pasteArea, ref, src; @@ -19693,7 +24485,7 @@ QR = (function() { blob = new Blob([arr], { type: m[1] }); - blob.name = "file." + m[2]; + blob.name = Conf['pastedname'] + "." + m[2]; QR.handleFiles([blob]); } else if (/^https?:\/\//.test(src)) { QR.handleUrl(src); @@ -19701,18 +24493,22 @@ QR = (function() { } }, handleUrl: function(urlDefault) { - var url; - url = prompt('Enter a URL:', urlDefault); - if (url === null) { - return; - } - QR.nodes.fileButton.focus(); - return CrossOrigin.file(url, function(blob) { - if (blob && !/^text\//.test(blob.type)) { - return QR.handleFiles([blob]); - } else { - return QR.error("Can't load file."); + QR.open(); + QR.selected.preventAutoPost(); + return CrossOrigin.permission(function() { + var url; + url = prompt('Enter a URL:', urlDefault); + if (url === null) { + return; } + QR.nodes.fileButton.focus(); + return CrossOrigin.file(url, function(blob) { + if (blob && !/^text\//.test(blob.type)) { + return QR.handleFiles([blob]); + } else { + return QR.error("Can't load file."); + } + }); }); }, handleFiles: function(files) { @@ -19782,11 +24578,9 @@ QR = (function() { return (g.VIEW === 'thread' ? $.addClass : $.rmClass)(QR.nodes.el, 'reply-to-thread'); }, dialog: function() { - var dialog, event, i, items, m, match_max, match_min, name, node, nodes, ref, rules, save, scriptData, setNode; + var classList, config, dialog, event, i, items, name, node, nodes, save, setNode; QR.nodes = nodes = { - el: dialog = UI.dialog('qr', 'top: 50px; right: 0px;', { - innerHTML: "
      ×
      No selected file
      " - }) + el: dialog = UI.dialog('qr', {innerHTML: "
      ×
      No selected file
      "}) }; setNode = function(name, query) { return nodes[name] = $(query, dialog); @@ -19803,6 +24597,7 @@ QR = (function() { setNode('sub', '[data-name=sub]'); setNode('com', '[data-name=com]'); setNode('charCount', '#char-count'); + setNode('splitPost', '#split-post'); setNode('texPreview', '#tex-preview'); setNode('dumpList', '#dump-list'); setNode('addPost', '#add-post'); @@ -19822,33 +24617,14 @@ QR = (function() { setNode('status', '[type=submit]'); setNode('flashTag', '[name=filetag]'); setNode('fileInput', '[type=file]'); - rules = $('ul.rules').textContent.trim(); - match_min = rules.match(/.+smaller than (\d+)x(\d+).+/); - match_max = rules.match(/.+greater than (\d+)x(\d+).+/); - QR.min_width = +(match_min != null ? match_min[1] : void 0) || 1; - QR.min_height = +(match_min != null ? match_min[2] : void 0) || 1; - QR.max_width = +(match_max != null ? match_max[1] : void 0) || 10000; - QR.max_height = +(match_max != null ? match_max[2] : void 0) || 10000; - scriptData = Get.scriptData(); - QR.max_size = (m = scriptData.match(/\bmaxFilesize *= *(\d+)\b/)) ? +m[1] : 4194304; - QR.max_size_video = (m = scriptData.match(/\bmaxWebmFilesize *= *(\d+)\b/)) ? +m[1] : QR.max_size; - QR.max_comment = (m = scriptData.match(/\bcomlen *= *(\d+)\b/)) ? +m[1] : 2000; - QR.max_width_video = QR.max_height_video = 2048; - QR.max_duration_video = (ref = g.BOARD.ID) === 'gif' || ref === 'wsg' ? 300 : 120; - if (Conf['Show New Thread Option in Threads']) { - $.addClass(QR.nodes.el, 'show-new-thread-option'); - } - QR.forcedAnon = !!$('form[name="post"] input[name="name"][type="hidden"]'); - if (QR.forcedAnon) { - $.addClass(QR.nodes.el, 'forced-anon'); - } - QR.spoiler = !!$('.postForm input[name=spoiler]'); - if (QR.spoiler) { - $.addClass(QR.nodes.el, 'has-spoiler'); - } - if (g.BOARD.ID === 'jp' && Conf['sjisPreview']) { - $.addClass(QR.nodes.el, 'sjis-preview'); - } + config = g.BOARD.config; + classList = QR.nodes.el.classList; + classList.toggle('forced-anon', QR.forcedAnon); + classList.toggle('has-spoiler', QR.spoiler); + classList.toggle('has-sjis', !!config.sjis_tags); + classList.toggle('has-math', !!config.math_tags); + classList.toggle('sjis-preview', !!config.sjis_tags && Conf['sjisPreview']); + classList.toggle('show-new-thread-option', Conf['Show New Thread Option in Threads']); if (parseInt(Conf['customCooldown'], 10) > 0) { $.addClass(QR.nodes.fileSubmit, 'custom-cooldown'); $.get('customCooldownEnabled', Conf['customCooldownEnabled'], function(arg) { @@ -19858,12 +24634,15 @@ QR = (function() { return $.sync('customCooldownEnabled', QR.setCustomCooldown); }); } + QR.flagsInput(); $.on(nodes.autohide, 'change', QR.toggleHide); $.on(nodes.close, 'click', QR.close); + $.on(nodes.status, 'click', QR.submit); $.on(nodes.form, 'submit', QR.submit); $.on(nodes.sjisToggle, 'click', QR.toggleSJIS); $.on(nodes.texButton, 'mousedown', QR.texPreviewShow); $.on(nodes.texButton, 'mouseup', QR.texPreviewHide); + $.on(nodes.splitPost, 'click', QR.splitPost); $.on(nodes.addPost, 'click', function() { return new QR.post(true); }); @@ -19894,13 +24673,13 @@ QR = (function() { window.addEventListener('focus', QR.focus, true); window.addEventListener('blur', QR.focus, true); $.on(d, 'click', QR.focus); - if ($.engine === 'gecko') { + if ($.engine === 'gecko' && !window.DataTransferItemList) { nodes.pasteArea.hidden = false; - new MutationObserver(QR.pasteFF).observe(nodes.pasteArea, { - childList: true - }); } - items = ['thread', 'name', 'email', 'sub', 'com', 'filename']; + new MutationObserver(QR.pasteFF).observe(nodes.pasteArea, { + childList: true + }); + items = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'flag']; i = 0; save = function() { return QR.selected.save(this); @@ -19934,35 +24713,80 @@ QR = (function() { QR.oekaki.setup(); return $.event('QRDialogCreation', null, dialog); }, + flags: function() { + var addFlag, ref, select, textContent, value; + select = $.el('select', { + name: 'flag', + className: 'flagSelector' + }); + addFlag = function(value, textContent) { + return $.add(select, $.el('option', { + value: value, + textContent: textContent + })); + }; + addFlag('0', (g.BOARD.config.country_flags ? 'Geographic Location' : 'None')); + ref = g.BOARD.config.board_flags; + for (value in ref) { + textContent = ref[value]; + addFlag(value, textContent); + } + return select; + }, + flagsInput: function() { + var flag, nodes; + nodes = QR.nodes; + if (!nodes) { + return; + } + if (nodes.flag) { + $.rm(nodes.flag); + delete nodes.flag; + } + if (g.BOARD.config.board_flags) { + flag = QR.flags(); + flag.dataset.name = 'flag'; + flag.dataset["default"] = '0'; + nodes.flag = flag; + return $.add(nodes.form, flag); + } + }, submit: function(e) { - var captcha, cb, err, extra, filetag, formData, options, post, ref, textOnly, thread, threadID; + var captcha, cb, err, filetag, force, formData, options, post, ref, thread, threadID; if (e != null) { e.preventDefault(); } + force = e != null ? e.shiftKey : void 0; if (QR.req) { QR.abort(); return; } + $.forceSync('cooldowns'); if (QR.cooldown.seconds) { - QR.cooldown.auto = !QR.cooldown.auto; - QR.status(); - return; + if (force) { + QR.cooldown.clear(); + } else { + QR.cooldown.auto = !QR.cooldown.auto; + QR.status(); + return; + } } post = QR.posts[0]; + delete post.quotedText; post.forceSave(); threadID = post.thread; - thread = g.BOARD.threads[threadID]; + thread = g.BOARD.threads.get(threadID); if (g.BOARD.ID === 'f' && threadID === 'new') { filetag = QR.nodes.flashTag.value; } if (threadID === 'new') { threadID = null; - if (g.BOARD.ID === 'vg' && !post.sub) { + if (!!g.BOARD.config.require_subject && !post.sub) { err = 'New threads require a subject.'; - } else if (!($.hasClass(d.body, 'text_only') || post.file || (textOnly = !!$('input[name=textonly]', $.id('postForm'))))) { + } else if (!(!!g.BOARD.config.text_only || post.file)) { err = 'No file selected.'; } - } else if (g.BOARD.threads[threadID].isClosed) { + } else if (g.BOARD.threads.get(threadID).isClosed) { err = 'You can\'t reply to this thread anymore.'; } else if (!(post.com || post.file)) { err = 'No comment or file.'; @@ -19972,29 +24796,29 @@ QR = (function() { if (g.BOARD.ID === 'r9k' && !((ref = post.com) != null ? ref.match(/[a-z-]/i) : void 0)) { err || (err = 'Original comment required.'); } - if (QR.captcha.isEnabled && !err) { - captcha = QR.captcha.getOne(); + if (QR.captcha.isEnabled && !(QR.captcha === Captcha.v2 && /\b_ct=/.test(d.cookie) && threadID) && !(err && !force)) { + captcha = QR.captcha.getOne(!!threadID); + if (QR.captcha === Captcha.v2) { + captcha || (captcha = Captcha.cache.request(!!threadID)); + } if (!captcha) { err = 'No valid captcha.'; QR.captcha.setup(!QR.cooldown.auto || d.activeElement === QR.nodes.status); } } QR.cleanNotifications(); - if (err) { + if (err && !force) { QR.cooldown.auto = false; QR.status(); QR.error(err); return; } QR.cooldown.auto = QR.posts.length > 1; - if (Conf['Auto Hide QR'] && !QR.cooldown.auto) { - QR.hide(); - } - if (!QR.cooldown.auto && $.x('ancestor::div[@id="qr"]', d.activeElement)) { - d.activeElement.blur(); - } post.lock(); formData = { + MAX_FILE_SIZE: QR.max_size, + mode: 'regist', + pwd: QR.persona.getPassword(), resto: threadID, name: !QR.forcedAnon ? post.name : void 0, email: post.email, @@ -20003,66 +24827,74 @@ QR = (function() { upfile: post.file, filetag: filetag, spoiler: post.spoiler, - textonly: textOnly, - mode: 'regist', - pwd: QR.persona.getPassword() + flag: post.flag }; options = { responseType: 'document', withCredentials: true, - onload: QR.response, - onerror: function() { - delete QR.req; - post.unlock(); - QR.cooldown.auto = false; - QR.status(); - return QR.error($.el('span', { - innerHTML: "Connection error while posting. [More info]" - })); - } - }; - extra = { + onloadend: QR.response, form: $.formData(formData) }; if (Conf['Show Upload Progress']) { - extra.upCallbacks = { - onload: function() { + options.onprogress = function(e) { + var ref1; + if (this !== ((ref1 = QR.req) != null ? ref1.upload : void 0)) { + return; + } + if (e.loaded < e.total) { + QR.req.progress = (Math.round(e.loaded / e.total * 100)) + "%"; + } else { QR.req.isUploadFinished = true; QR.req.progress = '...'; - return QR.status(); - }, - onprogress: function(e) { - QR.req.progress = (Math.round(e.loaded / e.total * 100)) + "%"; - return QR.status(); } + return QR.status(); }; } cb = function(response) { + var key, val; if (response != null) { - if (response.challenge != null) { - extra.form.append('recaptcha_challenge_field', response.challenge); - extra.form.append('recaptcha_response_field', response.response); + QR.currentCaptcha = response; + if (QR.captcha === Captcha.v2) { + if (response.challenge != null) { + options.form.append('recaptcha_challenge_field', response.challenge); + options.form.append('recaptcha_response_field', response.response); + } else { + options.form.append('g-recaptcha-response', response.response); + } } else { - extra.form.append('g-recaptcha-response', response.response); + for (key in response) { + val = response[key]; + options.form.append(key, val); + } } } - QR.req = $.ajax("https://sys.4chan.org/" + g.BOARD + "/post", options, extra); + QR.req = $.ajax("https://sys." + (location.hostname.split('.')[1]) + ".org/" + g.BOARD + "/post", options); return QR.req.progress = '...'; }; if (typeof captcha === 'function') { QR.req = { progress: '...', abort: function() { + if (QR.captcha === Captcha.v2) { + Captcha.cache.abort(); + } return cb = null; } }; captcha(function(response) { - if (response) { + if (QR.captcha === Captcha.v2 && Captcha.cache.haveCookie()) { + if (typeof cb === "function") { + cb(); + } + if (response) { + return Captcha.cache.save(response); + } + } else if (response) { return typeof cb === "function" ? cb(response) : void 0; } else { delete QR.req; post.unlock(); - QR.cooldown.auto = !!QR.captcha.captchas.length; + QR.cooldown.auto = !!Captcha.cache.getCount(); return QR.status(); } }); @@ -20072,39 +24904,52 @@ QR = (function() { return QR.status(); }, response: function() { - var _, ban, err, h1, isReply, lastPostToThread, m, post, postID, postsCount, ref, ref1, ref2, req, resDoc, seconds, threadID, url; - req = QR.req; + var URL, _, base, connErr, err, h1, isReply, j, lastPostToThread, len, m, mi, open, post, postID, postsCount, ref, ref1, ref2, ref3, seconds, threadID; + if (this !== QR.req) { + return; + } delete QR.req; post = QR.posts[0]; post.unlock(); - resDoc = req.response; - if (ban = $('.banType', resDoc)) { - err = $.el('span', ban.textContent.toLowerCase() === 'banned' ? { - innerHTML: "You are banned on " + ($(".board", resDoc)).innerHTML + "! ;_;
      Click here to see the reason." - } : { - innerHTML: "You were issued a warning on " + ($(".board", resDoc)).innerHTML + " as " + ($(".nameBlock", resDoc)).innerHTML + ".
      Reason: " + ($(".reason", resDoc)).innerHTML - }); - } else if (err = resDoc.getElementById('errmsg')) { - if ((ref = $('a', err)) != null) { - ref.target = '_blank'; + if ((err = (ref = this.response) != null ? ref.getElementById('errmsg') : void 0)) { + if ((ref1 = $('a', err)) != null) { + ref1.target = '_blank'; + } + } else if ((connErr = !this.response || this.response.title !== 'Post successful!')) { + err = QR.connectionError(); + if (QR.captcha === Captcha.v2 && QR.currentCaptcha) { + Captcha.cache.save(QR.currentCaptcha); + } + } else if (this.status !== 200) { + err = "Error " + this.statusText + " (" + this.status + ")"; + } + if (!connErr) { + if (typeof (base = QR.captcha).setUsed === "function") { + base.setUsed(); } - } else if (resDoc.title !== 'Post successful!') { - err = 'Connection error with sys.4chan.org.'; - } else if (req.status !== 200) { - err = "Error " + req.statusText + " (" + req.status + ")"; } + delete QR.currentCaptcha; if (err) { - if (/captcha|verification/i.test(err.textContent) || err === 'Connection error with sys.4chan.org.') { + QR.errorCount = (QR.errorCount || 0) + 1; + if (/captcha|verification/i.test(err.textContent) || connErr) { if (/mistyped/i.test(err.textContent)) { err = 'You mistyped the CAPTCHA, or the CAPTCHA malfunctioned.'; } else if (/expired/i.test(err.textContent)) { err = 'This CAPTCHA is no longer valid because it has expired.'; } - QR.cooldown.auto = QR.captcha.isEnabled || err === 'Connection error with sys.4chan.org.'; - QR.cooldown.addDelay(post, 2); - } else if (err.textContent && (m = err.textContent.match(/(?:(\d+)\s+minutes?\s+)?(\d+)\s+second/i)) && !/duplicate|hour/i.test(err.textContent)) { + if (QR.errorCount >= 5) { + QR.cooldown.auto = false; + } else { + QR.cooldown.auto = QR.captcha.isEnabled || connErr; + QR.cooldown.addDelay(post, 2); + } + } else if (err.textContent && (m = err.textContent.match(/\d+\s+(?:minute|second)/gi)) && !/duplicate|hour/i.test(err.textContent)) { QR.cooldown.auto = !/have\s+been\s+muted/i.test(err.textContent); - seconds = 60 * (+(m[1] || 0)) + (+m[2]); + seconds = 0; + for (j = 0, len = m.length; j < len; j++) { + mi = m[j]; + seconds += (/minute/i.test(mi) ? 60 : 1) * (+mi.match(/\d+/)[0]); + } if (/muted/i.test(err.textContent)) { QR.cooldown.addMute(seconds); } else { @@ -20113,16 +24958,14 @@ QR = (function() { } else { QR.cooldown.auto = false; } - QR.captcha.setup(QR.cooldown.auto && ((ref1 = d.activeElement) === QR.nodes.status || ref1 === d.body)); - if (QR.captcha.isEnabled && !QR.captcha.captchas.length) { - QR.cooldown.auto = false; - } + QR.captcha.setup(QR.cooldown.auto && ((ref2 = d.activeElement) === QR.nodes.status || ref2 === d.body)); QR.status(); QR.error(err); return; } - h1 = $('h1', resDoc); - ref2 = h1.nextSibling.textContent.match(/thread:(\d+),no:(\d+)/), _ = ref2[0], threadID = ref2[1], postID = ref2[2]; + delete QR.errorCount; + h1 = $('h1', this.response); + ref3 = h1.nextSibling.textContent.match(/thread:(\d+),no:(\d+)/), _ = ref3[0], threadID = ref3[1], postID = ref3[2]; postID = +postID; threadID = +threadID || postID; isReply = threadID !== postID; @@ -20139,37 +24982,49 @@ QR = (function() { postsCount = QR.posts.length - 1; QR.cooldown.auto = postsCount && isReply; lastPostToThread = !((function() { - var j, len, p, ref3; - ref3 = QR.posts.slice(1); - for (j = 0, len = ref3.length; j < len; j++) { - p = ref3[j]; + var k, len1, p, ref4; + ref4 = QR.posts.slice(1); + for (k = 0, len1 = ref4.length; k < len1; k++) { + p = ref4[k]; if (p.thread === post.thread) { return true; } } })()); - if (!(Conf['Persistent QR'] || postsCount)) { - QR.close(); - } else { + if (postsCount) { post.rm(); QR.captcha.setup(d.activeElement === QR.nodes.status); + } else if (Conf['Persistent QR']) { + post.rm(); + if (Conf['Auto Hide QR']) { + QR.hide(); + } else { + QR.blur(); + } + } else { + QR.close(); } QR.cleanNotifications(); if (Conf['Posting Success Notifications']) { QR.notifications.push(new Notice('success', h1.textContent, 5)); } QR.cooldown.add(threadID, postID); - url = threadID === postID ? window.location.origin + "/" + g.BOARD + "/thread/" + threadID : threadID !== g.THREADID && lastPostToThread ? window.location.origin + "/" + g.BOARD + "/thread/" + threadID + "#p" + postID : void 0; - if (url) { + URL = threadID === postID ? window.location.origin + "/" + g.BOARD + "/thread/" + threadID : threadID !== g.THREADID && lastPostToThread && Conf['Open Post in New Tab'] ? window.location.origin + "/" + g.BOARD + "/thread/" + threadID + "#p" + postID : void 0; + if (URL) { + open = Conf['Open Post in New Tab'] || postsCount ? function() { + return $.open(URL); + } : function() { + return location.href = URL; + }; if (threadID === postID) { - QR.waitForThread(url); + QR.waitForThread(URL, open); } else { - $.open(url); + open(); } } return QR.status(); }, - waitForThread: function(url) { + waitForThread: function(url, cb) { var attempts, check; attempts = 0; check = function() { @@ -20177,21 +25032,26 @@ QR = (function() { onloadend: function() { attempts++; if (attempts >= 6 || this.status === 200) { - return $.open(url); + return cb(); } else { return setTimeout(check, attempts * $.SECOND); } - } - }, { + }, + responseType: 'text', type: 'HEAD' }); }; return check(); }, abort: function() { - if (QR.req && !QR.req.isUploadFinished) { - QR.req.abort(); + var oldReq; + if ((oldReq = QR.req) && !QR.req.isUploadFinished) { delete QR.req; + oldReq.abort(); + if (QR.captcha === Captcha.v2 && QR.currentCaptcha) { + Captcha.cache.save(QR.currentCaptcha); + } + delete QR.currentCaptcha; QR.posts[0].unlock(); QR.cooldown.auto = false; QR.notifications.push(new Notice('info', 'QR upload aborted.', 5)); @@ -20208,26 +25068,19 @@ QR = (function() { QR.cooldown = { seconds: 0, delays: { - thread: 0, - reply: 0, - image: 0, - reply_intra: 0, - image_intra: 0, - deletion: 60, - thread_global: 300 + deletion: 60 }, init: function() { if (!Conf['Quick Reply']) { return; } this.data = Conf['cooldowns']; + this.changes = $.dict(); return $.sync('cooldowns', this.sync); }, setup: function() { - var delay, m, ref, type; - if (m = Get.scriptData().match(/\bcooldowns *= *({[^}]+})/)) { - $.extend(QR.cooldown.delays, JSON.parse(m[1])); - } + var delay, ref, type; + $.extend(QR.cooldown.delays, g.BOARD.cooldowns()); QR.cooldown.maxDelay = 0; ref = QR.cooldown.delays; for (type in ref) { @@ -20249,7 +25102,7 @@ QR = (function() { return QR.cooldown.count(); }, sync: function(data) { - QR.cooldown.data = data || {}; + QR.cooldown.data = data || $.dict(); return QR.cooldown.start(); }, add: function(threadID, postID) { @@ -20270,6 +25123,7 @@ QR = (function() { postID: postID }); } + QR.cooldown.save(); return QR.cooldown.start(); }, addDelay: function(post, delay) { @@ -20280,6 +25134,7 @@ QR = (function() { cooldown = QR.cooldown.categorize(post); cooldown.delay = delay; QR.cooldown.set(g.BOARD.ID, Date.now(), cooldown); + QR.cooldown.save(); return QR.cooldown.start(); }, addMute: function(delay) { @@ -20290,6 +25145,7 @@ QR = (function() { type: 'mute', delay: delay }); + QR.cooldown.save(); return QR.cooldown.start(); }, "delete": function(post) { @@ -20297,22 +25153,21 @@ QR = (function() { if (!QR.cooldown.data) { return; } - $.forceSync('cooldowns'); - cooldowns = ((base = QR.cooldown.data)[name = post.board.ID] || (base[name] = {})); + cooldowns = ((base = QR.cooldown.data)[name = post.board.ID] || (base[name] = $.dict())); for (id in cooldowns) { cooldown = cooldowns[id]; if ((cooldown.delay == null) && cooldown.threadID === post.thread.ID && cooldown.postID === post.ID) { - delete cooldowns[id]; + QR.cooldown.set(post.board.ID, id, null); } } - return QR.cooldown.save([post.board.ID]); + return QR.cooldown.save(); }, secondsDeletion: function(post) { var cooldown, cooldowns, seconds, start; if (!(QR.cooldown.data && Conf['Cooldown'])) { return 0; } - cooldowns = QR.cooldown.data[post.board.ID] || {}; + cooldowns = QR.cooldown.data[post.board.ID] || $.dict(); for (start in cooldowns) { cooldown = cooldowns[start]; if ((cooldown.delay == null) && cooldown.threadID === post.thread.ID && cooldown.postID === post.ID) { @@ -20334,28 +25189,56 @@ QR = (function() { }; } }, + mergeChange: function(data, scope, id, value) { + if (value) { + return (data[scope] || (data[scope] = $.dict()))[id] = value; + } else if (scope in data) { + delete data[scope][id]; + if (Object.keys(data[scope]).length === 0) { + return delete data[scope]; + } + } + }, set: function(scope, id, value) { - var base, cooldowns; - $.forceSync('cooldowns'); - cooldowns = ((base = QR.cooldown.data)[scope] || (base[scope] = {})); - cooldowns[id] = value; - return $.set('cooldowns', QR.cooldown.data); + var base; + QR.cooldown.mergeChange(QR.cooldown.data, scope, id, value); + return ((base = QR.cooldown.changes)[scope] || (base[scope] = $.dict()))[id] = value; }, - save: function(scopes) { - var data, i, len, scope; - data = QR.cooldown.data; - for (i = 0, len = scopes.length; i < len; i++) { - scope = scopes[i]; - if (scope in data && !Object.keys(data[scope]).length) { - delete data[scope]; - } + save: function() { + var changes; + changes = QR.cooldown.changes; + if (!Object.keys(changes).length) { + return; } - return $.set('cooldowns', data); + return $.get('cooldowns', $.dict(), function(arg) { + var cooldowns, id, ref, scope, value; + cooldowns = arg.cooldowns; + for (scope in QR.cooldown.changes) { + ref = QR.cooldown.changes[scope]; + for (id in ref) { + value = ref[id]; + QR.cooldown.mergeChange(cooldowns, scope, id, value); + } + QR.cooldown.data = cooldowns; + } + return $.set('cooldowns', cooldowns, function() { + return QR.cooldown.changes = $.dict(); + }); + }); }, - count: function() { + clear: function() { + QR.cooldown.data = $.dict(); + QR.cooldown.changes = $.dict(); + QR.cooldown.auto = false; + QR.cooldown.update(); + return $.queueTask($["delete"], 'cooldowns'); + }, + update: function() { var base, cooldown, cooldowns, elapsed, i, len, maxDelay, nCooldowns, now, ref, ref1, save, scope, seconds, start, suffix, threadID, type, update; - $.forceSync('cooldowns'); - save = []; + if (!QR.cooldown.isCounting) { + return; + } + save = false; nCooldowns = 0; now = Date.now(); ref = QR.cooldown.categorize(QR.posts[0]), type = ref.type, threadID = ref.threadID; @@ -20364,20 +25247,20 @@ QR = (function() { ref1 = [g.BOARD.ID, 'global']; for (i = 0, len = ref1.length; i < len; i++) { scope = ref1[i]; - cooldowns = ((base = QR.cooldown.data)[scope] || (base[scope] = {})); + cooldowns = ((base = QR.cooldown.data)[scope] || (base[scope] = $.dict())); for (start in cooldowns) { cooldown = cooldowns[start]; start = +start; elapsed = Math.floor((now - start) / $.SECOND); if (elapsed < 0) { - delete cooldowns[start]; - save.push(scope); + QR.cooldown.set(scope, start, null); + save = true; continue; } if (cooldown.delay != null) { if (cooldown.delay <= elapsed) { - delete cooldowns[start]; - save.push(scope); + QR.cooldown.set(scope, start, null); + save = true; } else if ((cooldown.type === type && cooldown.threadID === threadID) || cooldown.type === 'mute') { seconds = Math.max(seconds, cooldown.delay - elapsed); } @@ -20388,23 +25271,23 @@ QR = (function() { maxDelay = Math.max(maxDelay, parseInt(Conf['customCooldown'], 10)); } if (maxDelay <= elapsed) { - delete cooldowns[start]; - save.push(scope); + QR.cooldown.set(scope, start, null); + save = true; continue; } if ((type === 'thread') === (cooldown.threadID === cooldown.postID) && cooldown.boardID !== g.BOARD.ID) { - suffix = scope === 'global' ? '_global' : type !== 'thread' && threadID === cooldown.threadID ? '_intra' : ''; + suffix = scope === 'global' ? '_global' : ''; seconds = Math.max(seconds, QR.cooldown.delays[type + suffix] - elapsed); - } - if (QR.cooldown.customCooldown) { - seconds = Math.max(seconds, parseInt(Conf['customCooldown'], 10) - elapsed); + if (QR.cooldown.customCooldown) { + seconds = Math.max(seconds, parseInt(Conf['customCooldown'], 10) - elapsed); + } } } nCooldowns += Object.keys(cooldowns).length; } } - if (save.length) { - QR.cooldown.save(save); + if (save) { + QR.cooldown.save; } if (nCooldowns) { clearTimeout(QR.cooldown.timeout); @@ -20415,9 +25298,12 @@ QR = (function() { update = seconds !== QR.cooldown.seconds; QR.cooldown.seconds = seconds; if (update) { - QR.status(); + return QR.status(); } - if (seconds === 0 && QR.cooldown.auto && !QR.req) { + }, + count: function() { + QR.cooldown.update(); + if (QR.cooldown.seconds === 0 && QR.cooldown.auto && !QR.req) { return QR.submit(); } } @@ -20441,7 +25327,7 @@ QR = (function() { $.on(a, 'click', this.editFile); return Menu.menu.addEntry({ el: a, - order: 95, + order: 90, open: function(post) { var file; QR.oekaki.menu.post = post; @@ -20478,6 +25364,9 @@ QR = (function() { }); return video.currentTime = currentTime; }); + $.on(video, 'error', function() { + return QR.openError(); + }); return video.src = URL.createObjectURL(blob); } else { blob.name = post.file.name; @@ -20513,15 +25402,15 @@ QR = (function() { }, load: function(cb) { var n, onload, script, style; - if ($('script[src^="//s.4cdn.org/js/painter"]', d.head)) { + if ($('script[src^="//s.4cdn.org/js/tegaki"]', d.head)) { return cb(); } else { style = $.el('link', { rel: 'stylesheet', - href: "//s.4cdn.org/css/painter." + (Date.now()) + ".css" + href: "//s.4cdn.org/css/tegaki." + (Date.now()) + ".css" }); script = $.el('script', { - src: "//s.4cdn.org/js/painter.min." + (Date.now()) + ".js" + src: "//s.4cdn.org/js/tegaki.min." + (Date.now()) + ".js" }); n = 0; onload = function() { @@ -20578,45 +25467,55 @@ QR = (function() { })); }; cb = function(e) { - var file, isVideo; - document.removeEventListener('QRFile', cb, false); - if (!e.detail) { + var canvas, selected; + if (e) { + this.removeEventListener('QRMetadata', cb, false); + } + selected = document.getElementById('selected'); + if (!(selected != null ? selected.dataset.type : void 0)) { return error('No file to edit.'); } - if (!/^(image|video)\//.test(e.detail.type)) { + if (!/^(image|video)\//.test(selected.dataset.type)) { return error('Not an image.'); } - isVideo = /^video\//.test(e.detail.type); - file = document.createElement(isVideo ? 'video' : 'img'); - file.addEventListener('error', function() { - return error('Could not open file.', false); + if (!selected.dataset.height) { + return error('Metadata not available.'); + } + if (selected.dataset.height === 'loading') { + selected.addEventListener('QRMetadata', cb, false); + return; + } + if (Tegaki.bg) { + Tegaki.destroy(); + } + FCX.oekakiName = name; + Tegaki.open({ + onDone: FCX.oekakiCB, + onCancel: function() { + return Tegaki.bgColor = '#ffffff'; + }, + width: +selected.dataset.width, + height: +selected.dataset.height, + bgColor: 'transparent' }); - file.addEventListener((isVideo ? 'loadeddata' : 'load'), function() { - if (Tegaki.bg) { - Tegaki.destroy(); - } - FCX.oekakiName = name; - Tegaki.open({ - onDone: FCX.oekakiCB, - onCancel: function() { - return Tegaki.bgColor = '#ffffff'; - }, - width: file.naturalWidth || file.videoWidth, - height: file.naturalHeight || file.videoHeight, - bgColor: 'transparent' - }); - return Tegaki.activeCtx.drawImage(file, 0, 0); + canvas = document.createElement('canvas'); + canvas.width = canvas.naturalWidth = +selected.dataset.width; + canvas.height = canvas.naturalHeight = +selected.dataset.height; + canvas.hidden = true; + document.body.appendChild(canvas); + canvas.addEventListener('QRImageDrawn', function() { + this.remove(); + return Tegaki.onOpenImageLoaded.call(this); }, false); - return file.src = URL.createObjectURL(e.detail); + return canvas.dispatchEvent(new CustomEvent('QRDrawFile', { + bubbles: true + })); }; if (Tegaki.bg && Tegaki.onDoneCb === FCX.oekakiCB && source === FCX.oekakiLatest) { FCX.oekakiName = name; return Tegaki.resume(); } else { - document.addEventListener('QRFile', cb, false); - return document.dispatchEvent(new CustomEvent('QRGetFile', { - bubbles: true - })); + return cb(); } }); }); @@ -20720,7 +25619,8 @@ QR = (function() { var persona; persona = arg['QR.persona']; persona = { - name: post.name + name: post.name, + flag: post.flag }; return $.set('QR.persona', persona); }); @@ -20743,9 +25643,7 @@ QR = (function() { draggable: true, href: 'javascript:;' }); - $.extend(el, { - innerHTML: "" - }); + $.extend(el, {innerHTML: ""}); this.nodes = { el: el, rm: el.firstChild, @@ -20763,8 +25661,9 @@ QR = (function() { return function(e) { _this.spoiler = e.target.checked; if (_this === QR.selected) { - return QR.nodes.spoiler.checked = _this.spoiler; + QR.nodes.spoiler.checked = _this.spoiler; } + return _this.preventAutoPost(); }; })(this)); ref = $$('label', el); @@ -20789,6 +25688,9 @@ QR = (function() { _this.name = 'name' in QR.persona.always ? QR.persona.always.name : prev ? prev.name : persona.name; _this.email = 'email' in QR.persona.always ? QR.persona.always.email : ''; _this.sub = 'sub' in QR.persona.always ? QR.persona.always.sub : ''; + if (QR.nodes.flag) { + _this.flag = prev ? prev.flag : persona.flag && persona.flag in g.BOARD.config.board_flags ? persona.flag : void 0; + } if (QR.selected === _this) { return _this.load(); } @@ -20798,13 +25700,11 @@ QR = (function() { this.select(); } this.unlock(); - $.queueTask(function() { - return QR.captcha.onNewPost(); - }); + QR.captcha.moreNeeded(); } _Class.prototype.rm = function() { - var index; + var base, index; this["delete"](); index = QR.posts.indexOf(this); if (QR.posts.length === 1) { @@ -20814,7 +25714,8 @@ QR = (function() { (QR.posts[index - 1] || QR.posts[index + 1]).select(); } QR.posts.splice(index, 1); - return QR.status(); + QR.status(); + return typeof (base = QR.captcha).updateThread === "function" ? base.updateThread() : void 0; }; _Class.prototype["delete"] = function() { @@ -20832,7 +25733,7 @@ QR = (function() { if (this !== QR.selected) { return; } - ref = ['thread', 'name', 'email', 'sub', 'com', 'fileButton', 'filename', 'spoiler']; + ref = ['thread', 'name', 'email', 'sub', 'com', 'fileButton', 'filename', 'spoiler', 'flag']; for (i = 0, len = ref.length; i < len; i++) { name = ref[i]; if (node = QR.nodes[name]) { @@ -20865,7 +25766,7 @@ QR = (function() { _Class.prototype.load = function() { var i, len, name, node, ref; - ref = ['thread', 'name', 'email', 'sub', 'com', 'filename']; + ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'flag']; for (i = 0, len = ref.length; i < len; i++) { name = ref[i]; if (!(node = QR.nodes[name])) { @@ -20878,32 +25779,44 @@ QR = (function() { return QR.characterCount(); }; - _Class.prototype.save = function(input) { - var name, ref; + _Class.prototype.save = function(input, forced) { + var base, name, prev; if (input.type === 'checkbox') { this.spoiler = input.checked; return; } name = input.dataset.name; + if (name !== 'thread' && name !== 'name' && name !== 'email' && name !== 'sub' && name !== 'com' && name !== 'filename' && name !== 'flag') { + return; + } + prev = this[name] || input.dataset["default"] || null; this[name] = input.value || input.dataset["default"] || null; switch (name) { case 'thread': (this.thread !== 'new' ? $.addClass : $.rmClass)(QR.nodes.el, 'reply-to-thread'); - return QR.status(); + QR.status(); + if (typeof (base = QR.captcha).updateThread === "function") { + base.updateThread(); + } + break; case 'com': this.updateComment(); - if (QR.cooldown.auto && this === QR.posts[0] && (0 < (ref = QR.cooldown.seconds) && ref <= 5)) { - return QR.cooldown.auto = false; - } break; case 'filename': if (!this.file) { return; } this.saveFilename(); - return this.updateFilename(); + this.updateFilename(); + break; case 'name': - return QR.persona.set(this); + case 'flag': + if (this[name] !== prev) { + QR.persona.set(this); + } + } + if (!forced) { + return this.preventAutoPost(); } }; @@ -20912,13 +25825,22 @@ QR = (function() { if (this !== QR.selected) { return; } - ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler']; + ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler', 'flag']; for (i = 0, len = ref.length; i < len; i++) { name = ref[i]; if (!(node = QR.nodes[name])) { continue; } - this.save(node); + this.save(node, true); + } + }; + + _Class.prototype.preventAutoPost = function() { + if (QR.cooldown.auto && this === QR.posts[0]) { + QR.cooldown.update(); + if (QR.cooldown.seconds <= 5) { + return QR.cooldown.auto = false; + } } }; @@ -20935,9 +25857,14 @@ QR = (function() { QR.characterCount(); } this.nodes.span.textContent = this.com; - return $.queueTask(function() { - return QR.captcha.onPostChange(); - }); + QR.captcha.moreNeeded(); + if (QR.captcha === Captcha.v2) { + return Captcha.cache.prerequest(); + } + }; + + _Class.prototype.isOnlyQuotes = function() { + return (this.com || '').trim() === (this.quotedText || '').trim(); }; _Class.rmErrored = function(e) { @@ -20959,14 +25886,12 @@ QR = (function() { } }; - _Class.prototype.error = function(className, message) { + _Class.prototype.error = function(className, message, link) { var div, ref, rm, rmAll; div = $.el('div', { className: className }); - $.extend(div, { - innerHTML: E(message) + "
      [delete] [delete all]" - }); + $.extend(div, {innerHTML: E(message) + ((link) ? " [More info]" : "") + "
      [delete post] [delete all]"}); (this.errors || (this.errors = [])).push(div); ref = $$('a', div), rm = ref[0], rmAll = ref[1]; $.on(div, 'click', (function(_this) { @@ -20988,8 +25913,8 @@ QR = (function() { return QR.error(div, true); }; - _Class.prototype.fileError = function(message) { - return this.error('file-error', this.filename + ": " + message); + _Class.prototype.fileError = function(message, link) { + return this.error('file-error', this.filename + ": " + message, link); }; _Class.prototype.dismissErrors = function(test) { @@ -21014,7 +25939,7 @@ QR = (function() { var ext, ref; this.file = file1; if (Conf['Randomize Filename'] && g.BOARD.ID !== 'f') { - this.filename = "" + (Date.now() - Math.floor(Math.random() * 365 * $.DAY)); + this.filename = "" + (Date.now() * 1000 - Math.floor(Math.random() * 365 * $.DAY * 1000)); if (ext = this.file.name.match(QR.validExtension)) { this.filename += ext[0]; } @@ -21024,9 +25949,7 @@ QR = (function() { this.filesize = $.bytesToString(this.file.size); this.checkSize(); $.addClass(this.nodes.el, 'has-file'); - $.queueTask(function() { - return QR.captcha.onPostChange(); - }); + QR.captcha.moreNeeded(); URL.revokeObjectURL(this.URL); this.saveFilename(); if (this === QR.selected) { @@ -21034,12 +25957,15 @@ QR = (function() { } else { this.updateFilename(); } - this.nodes.el.style.backgroundImage = null; + this.rmMetadata(); + this.nodes.el.dataset.type = this.file.type; + this.nodes.el.style.backgroundImage = ''; if (ref = this.file.type, indexOf.call(QR.mimeTypes, ref) < 0) { - return this.fileError('Unsupported file type.'); + this.fileError('Unsupported file type.'); } else if (/^(image|video)\//.test(this.file.type)) { - return this.readFile(); + this.readFile(); } + return this.preventAutoPost(); }; _Class.prototype.checkSize = function() { @@ -21066,26 +25992,32 @@ QR = (function() { $.off(el, event, onload); $.off(el, 'error', onerror); _this.checkDimensions(el); - return _this.setThumbnail(el); + _this.setThumbnail(el); + return $.event('QRMetadata', null, _this.nodes.el); }; })(this); onerror = (function(_this) { return function() { $.off(el, event, onload); $.off(el, 'error', onerror); - _this.fileError((isVideo ? 'Video' : 'Image') + " appears corrupt"); - return URL.revokeObjectURL(el.src); + _this.fileError("Corrupt " + (isVideo ? 'video' : 'image') + " or error reading metadata.", 'https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions#error-reading-metadata'); + URL.revokeObjectURL(el.src); + _this.nodes.el.removeAttribute('data-height'); + return $.event('QRMetadata', null, _this.nodes.el); }; })(this); + this.nodes.el.dataset.height = 'loading'; $.on(el, event, onload); $.on(el, 'error', onerror); return el.src = URL.createObjectURL(this.file); }; _Class.prototype.checkDimensions = function(el) { - var duration, height, max_height, max_width, ref, videoHeight, videoWidth, width; + var duration, height, max_height, max_width, videoHeight, videoWidth, width; if (el.tagName === 'IMG') { height = el.height, width = el.width; + this.nodes.el.dataset.height = height; + this.nodes.el.dataset.width = width; if (height > QR.max_height || width > QR.max_width) { this.fileError("Image too large (image: " + height + "x" + width + "px, max: " + QR.max_height + "x" + QR.max_width + "px)"); } @@ -21094,6 +26026,9 @@ QR = (function() { } } else { videoHeight = el.videoHeight, videoWidth = el.videoWidth, duration = el.duration; + this.nodes.el.dataset.height = videoHeight; + this.nodes.el.dataset.width = videoWidth; + this.nodes.el.dataset.duration = duration; max_height = Math.min(QR.max_height, QR.max_height_video); max_width = Math.min(QR.max_width, QR.max_width_video); if (videoHeight > max_height || videoWidth > max_width) { @@ -21107,7 +26042,7 @@ QR = (function() { } else if (duration > QR.max_duration_video) { this.fileError("Video too long (video: " + duration + "s, max: " + QR.max_duration_video + "s)"); } - if (((ref = g.BOARD.ID) !== 'gif' && ref !== 'wsg') && $.hasAudio(el)) { + if (BoardConfig.noAudio(g.BOARD.ID) && $.hasAudio(el)) { return this.fileError('Audio not allowed'); } } @@ -21160,19 +26095,30 @@ QR = (function() { delete this.filesize; this.nodes.el.removeAttribute('title'); QR.nodes.filename.removeAttribute('title'); - this.nodes.el.style.backgroundImage = null; + this.rmMetadata(); + this.nodes.el.style.backgroundImage = ''; $.rmClass(this.nodes.el, 'has-file'); this.showFileData(); URL.revokeObjectURL(this.URL); - return this.dismissErrors(function(error) { + this.dismissErrors(function(error) { return $.hasClass(error, 'file-error'); }); + return this.preventAutoPost(); + }; + + _Class.prototype.rmMetadata = function() { + var attr, i, len, ref; + ref = ['type', 'height', 'width', 'duration']; + for (i = 0, len = ref.length; i < len; i++) { + attr = ref[i]; + this.nodes.el.removeAttribute("data-" + attr); + } }; _Class.prototype.saveFilename = function() { this.file.newName = (this.filename || '').replace(/[\/\\]/g, '-'); if (!QR.validExtension.test(this.filename)) { - return this.file.newName += "." + (QR.extensionFromType[this.file.type] || 'jpg'); + return this.file.newName += "." + ($.getOwn(QR.extensionFromType, this.file.type) || 'jpg'); } }; @@ -21208,6 +26154,7 @@ QR = (function() { _Class.prototype.pasteText = function(file) { var reader; this.pasting = true; + this.preventAutoPost(); reader = new FileReader(); reader.onload = (function(_this) { return function(e) { @@ -21245,7 +26192,7 @@ QR = (function() { }; _Class.prototype.drop = function() { - var el, index, newIndex, oldIndex, post; + var base, el, index, newIndex, oldIndex, post; $.rmClass(this, 'over'); if (!this.draggable) { return; @@ -21256,10 +26203,14 @@ QR = (function() { }; oldIndex = index(el); newIndex = index(this); + if (QR.posts[oldIndex].isLocked || QR.posts[newIndex].isLocked) { + return; + } (oldIndex < newIndex ? $.after : $.before)(this, el); post = QR.posts.splice(oldIndex, 1)[0]; QR.posts.splice(newIndex, 0, post); - return QR.status(); + QR.status(); + return typeof (base = QR.captcha).updateThread === "function" ? base.updateThread() : void 0; }; return _Class; @@ -21272,12 +26223,15 @@ QuoteBacklink = (function() { var QuoteBacklink; QuoteBacklink = { - containers: {}, + containers: $.dict(), init: function() { var ref; if (((ref = g.VIEW) !== 'index' && ref !== 'thread') || !Conf['Quote Backlinks']) { return; } + if ((this.bottomBacklinks = Conf['Bottom Backlinks'])) { + $.addClass(doc, 'bottom-backlinks'); + } Callbacks.Post.push({ name: 'Quote Backlinking Part 1', cb: this.firstNode @@ -21288,17 +26242,13 @@ QuoteBacklink = (function() { }); }, firstNode: function() { - var a, clone, container, containers, hash, i, j, k, len, len1, len2, link, markYours, nodes, post, quote, ref, ref1, ref2; + var a, clone, container, containers, hash, i, j, k, len, len1, len2, link, markYours, nodes, post, quote, ref, ref1; if (this.isClone || !this.quotes.length || this.isRebuilt) { return; } - markYours = Conf['Mark Quotes of You'] && ((ref = QuoteYou.db) != null ? ref.get({ - boardID: this.board.ID, - threadID: this.thread.ID, - postID: this.ID - }) : void 0); + markYours = Conf['Mark Quotes of You'] && QuoteYou.isYou(this); a = $.el('a', { - href: Build.postURL(this.board.ID, this.thread.ID, this.ID), + href: g.SITE.Build.postURL(this.board.ID, this.thread.ID, this.ID), className: this.isHidden ? 'filtered backlink' : 'backlink', textContent: Conf['backlink'].replace(/%(?:id|%)/g, (function(_this) { return function(x) { @@ -21307,16 +26257,19 @@ QuoteBacklink = (function() { '%%': '%' }[x]; }; - })(this)) + (markYours ? '\u00A0(You)' : '') + })(this)) }); - ref1 = this.quotes; - for (i = 0, len = ref1.length; i < len; i++) { - quote = ref1[i]; + if (markYours) { + $.add(a, QuoteYou.mark.cloneNode(true)); + } + ref = this.quotes; + for (i = 0, len = ref.length; i < len; i++) { + quote = ref[i]; containers = [QuoteBacklink.getContainer(quote)]; - if ((post = g.posts[quote]) && post.nodes.backlinkContainer) { - ref2 = post.clones; - for (j = 0, len1 = ref2.length; j < len1; j++) { - clone = ref2[j]; + if ((post = g.posts.get(quote)) && post.nodes.backlinkContainer) { + ref1 = post.clones; + for (j = 0, len1 = ref1.length; j < len1; j++) { + clone = ref1[j]; containers.push(clone.nodes.backlinkContainer); } } @@ -21341,7 +26294,7 @@ QuoteBacklink = (function() { secondNode: function() { var container; if (this.isClone && (this.origin.isReply || Conf['OP Backlinks'])) { - this.nodes.backlinkContainer = $('.container', this.nodes.info); + this.nodes.backlinkContainer = $('.container', this.nodes.post); return; } if (!(this.isReply || Conf['OP Backlinks'])) { @@ -21349,7 +26302,11 @@ QuoteBacklink = (function() { } container = QuoteBacklink.getContainer(this.fullID); this.nodes.backlinkContainer = container; - return $.add(this.nodes.info, container); + if (QuoteBacklink.bottomBacklinks) { + return $.add(this.nodes.post, container); + } else { + return $.add(this.nodes.info, container); + } }, getContainer: function(id) { var base; @@ -21375,7 +26332,10 @@ QuoteCT = (function() { if (Conf['Comment Expansion']) { ExpandComment.callbacks.push(this.node); } - this.text = '\u00A0(Cross-thread)'; + this.mark = $.el('span', { + textContent: '\u00A0(Cross-thread)', + className: 'qmark-ct' + }); return Callbacks.Post.push({ name: 'Mark Cross-thread Quotes', cb: this.node @@ -21395,10 +26355,10 @@ QuoteCT = (function() { continue; } if (this.isClone) { - quotelink.textContent = quotelink.textContent.replace(QuoteCT.text, ''); + $.rm($('.qmark-ct', quotelink)); } if (boardID === board.ID && threadID !== thread.ID) { - $.add(quotelink, $.tn(QuoteCT.text)); + $.add(quotelink, QuoteCT.mark.cloneNode(true)); } } } @@ -21458,11 +26418,14 @@ QuoteInline = (function() { }, toggle: function(e) { var boardID, context, postID, quoter, ref, ref1, threadID; - if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { + if ($.modifiedClick(e)) { return; } ref = Get.postDataFromLink(this), boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID; - if (Conf['Inline Cross-thread Quotes Only'] && g.VIEW === 'thread' && ((ref1 = g.posts[boardID + "." + postID]) != null ? ref1.nodes.root.offsetParent : void 0)) { + if (Conf['Inline Cross-thread Quotes Only'] && g.VIEW === 'thread' && ((ref1 = g.posts.get(boardID + "." + postID)) != null ? ref1.nodes.root.offsetParent : void 0)) { + return; + } + if ($.hasClass(doc, 'catalog-mode')) { return; } e.preventDefault(); @@ -21480,7 +26443,7 @@ QuoteInline = (function() { }, findRoot: function(quotelink, isBacklink) { if (isBacklink) { - return quotelink.parentNode.parentNode; + return $.x('ancestor::*[parent::*[contains(@class,"post")]][1]', quotelink); } else { return $.x('ancestor-or-self::*[parent::blockquote][1]', quotelink); } @@ -21497,7 +26460,7 @@ QuoteInline = (function() { qroot = $.x('ancestor::*[contains(@class,"postContainer")][1]', root); $.addClass(qroot, 'hasInline'); new Fetcher(boardID, threadID, postID, inline, quoter); - if (!((post = g.posts[boardID + "." + postID]) && context.thread === post.thread)) { + if (!((post = g.posts.get(boardID + "." + postID)) && context.thread === post.thread)) { return; } if (isBacklink && Conf['Forward Hiding']) { @@ -21510,21 +26473,23 @@ QuoteInline = (function() { return Unread.readSinglePost(post); }, rm: function(quotelink, boardID, threadID, postID, context) { - var el, inlined, isBacklink, post, qroot, ref, root; + var el, inlined, isBacklink, parentNode, post, qroot, ref, root; isBacklink = $.hasClass(quotelink, 'backlink'); root = QuoteInline.findRoot(quotelink, isBacklink); root = $.x("following-sibling::div[@data-full-i-d='" + boardID + "." + postID + "'][1]", root); qroot = $.x('ancestor::*[contains(@class,"postContainer")][1]', root); + parentNode = root.parentNode; $.rm(root); + $.event('PostsRemoved', null, parentNode); if (!$('.inline', qroot)) { $.rmClass(qroot, 'hasInline'); } if (!(el = root.firstElementChild)) { return; } - post = g.posts[boardID + "." + postID]; + post = g.posts.get(boardID + "." + postID); post.rmClone(el.dataset.clone); - if (Conf['Forward Hiding'] && isBacklink && context.thread === g.threads[boardID + "." + threadID] && !--post.forwarded) { + if (Conf['Forward Hiding'] && isBacklink && context.thread === g.threads.get(boardID + "." + threadID) && !--post.forwarded) { delete post.forwarded; $.rmClass(post.nodes.root, 'forwarded'); } @@ -21553,7 +26518,10 @@ QuoteOP = (function() { if (Conf['Comment Expansion']) { ExpandComment.callbacks.push(this.node); } - this.text = '\u00A0(OP)'; + this.mark = $.el('span', { + textContent: '\u00A0(OP)', + className: 'qmark-op' + }); return Callbacks.Post.push({ name: 'Mark OP Quotes', cb: this.node @@ -21571,7 +26539,7 @@ QuoteOP = (function() { if (this.isClone && (ref = this.thread.fullID, indexOf.call(quotes, ref) >= 0)) { i = 0; while (quotelink = quotelinks[i++]) { - quotelink.textContent = quotelink.textContent.replace(QuoteOP.text, ''); + $.rm($('.qmark-op', quotelink)); } } fullID = this.context.thread.fullID; @@ -21582,7 +26550,7 @@ QuoteOP = (function() { while (quotelink = quotelinks[i++]) { ref1 = Get.postDataFromLink(quotelink), boardID = ref1.boardID, postID = ref1.postID; if ((boardID + "." + postID) === fullID) { - $.add(quotelink, $.tn(QuoteOP.text)); + $.add(quotelink, QuoteOP.mark.cloneNode(true)); } } } @@ -21599,7 +26567,17 @@ QuotePreview = (function() { QuotePreview = { init: function() { var ref; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Quote Previewing'])) { + if (!Conf['Quote Previewing']) { + return; + } + if (g.VIEW === 'archive') { + $.on(d, 'mouseover', function(e) { + if (e.target.nodeName === 'A' && $.hasClass(e.target, 'quotelink')) { + return QuotePreview.mouseover.call(e.target, e); + } + }); + } + if ((ref = g.VIEW) !== 'index' && ref !== 'thread') { return; } if (Conf['Comment Expansion']) { @@ -21620,7 +26598,7 @@ QuotePreview = (function() { }, mouseover: function(e) { var boardID, i, len, origin, post, postID, posts, qp, ref, threadID; - if ($.hasClass(this, 'inlined') || !d.contains(this)) { + if (($.hasClass(this, 'inlined') && !$.hasClass(doc, 'catalog-mode')) || !d.contains(this)) { return; } ref = Get.postDataFromLink(this), boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID; @@ -21637,7 +26615,7 @@ QuotePreview = (function() { endEvents: 'mouseout click', cb: QuotePreview.mouseout }); - if (Conf['Quote Highlighting'] && (origin = g.posts[boardID + "." + postID])) { + if (Conf['Quote Highlighting'] && (origin = g.posts.get(boardID + "." + postID))) { posts = [origin].concat(origin.clones); posts.pop(); for (i = 0, len = posts.length; i < len; i++) { @@ -21651,6 +26629,7 @@ QuotePreview = (function() { if (!(root = this.el.firstElementChild)) { return; } + $.event('PostsRemoved', null, Header.hover); clone = Get.postFromRoot(root); post = clone.origin; post.rmClone(root.dataset.clone); @@ -21692,7 +26671,7 @@ QuoteStrikeThrough = (function() { for (i = 0, len = ref.length; i < len; i++) { quotelink = ref[i]; ref1 = Get.postDataFromLink(quotelink), boardID = ref1.boardID, postID = ref1.postID; - if ((ref2 = g.posts[boardID + "." + postID]) != null ? ref2.isHidden : void 0) { + if ((ref2 = g.posts.get(boardID + "." + postID)) != null ? ref2.isHidden : void 0) { $.addClass(quotelink, 'filtered'); } } @@ -21716,16 +26695,12 @@ QuoteThreading = if (!(Conf['Quote Threading'] && g.VIEW === 'thread')) { return; } - this.controls = $.el('label', { - innerHTML: " Threading" - }); + this.controls = $.el('label', {innerHTML: " Threading"}); this.threadNewLink = $.el('span', { className: 'brackets-wrap threadnewlink', hidden: true }); - $.extend(this.threadNewLink, { - innerHTML: "Thread New Posts" - }); + $.extend(this.threadNewLink, {innerHTML: "Thread New Posts"}); this.input = $('input', this.controls); this.input.checked = Conf['Thread Quotes']; $.on(this.input, 'change', this.setEnabled); @@ -21749,15 +26724,26 @@ QuoteThreading = cb: this.node }); }, - parent: {}, - children: {}, - inserted: {}, + parent: $.dict(), + children: $.dict(), + inserted: $.dict(), + toggleThreading: function() { + return this.setThreadingState(!Conf['Thread Quotes']); + }, + setThreadingState: function(enabled) { + this.input.checked = enabled; + this.setEnabled.call(this.input); + return this.rethread.call(this.input); + }, setEnabled: function() { var other, ref; - other = (ref = ReplyPruning.inputs) != null ? ref.enabled : void 0; - if (this.checked && (other != null ? other.checked : void 0)) { - other.checked = false; - $.event('change', null, other); + if (this.checked) { + $.set('Prune All Threads', false); + other = (ref = ReplyPruning.inputs) != null ? ref.enabled : void 0; + if (other != null ? other.checked : void 0) { + other.checked = false; + $.event('change', null, other); + } } return $.cb.checked.call(this); }, @@ -21782,7 +26768,7 @@ QuoteThreading = ref = this.quotes; for (j = 0, len = ref.length; j < len; j++) { quote = ref[j]; - if (parent = g.posts[quote]) { + if (parent = g.posts.get(quote)) { if (!parent.isFetchedQuote && parent.isReply && parent.ID < this.ID) { parents.add(parent.ID); if (!lastParent || parent.ID > lastParent.ID) { @@ -21890,7 +26876,7 @@ QuoteThreading = } else { nodes = []; Unread.order = new RandomAccessList(); - QuoteThreading.inserted = {}; + QuoteThreading.inserted = $.dict(); posts.forEach(function(post) { if (post.isFetchedQuote) { return; @@ -21906,7 +26892,7 @@ QuoteThreading = return delete post.nodes.threadContainer; } }); - $.add(thread.OP.nodes.root.parentNode, nodes); + $.add(thread.nodes.root, nodes); } Unread.position = Unread.order.first; Unread.updatePosition(); @@ -21934,19 +26920,23 @@ QuoteYou = (function() { return Conf['Remember Your Posts'] = enabled; }); $.on(d, 'QRPostSuccessful', function(e) { - var boardID, postID, ref, threadID; - $.forceSync('Remember Your Posts'); - if (Conf['Remember Your Posts']) { + var cb; + cb = PostRedirect.delay(); + return $.get('Remember Your Posts', Conf['Remember Your Posts'], function(items) { + var boardID, postID, ref, threadID; + if (!items['Remember Your Posts']) { + return; + } ref = e.detail, boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID; return QuoteYou.db.set({ boardID: boardID, threadID: threadID, postID: postID, val: true - }); - } + }, cb); + }); }); - if ((ref = g.VIEW) !== 'index' && ref !== 'thread') { + if ((ref = g.VIEW) !== 'index' && ref !== 'thread' && ref !== 'archive') { return; } if (Conf['Highlight Own Posts']) { @@ -21958,22 +26948,30 @@ QuoteYou = (function() { if (Conf['Comment Expansion']) { ExpandComment.callbacks.push(this.node); } - this.text = '\u00A0(You)'; - return Callbacks.Post.push({ + this.mark = $.el('span', { + textContent: '\u00A0(You)', + className: 'qmark-you' + }); + Callbacks.Post.push({ name: 'Mark Quotes of You', cb: this.node }); + return QuoteYou.menu.init(); + }, + isYou: function(post) { + var ref; + return !!((ref = QuoteYou.db) != null ? ref.get({ + boardID: post.boardID, + threadID: post.threadID, + postID: post.ID + }) : void 0); }, node: function() { var i, len, quotelink, ref; if (this.isClone) { return; } - if (QuoteYou.db.get({ - boardID: this.board.ID, - threadID: this.thread.ID, - postID: this.ID - })) { + if (QuoteYou.isYou(this)) { $.addClass(this.nodes.root, 'yourPost'); } if (!this.quotes.length) { @@ -21986,17 +26984,73 @@ QuoteYou = (function() { continue; } if (Conf['Mark Quotes of You']) { - $.add(quotelink, $.tn(QuoteYou.text)); + $.add(quotelink, QuoteYou.mark.cloneNode(true)); } $.addClass(quotelink, 'you'); $.addClass(this.nodes.root, 'quotesYou'); } }, + menu: { + init: function() { + var input, label, ref; + label = $.el('label', { + className: 'toggle-you' + }, {innerHTML: " You"}); + input = $('input', label); + $.on(input, 'change', QuoteYou.menu.toggle); + return (ref = Menu.menu) != null ? ref.addEntry({ + el: label, + order: 80, + open: function(post) { + QuoteYou.menu.post = post.origin || post; + input.checked = QuoteYou.isYou(post); + return true; + } + }) : void 0; + }, + toggle: function() { + var clone, data, i, j, len, len1, post, quotelink, quoter, ref, ref1; + post = QuoteYou.menu.post; + data = { + boardID: post.board.ID, + threadID: post.thread.ID, + postID: post.ID, + val: true + }; + if (this.checked) { + QuoteYou.db.set(data); + } else { + QuoteYou.db["delete"](data); + } + ref = [post].concat(post.clones); + for (i = 0, len = ref.length; i < len; i++) { + clone = ref[i]; + clone.nodes.root.classList.toggle('yourPost', this.checked); + } + ref1 = Get.allQuotelinksLinkingTo(post); + for (j = 0, len1 = ref1.length; j < len1; j++) { + quotelink = ref1[j]; + if (this.checked) { + if (Conf['Mark Quotes of You']) { + $.add(quotelink, QuoteYou.mark.cloneNode(true)); + } + } else { + $.rm($('.qmark-you', quotelink)); + } + quotelink.classList.toggle('you', this.checked); + if ($.hasClass(quotelink, 'quotelink')) { + quoter = Get.postFromNode(quotelink).nodes.root; + quoter.classList.toggle('quotesYou', !!$('.quotelink.you', quoter)); + } + } + } + }, cb: { seek: function(type) { - var highlight, post, posts, result, str; - if (highlight = $('.highlight')) { - $.rmClass(highlight, 'highlight'); + var highlight, highlighted, post, posts, result, str; + highlight = g.SITE.classes.highlight; + if ((highlighted = $("." + highlight))) { + $.rmClass(highlighted, highlight); } if (!(QuoteYou.lastRead && doc.contains(QuoteYou.lastRead) && $.hasClass(QuoteYou.lastRead, 'quotesYou'))) { if (!(post = QuoteYou.lastRead = $('.quotesYou'))) { @@ -22019,15 +27073,22 @@ QuoteYou = (function() { return QuoteYou.cb.scroll(posts[type === 'following' ? 0 : posts.length - 1]); }, scroll: function(root) { - var post; - post = $('.post', root); - if (!post.getBoundingClientRect().height) { + var node, post, sel; + post = Get.postFromRoot(root); + if (!post.nodes.post.getBoundingClientRect().height) { return false; } else { QuoteYou.lastRead = root; - window.location = "#" + post.id; - Header.scrollTo(post); - $.addClass(post, 'highlight'); + location.href = Get.url('post', post); + Header.scrollTo(post.nodes.post); + if (post.isReply) { + sel = "" + g.SITE.selectors.postContainer + g.SITE.selectors.highlightable.reply; + node = post.nodes.root; + if (!node.matches(sel)) { + node = $(sel, node); + } + $.addClass(node, g.SITE.classes.highlight); + } return true; } } @@ -22049,6 +27110,7 @@ Quotify = (function() { if (((ref = g.VIEW) !== 'index' && ref !== 'thread') || !Conf['Resurrect Quotes']) { return; } + $.addClass(doc, 'resurrect-quotes'); if (Conf['Comment Expansion']) { ExpandComment.callbacks.push(this.node); } @@ -22075,16 +27137,16 @@ Quotify = (function() { } }, parseArchivelink: function(link) { - var boardID, m, postID, threadID; + var boardID, m, postID, ref, threadID; if (!(m = link.pathname.match(/^\/([^\/]+)\/thread\/S?(\d+)\/?$/))) { return; } - if (link.hostname === 'boards.4chan.org') { + if ((ref = link.hostname) === 'boards.4chan.org' || ref === 'boards.4channel.org') { return; } boardID = m[1]; threadID = m[2]; - postID = link.hash.match(/^#p?(\d+)$|$/)[1] || threadID; + postID = link.hash.match(/^#[pq]?(\d+)$|$/)[1] || threadID; if (Redirect.to('post', { boardID: boardID, postID: postID @@ -22114,19 +27176,20 @@ Quotify = (function() { } boardID = (m = quote.match(/^>>>\/([a-z\d]+)/)) ? m[1] : this.board.ID; quoteID = boardID + "." + postID; - if (post = g.posts[quoteID]) { + if (post = g.posts.get(quoteID)) { if (!post.isDead) { a = $.el('a', { - href: Build.postURL(boardID, post.thread.ID, postID), + href: g.SITE.Build.postURL(boardID, post.thread.ID, postID), className: 'quotelink', textContent: quote }); } else { a = $.el('a', { - href: Build.postURL(boardID, post.thread.ID, postID), + href: g.SITE.Build.postURL(boardID, post.thread.ID, postID), className: 'quotelink deadlink', - textContent: quote + "\u00A0(Dead)" + textContent: quote }); + $.add(a, Post.deadMark.cloneNode(true)); $.extend(a.dataset, { boardID: boardID, threadID: post.thread.ID, @@ -22147,8 +27210,9 @@ Quotify = (function() { a = $.el('a', { href: redirect || 'javascript:;', className: 'deadlink', - textContent: quote + "\u00A0(Dead)" + textContent: quote }); + $.add(a, Post.deadMark.cloneNode(true)); if (fetchable) { $.addClass(a, 'quotelink'); $.extend(a.dataset, { @@ -22162,7 +27226,7 @@ Quotify = (function() { this.quotes.push(quoteID); } if (!a) { - deadlink.textContent = quote + "\u00A0(Dead)"; + $.add(deadlink, Post.deadMark.cloneNode(true)); return; } $.replace(deadlink, a); @@ -22188,35 +27252,36 @@ Quotify = (function() { }).call(this); Main = (function() { - var Main; + var Main, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; Main = { init: function() { - var db, flatten, items, j, key, len, ref; - if (d.body && !$('title', d.head)) { - return; - } - if (window['4chan X antidup']) { - return; - } - window['4chan X antidup'] = true; - if (location.hostname === 'www.google.com') { - $.get('Captcha Fixes', true, function(arg1) { - var enabled; - enabled = arg1['Captcha Fixes']; - if (enabled) { - return $.ready(function() { - return Captcha.fixes.init(); - }); - } - }); - return; - } + var db, flatten, i, items, j, k, key, len, mountedCB, ref, ref1, ref2, w; + try { + w = window; + if ($.platform === 'crx') { + w = w.wrappedJSObject || w; + } + if ('4chan X antidup' in w) { + return; + } + w['4chan X antidup'] = true; + } catch (error1) {} try { - if (window.frameElement && window.frameElement.src === '') { + if (window.frameElement && ((ref = window.frameElement.src) === '' || ref === 'about:blank')) { return; } - } catch (_error) {} + } catch (error1) {} + if (doc && $.hasClass(doc, 'fourchan-x')) { + return; + } + $.asap(docSet, function() { + $.addClass(doc, 'fourchan-x', 'seaweedchan'); + if ($.engine) { + return $.addClass(doc, "ua-" + $.engine); + } + }); $.on(d, '4chanXInitFinished', function() { if (Main.expectInitFinished) { return delete Main.expectInitFinished; @@ -22225,10 +27290,25 @@ Main = (function() { return $.addClass(doc, 'tainted'); } }); + mountedCB = function() { + var cb, j, len, ref1, results; + d.removeEventListener('mounted', mountedCB, true); + Main.isMounted = true; + ref1 = Main.mountedCBs; + results = []; + for (j = 0, len = ref1.length; j < len; j++) { + cb = ref1[j]; + try { + results.push(cb()); + } catch (error1) {} + } + return results; + }; + d.addEventListener('mounted', mountedCB, true); flatten = function(parent, obj) { var key, val; if (obj instanceof Array) { - Conf[parent] = obj[0]; + Conf[parent] = $.dict.clone(obj[0]); } else if (typeof obj === 'object') { for (key in obj) { val = obj[key]; @@ -22238,77 +27318,95 @@ Main = (function() { Conf[parent] = obj; } }; - flatten(null, Config); - ref = DataBoard.keys; - for (j = 0, len = ref.length; j < len; j++) { - db = ref[j]; - Conf[db] = { - boards: {} - }; + if ((ref1 = location.hostname) === 'boards.4chan.org' || ref1 === 'boards.4channel.org') { + $.global(function() { + var fromCharCode0; + fromCharCode0 = String.fromCharCode; + return String.fromCharCode = function() { + if (document.body) { + String.fromCharCode = fromCharCode0; + } else if (document.currentScript && !document.currentScript.src) { + throw Error(); + } + return fromCharCode0.apply(this, arguments); + }; + }); + $.asap(docSet, function() { + return $.onExists(doc, 'iframe[srcdoc]', $.rm); + }); } + flatten(null, Config); + ref2 = DataBoard.keys; + for (j = 0, len = ref2.length; j < len; j++) { + db = ref2[j]; + Conf[db] = $.dict(); + } + Conf['customTitles'] = $.dict.clone({ + '4chan.org': { + boards: { + 'qa': { + 'boardTitle': { + orig: '/qa/ - Question & Answer', + title: '/qa/ - 2D/Random' + } + } + } + } + }); + Conf['boardConfig'] = { + boards: $.dict() + }; Conf['archives'] = Redirect.archives; - Conf['selectedArchives'] = {}; - Conf['cooldowns'] = {}; - Conf['Index Sort'] = {}; + Conf['selectedArchives'] = $.dict(); + Conf['cooldowns'] = $.dict(); + Conf['Index Sort'] = $.dict(); + for (i = k = 0; k < 2; i = ++k) { + Conf["Last Long Reply Thresholds " + i] = $.dict(); + } + Conf['siteProperties'] = $.dict(); Conf['Except Archives from Encryption'] = false; Conf['JSON Navigation'] = true; Conf['Oekaki Links'] = true; Conf['Show Name and Subject'] = false; Conf['QR Shortcut'] = true; Conf['Bottom QR Link'] = true; - if ($.platform === 'crx') { - $.global(function() { - var k, key, len1, oldFun, ref1, whitelist; - whitelist = document.currentScript.dataset.whitelist; - whitelist = whitelist.split('\n').filter(function(x) { - return x[0] !== "'"; - }); - whitelist.push(location.protocol + "//" + location.host); - oldFun = {}; - ref1 = ['createElement', 'write']; - for (k = 0, len1 = ref1.length; k < len1; k++) { - key = ref1[k]; - oldFun[key] = document[key]; - document[key] = (function(key) { - return function(arg) { - var s; - s = document.currentScript; - if (s && s.src && whitelist.indexOf(s.src.split('/').slice(0, 3).join('/')) < 0) { - throw Error(); - } - return oldFun[key].call(document, arg); - }; - })(key); + Conf['Toggleable Thread Watcher'] = true; + Conf['siteSoftware'] = ''; + Conf['Use Faster Image Host'] = 'true'; + Conf['Captcha Fixes'] = true; + Conf['captchaServiceDomain'] = ''; + Conf['captchaServiceKey'] = $.dict(); + if (/\.4chan(?:nel)?\.org$/.test(location.hostname) && !SW.yotsuba.regexp.pass.test(location.href) && !SW.yotsuba.regexp.captcha.test(location.href) && !$$('script:not([src])', d).filter(function(s) { + return /this\[/.test(s.textContent); + }).length) { + ($.getSync || $.get)({ + 'jsWhitelist': Conf['jsWhitelist'] + }, function(arg) { + var jsWhitelist, parsedList; + jsWhitelist = arg.jsWhitelist; + parsedList = jsWhitelist.replace(/^#.*$/mg, '').replace(/[\s;]+/g, ' ').trim(); + if (/\S/.test(parsedList)) { + return $.addCSP("script-src " + parsedList); } - return document.addEventListener('csp-ready', function() { - var results; - results = []; - for (key in oldFun) { - results.push(document[key] = oldFun[key]); - } - return results; - }, false); - }, { - whitelist: Conf['jsWhitelist'] }); } - items = {}; + items = $.dict(); for (key in Conf) { items[key] = void 0; } items['previousversion'] = void 0; return ($.getSync || $.get)(items, function(items) { - var jsWhitelist, ref1; - jsWhitelist = (ref1 = items['jsWhitelist']) != null ? ref1 : Conf['jsWhitelist']; - $.addCSP("script-src " + (jsWhitelist.replace(/[\s;]+/g, ' '))); - if ($.platform === 'crx') { - $.event('csp-ready'); + var ref3; + if (!$.perProtocolSettings && /\.4chan(?:nel)?\.org$/.test(location.hostname) && ((ref3 = items['Redirect to HTTPS']) != null ? ref3 : Conf['Redirect to HTTPS']) && location.protocol !== 'https:') { + location.replace('https://' + location.host + location.pathname + location.search + location.hash); + return; } return $.asap(docSet, function() { - var ref2, val; + var ref4, val; if ($.cantSet) { } else if (items.previousversion == null) { + Main.isFirstRun = true; Main.ready(function() { $.set('previousversion', g.VERSION); return Settings.open(); @@ -22318,9 +27416,9 @@ Main = (function() { } for (key in Conf) { val = Conf[key]; - Conf[key] = (ref2 = items[key]) != null ? ref2 : val; + Conf[key] = (ref4 = items[key]) != null ? ref4 : val; } - return Main.initFeatures(); + return Site.init(Main.initFeatures); }); }); }, @@ -22332,100 +27430,105 @@ Main = (function() { return $.set(changes, function() { var el, ref; if ((ref = items['Show Updated Notifications']) != null ? ref : true) { - el = $.el('span', { - innerHTML: "4chan X has been updated to version " + E(g.VERSION) + "." - }); + el = $.el('span', {innerHTML: "4chan X has been updated to version " + E(g.VERSION) + "."}); return new Notice('info', el, 15); } }); }, + parseURL: function(site, url) { + var pathname, r, ref; + if (site == null) { + site = g.SITE; + } + if (url == null) { + url = location; + } + r = {}; + if (!site) { + return r; + } + r.siteID = site.ID; + if (typeof site.isBoardlessPage === "function" ? site.isBoardlessPage(url) : void 0) { + return r; + } + pathname = url.pathname.split(/\/+/); + r.boardID = pathname[1]; + if (site.isFileURL(url)) { + r.VIEW = 'file'; + } else if (typeof site.isAuxiliaryPage === "function" ? site.isAuxiliaryPage(url) : void 0) { + + } else if ((ref = pathname[2]) === 'thread' || ref === 'res') { + r.VIEW = 'thread'; + r.threadID = r.THREADID = +pathname[3].replace(/\.\w+$/, ''); + } else if (pathname[2] === 'archive' && pathname[3] === 'res') { + r.VIEW = 'thread'; + r.threadID = r.THREADID = +pathname[4].replace(/\.\w+$/, ''); + r.threadArchived = true; + } else if (/^(?:catalog|archive)(?:\.\w+)?$/.test(pathname[2])) { + r.VIEW = pathname[2].replace(/\.\w+$/, ''); + } else if (/^(?:index|\d*)(?:\.\w+)?$/.test(pathname[2])) { + r.VIEW = 'index'; + } + return r; + }, initFeatures: function() { - var err, feature, hostname, j, len, match, name, pathname, ref, ref1, ref2, ref3, search; - hostname = location.hostname, search = location.search; - pathname = location.pathname.split(/\/+/); - if (hostname !== 'www.4chan.org') { - g.BOARD = new Board(pathname[1]); + var base, err, feature, j, len, name, ref, ref1; + $.global(function() { + document.documentElement.classList.add('js-enabled'); + return window.FCX = {}; + }); + Main.jsEnabled = $.hasClass(doc, 'js-enabled'); + if (typeof $.ajaxPageInit === "function") { + $.ajaxPageInit(); } - if (hostname === 'boards.4chan.org' || hostname === 'sys.4chan.org' || hostname === 'www.4chan.org') { - $.global(function() { - document.documentElement.classList.add('js-enabled'); - return window.FCX = {}; - }); - Main.jsEnabled = $.hasClass(doc, 'js-enabled'); + $.extend(g, Main.parseURL()); + if (g.boardID) { + g.BOARD = new Board(g.boardID); } - switch (hostname) { - case 'www.4chan.org': - $.onExists(doc, 'body', function() { - return $.addStyle(CSS.www); - }); - Captcha.replace.init(); - return; - case 'sys.4chan.org': - if (pathname[2] === 'imgboard.php') { - if (/\bmode=report\b/.test(search)) { - Report.init(); - } else if ((match = search.match(/\bres=(\d+)/))) { - $.ready(function() { - var ref; - if (Conf['404 Redirect'] && ((ref = $.id('errmsg')) != null ? ref.textContent : void 0) === 'Error: Specified thread does not exist.') { - return Redirect.navigate('thread', { - boardID: g.BOARD.ID, - postID: +match[1] - }); - } - }); + if (!g.VIEW) { + if (typeof (base = g.SITE).initAuxiliary === "function") { + base.initAuxiliary(); + } + return; + } + if (g.VIEW === 'file') { + $.asap((function() { + return d.readyState !== 'loading'; + }), function() { + var base1, pathname, video; + if (g.SITE.software === 'yotsuba' && Conf['404 Redirect'] && (typeof (base1 = g.SITE).is404 === "function" ? base1.is404() : void 0)) { + pathname = location.pathname.split(/\/+/); + return Redirect.navigate('file', { + boardID: g.BOARD.ID, + filename: pathname[pathname.length - 1] + }); + } else if (video = $('video')) { + if (Conf['Volume in New Tab']) { + Volume.setup(video); } - } else if (pathname[2] === 'post') { - PostSuccessful.init(); - } - return; - case 'i.4cdn.org': - if (!(pathname[2] && !/s\.jpg$/.test(pathname[2]))) { - return; - } - $.asap((function() { - return d.readyState !== 'loading'; - }), function() { - var ref, video; - if (Conf['404 Redirect'] && ((ref = d.title) === '4chan - Temporarily Offline' || ref === '4chan - 404 Not Found')) { - return Redirect.navigate('file', { - boardID: g.BOARD.ID, - filename: pathname[pathname.length - 1] - }); - } else if (video = $('video')) { - if (Conf['Volume in New Tab']) { - Volume.setup(video); - } - if (Conf['Loop in New Tab']) { - video.loop = true; - video.controls = false; - video.play(); - return ImageCommon.addControls(video); - } + if (Conf['Loop in New Tab']) { + video.loop = true; + video.controls = false; + video.play(); + return ImageCommon.addControls(video); } - }); - return; - } - if ((ref = pathname[2]) === 'thread' || ref === 'res') { - g.VIEW = 'thread'; - g.THREADID = +pathname[3]; - } else if ((ref1 = pathname[2]) === 'catalog' || ref1 === 'archive') { - g.VIEW = pathname[2]; - } else if (pathname[2].match(/^\d*$/)) { - g.VIEW = 'index'; - } else { + } + }); return; } g.threads = new SimpleDict(); g.posts = new SimpleDict(); $.onExists(doc, 'body', Main.initStyle); - ref2 = Main.features; - for (j = 0, len = ref2.length; j < len; j++) { - ref3 = ref2[j], name = ref3[0], feature = ref3[1]; + ref = Main.features; + for (j = 0, len = ref.length; j < len; j++) { + ref1 = ref[j], name = ref1[0], feature = ref1[1]; + if (g.SITE.disabledFeatures && indexOf.call(g.SITE.disabledFeatures, name) >= 0) { + continue; + } try { feature.init(); - } catch (_error) { - err = _error; + } catch (error1) { + err = error1; Main.handleErrors({ message: "\"" + name + "\" initialization crashed.", error: err @@ -22442,13 +27545,11 @@ Main = (function() { if ((ref = $('link[href*=mobile]', d.head)) != null) { ref.disabled = true; } - $.addClass(doc, 'fourchan-x', 'seaweedchan'); + doc.dataset.host = location.host; + $.addClass(doc, "sw-" + g.SITE.software); $.addClass(doc, g.VIEW === 'thread' ? 'thread-view' : g.VIEW); - if ($.engine) { - $.addClass(doc, "ua-" + $.engine); - } - $.onExists(doc, '.ad-cnt', function(ad) { - return $.onExists(ad, 'img', function() { + $.onExists(doc, '.ad-cnt, .adg-rects > .desktop', function(ad) { + return $.onExists(ad, 'img, iframe', function() { return $.addClass(doc, 'ads-loaded'); }); }); @@ -22462,7 +27563,7 @@ Main = (function() { return $.toggleClass(doc, 'autohiding-scrollbar'); } }); - $.addStyle(CSS.boards, 'fourchanx-css'); + $.addStyle(CSS.sub(CSS.boards), 'fourchanx-css'); Main.bgColorStyle = $.el('style', { id: 'fourchanx-bgcolor-css' }); @@ -22481,135 +27582,152 @@ Main = (function() { return Main.setClass(); }, setClass: function() { - var mainStyleSheet, setStyle, style, styleSheets; - if (g.VIEW === 'catalog') { - $.addClass(doc, $.id('base-css').href.match(/catalog_(\w+)/)[1].replace('_new', '').replace(/_+/g, '-')); - return; + var j, knownStyles, len, mainStyleSheet, ref, ref1, setStyle, style, styleSheet, styleSheets; + knownStyles = ['yotsuba', 'yotsuba-b', 'futaba', 'burichan', 'photon', 'tomorrow', 'spooky']; + if (g.SITE.software === 'yotsuba' && g.VIEW === 'catalog') { + if ((mainStyleSheet = $.id('base-css'))) { + style = (ref = mainStyleSheet.href.match(/catalog_(\w+)/)) != null ? ref[1].replace('_new', '').replace(/_+/g, '-') : void 0; + if (indexOf.call(knownStyles, style) >= 0) { + $.addClass(doc, style); + return; + } + } } - style = 'yotsuba-b'; - mainStyleSheet = $('link[title=switch]', d.head); - styleSheets = $$('link[rel="alternate stylesheet"]', d.head); + style = mainStyleSheet = styleSheets = null; setStyle = function() { - var bgColor, div, j, len, styleSheet; - $.rmClass(doc, style); - style = null; - for (j = 0, len = styleSheets.length; j < len; j++) { - styleSheet = styleSheets[j]; - if (styleSheet.href === (mainStyleSheet != null ? mainStyleSheet.href : void 0)) { - style = styleSheet.title.toLowerCase().replace('new', '').trim().replace(/\s+/g, '-'); - break; + var bgColor, css, div, j, len, rgb, s, styleSheet; + if (g.SITE.software === 'yotsuba') { + $.rmClass(doc, style); + style = null; + for (j = 0, len = styleSheets.length; j < len; j++) { + styleSheet = styleSheets[j]; + if (styleSheet.href === (mainStyleSheet != null ? mainStyleSheet.href : void 0)) { + style = styleSheet.title.toLowerCase().replace('new', '').trim().replace(/\s+/g, '-'); + if (style === '_special') { + style = styleSheet.href.match(/[a-z]*(?=[^\/]*$)/)[0]; + } + if (indexOf.call(knownStyles, style) < 0) { + style = null; + } + break; + } + } + if (style) { + $.addClass(doc, style); + $.rm(Main.bgColorStyle); + return; } } - if (style) { - $.addClass(doc, style); - return $.rm(Main.bgColorStyle); - } else { - div = $.el('div', { - className: 'reply' - }); - div.style.cssText = 'position: absolute; visibility: hidden;'; - $.add(d.body, div); - bgColor = window.getComputedStyle(div).backgroundColor; - $.rm(div); - Main.bgColorStyle.textContent = ".dialog, .suboption-list > div:last-of-type {\n background-color: " + bgColor + ";\n}"; - return $.after($.id('fourchanx-css'), Main.bgColorStyle); + div = g.SITE.bgColoredEl(); + div.style.position = 'absolute'; + div.style.visibility = 'hidden'; + $.add(d.body, div); + bgColor = window.getComputedStyle(div).backgroundColor; + $.rm(div); + rgb = bgColor.match(/[\d.]+/g); + if (!/^rgb\(/.test(bgColor)) { + s = window.getComputedStyle(d.body); + bgColor = s.backgroundColor + " " + s.backgroundImage + " " + s.backgroundRepeat + " " + s.backgroundPosition; + } + css = ".dialog, .suboption-list > div:last-of-type, :root.catalog-hover-expand .catalog-container:hover > .post {\n background: " + bgColor + ";\n}\n.unread-mark-read {\n background-color: rgba(" + (rgb.slice(0, 3).join(', ')) + ", " + (0.5 * (rgb[3] || 1)) + ");\n}"; + if ($.luma(rgb) < 100) { + css += ".watch-thread-link {\n background-image: url(\"data:image/svg+xml,\");\n}"; } + Main.bgColorStyle.textContent = css; + return $.after($.id('fourchanx-css'), Main.bgColorStyle); }; - setStyle(); + $.onExists(d.head, g.SITE.selectors.styleSheet, function(el) { + mainStyleSheet = el; + if (g.SITE.software === 'yotsuba') { + styleSheets = $$('link[rel="alternate stylesheet"]', d.head); + } + new MutationObserver(setStyle).observe(mainStyleSheet, { + attributes: true, + attributeFilter: ['href'] + }); + $.on(mainStyleSheet, 'load', setStyle); + return setStyle(); + }); if (!mainStyleSheet) { - return; + ref1 = $$('link[rel="stylesheet"]', d.head); + for (j = 0, len = ref1.length; j < len; j++) { + styleSheet = ref1[j]; + $.on(styleSheet, 'load', setStyle); + } + return setStyle(); } - return new MutationObserver(setStyle).observe(mainStyleSheet, { - attributes: true, - attributeFilter: ['href'] - }); }, initReady: function() { - var msg, ref, ref1, ref2; - if (g.VIEW === 'thread' && (((ref = d.title) === '4chan - Temporarily Offline' || ref === '4chan - 404 Not Found') || ($('.board') && !$('.opContainer')))) { - ThreadWatcher.set404(g.BOARD.ID, g.THREADID, function() { - if (Conf['404 Redirect']) { - return Redirect.navigate('thread', { - boardID: g.BOARD.ID, - threadID: g.THREADID, - postID: +location.hash.match(/\d+/) - }, "/" + g.BOARD + "/"); - } - }); - return; - } - if ((ref1 = d.title) === '4chan - Temporarily Offline' || ref1 === '4chan - 404 Not Found') { + var base, base1, msg; + if (typeof (base = g.SITE).is404 === "function" ? base.is404() : void 0) { + if (g.VIEW === 'thread') { + ThreadWatcher.set404(g.BOARD.ID, g.THREADID, function() { + if (Conf['404 Redirect']) { + return Redirect.navigate('thread', { + boardID: g.BOARD.ID, + threadID: g.THREADID, + postID: +location.hash.match(/\d+/) + }, "/" + g.BOARD + "/"); + } + }); + } return; } - if (((ref2 = g.VIEW) === 'index' || ref2 === 'thread') && !$('.board + *')) { - msg = $.el('div', { - innerHTML: "The page didn't load completely.
      Some features may not work unless you reload." - }); + if (typeof (base1 = g.SITE).isIncomplete === "function" ? base1.isIncomplete() : void 0) { + msg = $.el('div', {innerHTML: "The page didn't load completely.
      Some features may not work unless you reload."}); $.on($('a', msg), 'click', function() { return location.reload(); }); new Notice('warning', msg); } - if (!(Conf['JSON Index'] && g.VIEW === 'index')) { - return Main.initThread(); + if (g.VIEW === 'catalog') { + return Main.initCatalog(); + } else if (!Index.enabled) { + if (g.SITE.awaitBoard) { + return g.SITE.awaitBoard(Main.initThread); + } else { + return Main.initThread(); + } } else { Main.expectInitFinished = true; return $.event('4chanXInitFinished'); } }, initThread: function() { - var board, err, errors, j, k, len, len1, m, postRoot, posts, ref, ref1, scriptData, thread, threadRoot, threads; - if ((board = $('.board'))) { + var base, base1, board, errors, posts, ref, s, threads; + s = g.SITE.selectors; + if ((board = $(((ref = s.boardFor) != null ? ref[g.VIEW] : void 0) || s.board))) { threads = []; posts = []; - ref = $$('.board > .thread', board); - for (j = 0, len = ref.length; j < len; j++) { - threadRoot = ref[j]; - thread = new Thread(+threadRoot.id.slice(1), g.BOARD); - threads.push(thread); - ref1 = $$('.thread > .postContainer', threadRoot); - for (k = 0, len1 = ref1.length; k < len1; k++) { - postRoot = ref1[k]; - if ($('.postMessage', postRoot)) { - try { - posts.push(new Post(postRoot, thread, g.BOARD)); - } catch (_error) { - err = _error; - if (!errors) { - errors = []; - } - errors.push({ - message: "Parsing of Post No." + (postRoot.id.match(/\d+/)) + " failed. Post will be skipped.", - error: err - }); - } - } + errors = []; + try { + if (typeof (base = g.SITE).preParsingFixes === "function") { + base.preParsingFixes(board); } - } - if (errors) { + } catch (error1) {} + Main.addThreadsObserver = new MutationObserver(Main.addThreads); + Main.addPostsObserver = new MutationObserver(Main.addPosts); + Main.addThreadsObserver.observe(board, { + childList: true + }); + Main.parseThreads($$(s.thread, board), threads, posts, errors); + if (errors.length) { Main.handleErrors(errors); } if (g.VIEW === 'thread') { - scriptData = Get.scriptData(); - threads[0].postLimit = /\bbumplimit *= *1\b/.test(scriptData); - threads[0].fileLimit = /\bimagelimit *= *1\b/.test(scriptData); - threads[0].ipCount = (m = scriptData.match(/\bunique_ips *= *(\d+)\b/)) ? +m[1] : void 0; - } - if (g.BOARD.ID === 'f' && g.VIEW === 'thread') { - $.ajax("//a.4cdn.org/f/thread/" + g.THREADID + ".json", { - timeout: $.MINUTE, - onloadend: function() { - if (this.response && posts[0].file) { - return posts[0].file.text.dataset.md5 = posts[0].file.MD5 = this.response.posts[0].md5; - } - } - }); + if (g.threadArchived) { + threads[0].isArchived = true; + threads[0].kill(); + } + if (typeof (base1 = g.SITE).parseThreadMetadata === "function") { + base1.parseThreadMetadata(threads[0]); + } } Main.callbackNodes('Thread', threads); return Main.callbackNodesDB('Post', posts, function() { - var l, len2, post; - for (l = 0, len2 = posts.length; l < len2; l++) { - post = posts[l]; + var j, len, post; + for (j = 0, len = posts.length; j < len; j++) { + post = posts[j]; QuoteThreading.insert(post); } Main.expectInitFinished = true; @@ -22620,6 +27738,189 @@ Main = (function() { return $.event('4chanXInitFinished'); } }, + parseThreads: function(threadRoots, threads, posts, errors) { + var boardID, boardObj, j, len, postRoots, ref, thread, threadID, threadRoot; + for (j = 0, len = threadRoots.length; j < len; j++) { + threadRoot = threadRoots[j]; + boardObj = (boardID = threadRoot.dataset.board) ? (boardID = encodeURIComponent(boardID), g.boards[boardID] || new Board(boardID)) : g.BOARD; + threadID = +threadRoot.id.match(/\d*$/)[0]; + if (!threadID || ((ref = boardObj.threads.get(threadID)) != null ? ref.nodes.root : void 0)) { + return; + } + thread = new Thread(threadID, boardObj); + thread.nodes.root = threadRoot; + threads.push(thread); + postRoots = $$(g.SITE.selectors.postContainer, threadRoot); + if (g.SITE.isOPContainerThread) { + postRoots.unshift(threadRoot); + } + Main.parsePosts(postRoots, thread, posts, errors); + Main.addPostsObserver.observe(threadRoot, { + childList: true + }); + } + }, + parsePosts: function(postRoots, thread, posts, errors) { + var err, j, len, postRoot; + for (j = 0, len = postRoots.length; j < len; j++) { + postRoot = postRoots[j]; + if (!(postRoot.dataset.fullID && g.posts.get(postRoot.dataset.fullID)) && $(g.SITE.selectors.comment, postRoot)) { + try { + posts.push(new Post(postRoot, thread, thread.board)); + } catch (error1) { + err = error1; + errors.push({ + message: "Parsing of Post No." + (postRoot.id.match(/\d+/)) + " failed. Post will be skipped.", + error: err, + html: postRoot.outerHTML + }); + } + } + } + }, + addThreads: function(records) { + var errors, j, k, len, len1, node, posts, record, ref, threadRoots, threads; + threadRoots = []; + for (j = 0, len = records.length; j < len; j++) { + record = records[j]; + ref = record.addedNodes; + for (k = 0, len1 = ref.length; k < len1; k++) { + node = ref[k]; + if (node.nodeType === Node.ELEMENT_NODE && node.matches(g.SITE.selectors.thread)) { + threadRoots.push(node); + } + } + } + if (!threadRoots.length) { + return; + } + threads = []; + posts = []; + errors = []; + Main.parseThreads(threadRoots, threads, posts, errors); + if (errors.length) { + Main.handleErrors(errors); + } + Main.callbackNodes('Thread', threads); + return Main.callbackNodesDB('Post', posts, function() { + return $.event('PostsInserted', null, records[0].target); + }); + }, + addPosts: function(records) { + var anyRemoved, el, errors, j, k, l, len, len1, len2, n, node, postRoots, posts, record, ref, ref1, ref2, thread, threads, threadsRM; + threads = []; + threadsRM = []; + posts = []; + errors = []; + for (j = 0, len = records.length; j < len; j++) { + record = records[j]; + thread = Get.threadFromRoot(record.target); + postRoots = []; + ref = record.addedNodes; + for (k = 0, len1 = ref.length; k < len1; k++) { + node = ref[k]; + if (node.nodeType === Node.ELEMENT_NODE) { + if (node.matches(g.SITE.selectors.postContainer) || (node = $(g.SITE.selectors.postContainer, node))) { + postRoots.push(node); + } + } + } + n = posts.length; + Main.parsePosts(postRoots, thread, posts, errors); + if (posts.length > n && indexOf.call(threads, thread) < 0) { + threads.push(thread); + } + anyRemoved = false; + ref1 = record.removedNodes; + for (l = 0, len2 = ref1.length; l < len2; l++) { + el = ref1[l]; + if (((ref2 = Get.postFromRoot(el)) != null ? ref2.nodes.root : void 0) === el && !doc.contains(el)) { + anyRemoved = true; + break; + } + } + if (anyRemoved && indexOf.call(threadsRM, thread) < 0) { + threadsRM.push(thread); + } + } + if (errors.length) { + Main.handleErrors(errors); + } + return Main.callbackNodesDB('Post', posts, function() { + var len3, len4, m, o; + for (m = 0, len3 = threads.length; m < len3; m++) { + thread = threads[m]; + $.event('PostsInserted', null, thread.nodes.root); + } + for (o = 0, len4 = threadsRM.length; o < len4; o++) { + thread = threadsRM[o]; + $.event('PostsRemoved', null, thread.nodes.root); + } + }); + }, + initCatalog: function() { + var board, errors, s, threads; + s = g.SITE.selectors.catalog; + if (s && (board = $(s.board))) { + threads = []; + errors = []; + Main.addCatalogThreadsObserver = new MutationObserver(Main.addCatalogThreads); + Main.addCatalogThreadsObserver.observe(board, { + childList: true + }); + Main.parseCatalogThreads($$(s.thread, board), threads, errors); + if (errors.length) { + Main.handleErrors(errors); + } + Main.callbackNodes('CatalogThreadNative', threads); + } + Main.expectInitFinished = true; + return $.event('4chanXInitFinished'); + }, + parseCatalogThreads: function(threadRoots, threads, errors) { + var err, j, len, ref, thread, threadRoot; + for (j = 0, len = threadRoots.length; j < len; j++) { + threadRoot = threadRoots[j]; + try { + thread = new CatalogThreadNative(threadRoot); + if (((ref = thread.thread.catalogViewNative) != null ? ref.nodes.root : void 0) !== threadRoot) { + thread.thread.catalogViewNative = thread; + threads.push(thread); + } + } catch (error1) { + err = error1; + errors.push({ + message: "Parsing of Catalog Thread No." + ((threadRoot.dataset.id || threadRoot.id).match(/\d+/)) + " failed. Thread will be skipped.", + error: err, + html: threadRoot.outerHTML + }); + } + } + }, + addCatalogThreads: function(records) { + var errors, j, k, len, len1, node, record, ref, threadRoots, threads; + threadRoots = []; + for (j = 0, len = records.length; j < len; j++) { + record = records[j]; + ref = record.addedNodes; + for (k = 0, len1 = ref.length; k < len1; k++) { + node = ref[k]; + if (node.nodeType === Node.ELEMENT_NODE && node.matches(g.SITE.selectors.catalog.thread)) { + threadRoots.push(node); + } + } + } + if (!threadRoots.length) { + return; + } + threads = []; + errors = []; + Main.parseCatalogThreads(threadRoots, threads, errors); + if (errors.length) { + Main.handleErrors(errors); + } + return Main.callbackNodes('CatalogThreadNative', threads); + }, callbackNodes: function(klass, nodes) { var cb, i, node; i = 0; @@ -22655,11 +27956,21 @@ Main = (function() { return softTask(); }, handleErrors: function(errors) { - var div, error, j, len, logs; + var div, enabled, error, j, len, logs, msg; if (d.body && $.hasClass(d.body, 'fourchan_x') && !$.hasClass(doc, 'tainted')) { new Notice('error', 'Error: Multiple copies of 4chan X are enabled.'); $.addClass(doc, 'tainted'); } + if (g.SITE.testNativeExtension && !$.hasClass(doc, 'tainted')) { + enabled = g.SITE.testNativeExtension().enabled; + if (enabled) { + $.addClass(doc, 'tainted'); + if (Conf['Disable Native Extension'] && !Main.isFirstRun) { + msg = $.el('div', {innerHTML: "Failed to disable the native extension. You may need to block it."}); + new Notice('error', msg); + } + } + } if (!(errors instanceof Array)) { error = errors; } else if (errors.length === 1) { @@ -22669,9 +27980,7 @@ Main = (function() { new Notice('error', Main.parseError(error, Main.reportLink([error])), 15); return; } - div = $.el('div', { - innerHTML: E(errors.length) + " errors occurred." + (Main.reportLink(errors)).innerHTML + " [show]" - }); + div = $.el('div', {innerHTML: E(errors.length) + " errors occurred." + (Main.reportLink(errors)).innerHTML + " [show]"}); $.on(div.lastElementChild, 'click', function() { var ref; return ref = this.textContent === 'show' ? ['hide', false] : ['show', true], this.textContent = ref[0], logs.hidden = ref[1], ref; @@ -22688,9 +27997,7 @@ Main = (function() { parseError: function(data, reportLink) { var context, error, lines, message, ref, ref1; c.error(data.message, data.error.stack); - message = $.el('div', { - innerHTML: E(data.message) + ((reportLink) ? (reportLink).innerHTML : "") - }); + message = $.el('div', {innerHTML: E(data.message) + ((reportLink) ? (reportLink).innerHTML : "")}); error = $.el('div', { textContent: (data.error.name || 'Error') + ": " + (data.error.message || 'see console for details') }); @@ -22701,7 +28008,7 @@ Main = (function() { return [message, error, context]; }, reportLink: function(errors) { - var addDetails, data, details, title, url; + var addDetails, data, details, info, title, url; data = errors[0]; title = data.message; if (errors.length > 1) { @@ -22709,11 +28016,14 @@ Main = (function() { } details = ''; addDetails = function(text) { - if (!(encodeURIComponent(title + details + text + '\n').length > 8110)) { + if (!(encodeURIComponent(title + details + text + '\n').length > 8143)) { return details += text + '\n'; } }; - addDetails("[Please describe the steps needed to reproduce this error.]\n\nScript: 4chan X ccd0 v" + g.VERSION + " " + $.platform + "\nUser agent: " + navigator.userAgent + "\nURL: " + location.href); + addDetails("[Please describe the steps needed to reproduce this error.]\n\nScript: 4chan X ccd0 v" + g.VERSION + " " + $.platform + "\nURL: " + location.href + "\nUser agent: " + navigator.userAgent); + if ($.platform === 'userscript' && (info = typeof GM !== "undefined" && GM !== null ? GM.info : (typeof GM_info !== "undefined" && GM_info !== null ? GM_info : void 0))) { + addDetails("Userscript manager: " + info.scriptHandler + " " + info.version); + } addDetails('\n' + data.error); if (data.error.stack) { addDetails(data.error.stack.replace(data.error.toString(), '').trim()); @@ -22722,15 +28032,12 @@ Main = (function() { addDetails('\n`' + data.html + '`'); } details = details.replace(/file:\/{3}.+\//g, ''); - url = "https://gitreports.com/issue/ccd0/4chan-x?issue_title=" + (encodeURIComponent(title)) + "&details=" + (encodeURIComponent(details)); - return { - innerHTML: " [report]" - }; + url = 'https://github.com/ccd0/4chan-x/issues'.replace('%title', encodeURIComponent(title)).replace('%details', encodeURIComponent(details)); + return {innerHTML: " [report]"}; }, isThisPageLegit: function() { - var ref; if (!('thisPageIsLegit' in Main)) { - Main.thisPageIsLegit = location.hostname === 'boards.4chan.org' && !$('link[href*="favicon-status.ico"]', d.head) && ((ref = d.title) !== '4chan - Temporarily Offline' && ref !== '4chan - Error' && ref !== '504 Gateway Time-out'); + Main.thisPageIsLegit = g.SITE.isThisPageLegit ? g.SITE.isThisPageLegit() : !/^[45]\d\d\b/.test(document.title) && !/\.(?:json|rss)$/.test(location.pathname); } return Main.thisPageIsLegit; }, @@ -22741,7 +28048,15 @@ Main = (function() { } }); }, - features: [['Polyfill', Polyfill], ['Normalize URL', NormalizeURL], ['Captcha Configuration', Captcha.replace], ['Redirect', Redirect], ['Header', Header], ['Catalog Links', CatalogLinks], ['Settings', Settings], ['Index Generator', Index], ['Disable Autoplay', AntiAutoplay], ['Announcement Hiding', PSAHiding], ['Fourchan thingies', Fourchan], ['Color User IDs', IDColor], ['Highlight by User ID', IDHighlight], ['Custom CSS', CustomCSS], ['Thread Links', ThreadLinks], ['Linkify', Linkify], ['Reveal Spoilers', RemoveSpoilers], ['Resurrect Quotes', Quotify], ['Filter', Filter], ['Thread Hiding Buttons', ThreadHiding], ['Reply Hiding Buttons', PostHiding], ['Recursive', Recursive], ['Strike-through Quotes', QuoteStrikeThrough], ['Quick Reply Personas', QR.persona], ['Quick Reply', QR], ['Cooldown', QR.cooldown], ['Pass Link', PassLink], ['Menu', Menu], ['Index Generator (Menu)', Index.menu], ['Report Link', ReportLink], ['Thread Hiding (Menu)', ThreadHiding.menu], ['Reply Hiding (Menu)', PostHiding.menu], ['Delete Link', DeleteLink], ['Filter (Menu)', Filter.menu], ['Edit Link', QR.oekaki.menu], ['Download Link', DownloadLink], ['Archive Link', ArchiveLink], ['Quote Inlining', QuoteInline], ['Quote Previewing', QuotePreview], ['Quote Backlinks', QuoteBacklink], ['Mark Quotes of You', QuoteYou], ['Mark OP Quotes', QuoteOP], ['Mark Cross-thread Quotes', QuoteCT], ['Anonymize', Anonymize], ['Time Formatting', Time], ['Relative Post Dates', RelativeDates], ['File Info Formatting', FileInfo], ['Fappe Tyme', FappeTyme], ['Gallery', Gallery], ['Gallery (menu)', Gallery.menu], ['Sauce', Sauce], ['Image Expansion', ImageExpand], ['Image Expansion (Menu)', ImageExpand.menu], ['Reveal Spoiler Thumbnails', RevealSpoilers], ['Image Loading', ImageLoader], ['Image Hover', ImageHover], ['Volume Control', Volume], ['WEBM Metadata', Metadata], ['Comment Expansion', ExpandComment], ['Thread Expansion', ExpandThread], ['Favicon', Favicon], ['Unread', Unread], ['Quote Threading', QuoteThreading], ['Thread Stats', ThreadStats], ['Thread Updater', ThreadUpdater], ['Thread Watcher', ThreadWatcher], ['Thread Watcher (Menu)', ThreadWatcher.menu], ['Mark New IPs', MarkNewIPs], ['Index Navigation', Nav], ['Keybinds', Keybinds], ['Banner', Banner], ['Flash Features', Flash], ['Reply Pruning', ReplyPruning]] + mounted: function(cb) { + if (Main.isMounted) { + return cb(); + } else { + return Main.mountedCBs.push(cb); + } + }, + mountedCBs: [], + features: [['Polyfill', Polyfill], ['Board Configuration', BoardConfig], ['Normalize URL', NormalizeURL], ['Delay Redirect on Post', PostRedirect], ['Captcha Configuration', Captcha.replace], ['Image Host Rewriting', ImageHost], ['Redirect', Redirect], ['Header', Header], ['Catalog Links', CatalogLinks], ['Settings', Settings], ['Index Generator', Index], ['Disable Autoplay', AntiAutoplay], ['Announcement Hiding', PSAHiding], ['Fourchan thingies', Fourchan], ['Tinyboard Glue', Tinyboard], ['Color User IDs', IDColor], ['Highlight by User ID', IDHighlight], ['Count Posts by ID', IDPostCount], ['Custom CSS', CustomCSS], ['Thread Links', ThreadLinks], ['Linkify', Linkify], ['Reveal Spoilers', RemoveSpoilers], ['Resurrect Quotes', Quotify], ['Filter', Filter], ['Thread Hiding Buttons', ThreadHiding], ['Reply Hiding Buttons', PostHiding], ['Recursive', Recursive], ['Strike-through Quotes', QuoteStrikeThrough], ['Quick Reply Personas', QR.persona], ['Quick Reply', QR], ['Cooldown', QR.cooldown], ['Post Jumper', PostJumper], ['Pass Link', PassLink], ['Menu', Menu], ['Index Generator (Menu)', Index.menu], ['Report Link', ReportLink], ['Copy Text Link', CopyTextLink], ['Thread Hiding (Menu)', ThreadHiding.menu], ['Reply Hiding (Menu)', PostHiding.menu], ['Delete Link', DeleteLink], ['Filter (Menu)', Filter.menu], ['Edit Link', QR.oekaki.menu], ['Download Link', DownloadLink], ['Archive Link', ArchiveLink], ['Quote Inlining', QuoteInline], ['Quote Previewing', QuotePreview], ['Quote Backlinks', QuoteBacklink], ['Mark Quotes of You', QuoteYou], ['Mark OP Quotes', QuoteOP], ['Mark Cross-thread Quotes', QuoteCT], ['Anonymize', Anonymize], ['Time Formatting', Time], ['Relative Post Dates', RelativeDates], ['File Info Formatting', FileInfo], ['Fappe Tyme', FappeTyme], ['Gallery', Gallery], ['Gallery (menu)', Gallery.menu], ['Sauce', Sauce], ['Image Expansion', ImageExpand], ['Image Expansion (Menu)', ImageExpand.menu], ['Reveal Spoiler Thumbnails', RevealSpoilers], ['Image Loading', ImageLoader], ['Image Hover', ImageHover], ['Volume Control', Volume], ['WEBM Metadata', Metadata], ['Comment Expansion', ExpandComment], ['Thread Expansion', ExpandThread], ['Favicon', Favicon], ['Unread', Unread], ['Unread Line in Index', UnreadIndex], ['Quote Threading', QuoteThreading], ['Thread Stats', ThreadStats], ['Thread Updater', ThreadUpdater], ['Thread Watcher', ThreadWatcher], ['Thread Watcher (Menu)', ThreadWatcher.menu], ['Mark New IPs', MarkNewIPs], ['Index Navigation', Nav], ['Keybinds', Keybinds], ['Banner', Banner], ['Announcements', PSA], ['Flash Features', Flash], ['Reply Pruning', ReplyPruning], ['Mod Contact Links', ModContact]] }; return Main; diff --git a/builds/4chan-X-noupdate.crx b/builds/4chan-X-noupdate.crx index 3f404d92b1..ca2a2fb2f0 100644 Binary files a/builds/4chan-X-noupdate.crx and b/builds/4chan-X-noupdate.crx differ diff --git a/builds/4chan-X-noupdate.user.js b/builds/4chan-X-noupdate.user.js index 79936bf2bd..6966879b34 100644 --- a/builds/4chan-X-noupdate.user.js +++ b/builds/4chan-X-noupdate.user.js @@ -1,10 +1,10 @@ // ==UserScript== // @name 4chan X -// @version 1.12.0.0 +// @version 1.14.23.1 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X -// @description Cross-browser userscript for maximum lurking on 4chan. +// @description 4chan X is a script that adds various features to anonymous imageboards. // @license MIT; https://github.com/ccd0/4chan-x/blob/master/LICENSE // @include http://boards.4chan.org/* // @include https://boards.4chan.org/* @@ -12,17 +12,85 @@ // @include https://sys.4chan.org/* // @include http://www.4chan.org/* // @include https://www.4chan.org/* +// @include http://boards.4channel.org/* +// @include https://boards.4channel.org/* +// @include http://sys.4channel.org/* +// @include https://sys.4channel.org/* +// @include http://www.4channel.org/* +// @include https://www.4channel.org/* // @include http://i.4cdn.org/* // @include https://i.4cdn.org/* -// @include https://www.google.com/recaptcha/api2/anchor?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @include https://www.google.com/recaptcha/api2/frame?*&k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @include http://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @include https://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @exclude http://www.4chan.org/pass -// @exclude https://www.4chan.org/pass -// @exclude http://www.4chan.org/pass?* -// @exclude https://www.4chan.org/pass?* -// @connect i.4cdn.org +// @include http://is.4chan.org/* +// @include https://is.4chan.org/* +// @include http://is2.4chan.org/* +// @include https://is2.4chan.org/* +// @include http://is.4channel.org/* +// @include https://is.4channel.org/* +// @include http://is2.4channel.org/* +// @include https://is2.4channel.org/* +// @include https://erischan.org/* +// @include https://www.erischan.org/* +// @include https://fufufu.moe/* +// @include https://gnfos.com/* +// @include https://himasugi.blog/* +// @include https://www.himasugi.blog/* +// @include https://kakashinenpo.com/* +// @include https://www.kakashinenpo.com/* +// @include https://kissu.moe/* +// @include https://www.kissu.moe/* +// @include https://lainchan.org/* +// @include https://www.lainchan.org/* +// @include https://merorin.com/* +// @include https://ota-ch.com/* +// @include https://www.ota-ch.com/* +// @include https://ponyville.us/* +// @include https://www.ponyville.us/* +// @include https://smuglo.li/* +// @include https://notso.smuglo.li/* +// @include https://smugloli.net/* +// @include https://smug.nepu.moe/* +// @include https://sportschan.org/* +// @include https://www.sportschan.org/* +// @include https://sushigirl.us/* +// @include https://www.sushigirl.us/* +// @include https://tvch.moe/* +// @exclude http://www.4chan.org/advertise +// @exclude https://www.4chan.org/advertise +// @exclude http://www.4chan.org/advertise?* +// @exclude https://www.4chan.org/advertise?* +// @exclude http://www.4chan.org/donate +// @exclude https://www.4chan.org/donate +// @exclude http://www.4chan.org/donate?* +// @exclude https://www.4chan.org/donate?* +// @exclude http://www.4channel.org/advertise +// @exclude https://www.4channel.org/advertise +// @exclude http://www.4channel.org/advertise?* +// @exclude https://www.4channel.org/advertise?* +// @exclude http://www.4channel.org/donate +// @exclude https://www.4channel.org/donate +// @exclude http://www.4channel.org/donate?* +// @exclude https://www.4channel.org/donate?* +// @connect 4chan.org +// @connect 4channel.org +// @connect 4cdn.org +// @connect 4chenz.github.io +// @connect archive.4plebs.org +// @connect warosu.org +// @connect desuarchive.org +// @connect boards.fireden.net +// @connect arch.b4k.co +// @connect archived.moe +// @connect thebarchive.com +// @connect archiveofsins.com +// @connect archive.palanq.win +// @connect eientei.xyz +// @connect api.clyp.it +// @connect api.dailymotion.com +// @connect api.github.com +// @connect soundcloud.com +// @connect api.streamable.com +// @connect vimeo.com +// @connect www.youtube.com // @connect * // @grant GM_getValue // @grant GM_setValue @@ -31,6 +99,12 @@ // @grant GM_addValueChangeListener // @grant GM_openInTab // @grant GM_xmlhttpRequest +// @grant GM.getValue +// @grant GM.setValue +// @grant GM.deleteValue +// @grant GM.listValues +// @grant GM.openInTab +// @grant GM.xmlHttpRequest // @run-at document-start // @updateURL https://noupdate.invalid/ // @downloadURL https://noupdate.invalid/ @@ -121,11 +195,11 @@ 'use strict'; -var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, Build, CSS, Callbacks, Captcha, CatalogLinks, CatalogThread, Config, Connection, CrossOrigin, CustomCSS, DataBoard, DeleteLink, DownloadLink, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, Fetcher, FileInfo, Filter, Flash, Fourchan, Gallery, Get, Header, IDColor, IDHighlight, ImageCommon, ImageExpand, ImageHover, ImageLoader, Index, Keybinds, Linkify, Main, MarkNewIPs, Menu, Metadata, Nav, NormalizeURL, Notice, PSAHiding, PassLink, Polyfill, Post, PostHiding, PostSuccessful, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, ReplyPruning, Report, ReportLink, RevealSpoilers, Sauce, Settings, SimpleDict, Thread, ThreadHiding, ThreadLinks, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, Volume; +var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, BoardConfig, CSS, Callbacks, Captcha, CatalogLinks, CatalogThread, CatalogThreadNative, Config, Connection, CopyTextLink, CrossOrigin, CustomCSS, DataBoard, DeleteLink, DownloadLink, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, Fetcher, FileInfo, Filter, Flash, Fourchan, Gallery, Get, Header, IDColor, IDHighlight, IDPostCount, ImageCommon, ImageExpand, ImageHost, ImageHover, ImageLoader, Index, Keybinds, Linkify, Main, MarkNewIPs, Menu, Metadata, ModContact, Nav, NormalizeURL, Notice, PSA, PSAHiding, PassLink, PassMessage, Polyfill, Post, PostHiding, PostJumper, PostRedirect, PostSuccessful, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, ReplyPruning, Report, ReportLink, RevealSpoilers, SW, Sauce, Settings, ShimSet, SimpleDict, Site, Test, Thread, ThreadHiding, ThreadLinks, ThreadStats, ThreadUpdater, ThreadWatcher, Time, Tinyboard, UI, Unread, UnreadIndex, Volume; var Conf, E, c, d, doc, docSet, g; -Conf = {}; +Conf = Object.create(null); c = console; d = document; doc = d.documentElement; @@ -136,9 +210,10 @@ docSet = function() { }; g = { - VERSION: '1.12.0.0', + VERSION: '1.14.23.1', NAMESPACE: '4chan X.', - boards: {} + sites: Object.create(null), + boards: Object.create(null) }; E = (function() { @@ -169,25 +244,24 @@ E.cat = function(templates) { return html; }; -E.url = function(content) { - return "data:text/html;charset=utf-8," + encodeURIComponent(content.innerHTML); -}; - Config = (function() { var Config; Config = { main: { 'Miscellaneous': { + 'Redirect to HTTPS': [true, 'Redirect to the HTTPS version of 4chan.'], 'JSON Index': [true, 'Replace the original board index with one supporting searching, sorting, infinite scrolling, and a catalog mode.'], 'Use 4chan X Catalog': [true, 'Link to 4chan X\'s catalog instead of the native 4chan one.', 1], 'Index Refresh Notifications': [false, 'Show a notice at the top of the page when the index is refreshed.', 1], + 'Follow Cursor': [true, 'Image Hover and Quote Preview move with the mouse cursor.'], 'Open Threads in New Tab': [false, 'Make links to threads in the index / 4chan X catalog open in a new tab.'], 'External Catalog': [false, 'Link to external catalog instead of the internal one.'], 'Catalog Links': [false, 'Add toggle link in header menu to turn Navigation links into links to each board\'s catalog.'], 'Announcement Hiding': [true, 'Add button to hide 4chan announcements.'], 'Desktop Notifications': [true, 'Enables desktop notifications across various 4chan X features.'], '404 Redirect': [true, 'Redirect dead threads and images to the archives.'], + 'Archive Report': [true, 'Enable reporting posts to supported archives.'], 'Exempt Archives from Encryption': [true, 'Permit loading content from, and warningless redirects to, HTTP-only archives from HTTPS pages.'], 'Keybinds': [true, 'Bind actions to keyboard shortcuts.'], 'Time Formatting': [true, 'Localize and format timestamps.'], @@ -198,13 +272,16 @@ Config = (function() { 'Thread Expansion': [true, 'Add buttons to expand threads.'], 'Index Navigation': [false, 'Add buttons to navigate between threads.'], 'Reply Navigation': [false, 'Add buttons to navigate to top / bottom of thread.'], + 'Unique ID and Capcode Navigation': [false, 'Add buttons to navigate to posts having the same unique ID or capcode.'], 'Custom Board Titles': [true, 'Allow editing of the board title and subtitle by ctrl/\u2318+clicking them.'], 'Persistent Custom Board Titles': [false, 'Force custom board titles to be persistent, even if the board titles are updated.', 1], 'Show Updated Notifications': [true, 'Show notifications when 4chan X is successfully updated.'], 'Color User IDs': [true, 'Assign unique colors to user IDs on boards that use them'], + 'Count Posts by ID': [true, 'Display number of posts in the thread when hovering over an ID.'], 'Remove Spoilers': [false, 'Remove all spoilers in text.'], 'Reveal Spoilers': [false, 'Indicate spoilers if Remove Spoilers is enabled, or make the text appear hovered if Remove Spoiler is disabled.'], 'Normalize URL': [true, 'Rewrite the URL of the current page, removing slugs and excess slashes, and changing /res/ to /thread/.'], + 'Work around CORB Bug': [true, 'Leave this checked until your garbage browser is fixed.'], 'Disable Autoplaying Sounds': [false, 'Prevent sounds on the page from autoplaying.'], 'Disable Native Extension': [true, '4chan X is NOT designed to work with the native extension.'], 'Enable Native Flash Embedding': [true, 'Activate the native extension\'s Flash embedding if the native extension is disabled.'] @@ -212,6 +289,7 @@ Config = (function() { 'Linkification': { 'Linkify': [true, 'Convert text into links where applicable.'], 'Link Title': [true, 'Replace the link of a supported site with its actual title.', 1], + 'Cover Preview': [true, 'Show preview of supported links on hover.', 1], 'Embedding': [true, 'Embed supported services. Note: Some services don\'t work on HTTPS.', 1], 'Auto-embed': [false, 'Auto-embed Linkify Embeds.', 2], 'Floating Embeds': [false, 'Embed content in a frame that remains in place when the page is scrolled.', 2] @@ -220,6 +298,8 @@ Config = (function() { 'Anonymize': [false, 'Make everyone Anonymous.'], 'Filter': [true, 'Self-moderation placebo.'], 'Filtered Backlinks': [false, 'When enabled, shows backlinks to filtered posts with a line-through decoration. Otherwise, hides the backlinks.', 1], + 'Filter in Native Catalog': [true, 'Apply 4chan X filters in native catalog.', 1], + 'MD5 Quick Filter Notifications': [true, 'Show notification when quick filtering MD5s using the button or keybind.', 1], 'Recursive Hiding': [true, 'Hide replies of hidden posts, recursively.'], 'Thread Hiding Buttons': [true, 'Add buttons to hide entire threads.'], 'Reply Hiding Buttons': [true, 'Add buttons to hide single replies.'], @@ -228,8 +308,8 @@ Config = (function() { 'Images and Videos': { 'Image Expansion': [true, 'Expand images / videos.'], 'Image Hover': [true, 'Show full image / video on mouseover.'], - 'Image Hover in Catalog': [false, 'Show full image / video on mouseover in 4chan X catalog.'], - 'Gallery': [true, 'Adds a simple and cute image gallery.'], + 'Image Hover in Catalog': [true, 'Show full image / video on mouseover in 4chan X catalog.'], + 'Gallery': [true, 'Adds a simple and cute image gallery. Has more options in the gallery menu.'], 'Fullscreen Gallery': [false, 'Open gallery in fullscreen mode.', 1], 'PDF in Gallery': [false, 'Show PDF files in gallery.', 1], 'Sauce': [true, 'Add sauce links to images.'], @@ -238,11 +318,12 @@ Config = (function() { 'Replace GIF': [false, 'Replace gif thumbnails with the actual image.'], 'Replace JPG': [false, 'Replace jpg thumbnails with the actual image.'], 'Replace PNG': [false, 'Replace png thumbnails with the actual image.'], - 'Replace WEBM': [false, 'Replace webm thumbnails with the actual webm video. Probably will degrade browser performance ;)'], - 'Image Prefetching': [false, 'Add link in header menu to turn on image preloading.'], + 'Replace WEBM': [false, 'Replace webm, mp4, and ogv thumbnails with the actual video. Probably will degrade browser performance ;)'], + 'Image Prefetching': [true, 'Add a shortcut icon to the header to turn on image preloading.'], 'Fappe Tyme': [true, 'Hide posts without images when header menu item is checked. *hint* *hint*'], 'Werk Tyme': [true, 'Hide all post images when header menu item is checked.'], 'Autoplay': [true, 'Videos begin playing immediately when opened.'], + 'Restart when Opened': [false, 'Restart GIFs and WebMs when you hover over or expand them.'], 'Show Controls': [true, 'Show controls on videos expanded inline.'], 'Click Passthrough': [false, 'Clicks on videos trigger your browser\'s default behavior. Videos can be contracted with button / dragging to the left.', 1], 'Allow Sound': [true, 'Open videos with the sound unmuted.'], @@ -253,12 +334,13 @@ Config = (function() { 'Menu': { 'Menu': [true, 'Add a drop-down menu to posts.'], 'Report Link': [true, 'Add a report link to the menu.', 1], + 'Copy Text Link': [true, 'Add a link to copy the post\'s text.', 1], 'Thread Hiding Link': [true, 'Add a link to hide entire threads.', 1], 'Reply Hiding Link': [true, 'Add a link to hide single replies.', 1], 'Delete Link': [true, 'Add post and image deletion links to the menu.', 1], 'Archive Link': [true, 'Add an archive link to the menu.', 1], 'Edit Link': [true, 'Add a link to edit the image in Tegaki, /i/\'s painting program. Requires Quick Reply.', 1], - 'Download Link': [true, 'Add a download with original filename link to the menu.', 1] + 'Download Link': [false, 'Add a download with original filename link to the menu.', 1] }, 'Monitoring': { 'Thread Updater': [true, 'Fetch and insert new replies. Has more options in the header menu and the "Advanced" tab.'], @@ -269,20 +351,24 @@ Config = (function() { 'Unread Line': [true, 'Show a line to distinguish read posts from unread ones.'], 'Remember Last Read Post': [true, 'Remember how far you\'ve read after you close the thread.'], 'Scroll to Last Read Post': [true, 'Scroll back to the last read post when reopening a thread.', 1], + 'Unread Line in Index': [false, 'Show a line between read and unread posts in threads in the index.', 1], + 'Remove Thread Excerpt': [false, 'Replace the excerpt of the thread in the tab title with the board title.'], 'Thread Stats': [true, 'Display reply and image count.'], 'IP Count in Stats': [true, 'Display the unique IP count in the thread stats.', 1], 'Page Count in Stats': [true, 'Display the page count in the thread stats.', 1], 'Updater and Stats in Header': [true, 'Places the thread updater and thread stats in the header instead of floating them.'], - 'Thread Watcher': [true, 'Bookmark threads.'], + 'Thread Watcher': [true, 'Bookmark threads. Has more options in the thread watcher menu.'], 'Fixed Thread Watcher': [true, 'Makes the thread watcher scroll with the page.', 1], - 'Toggleable Thread Watcher': [true, 'Adds a shortcut for the thread watcher and hides the watcher by default.', 1], + 'Persistent Thread Watcher': [false, 'The thread watcher will be visible when the page is loaded.', 1], 'Mark New IPs': [false, 'Label each post from a new IP with the thread\'s current IP count.'], - 'Reply Pruning': [true, 'Hide old replies in long threads. Number of replies shown can be set from header menu.'] + 'Reply Pruning': [true, 'Add option in header menu to hide old replies in long threads. Activated by default in stickies.'], + 'Prune All Threads': [false, 'Activate Reply Pruning by default in all threads.', 1] }, 'Posting and Captchas': { 'Quick Reply': [true, 'All-in-one form to reply, create threads, automate dumping and more.'], 'Persistent QR': [false, 'The Quick reply won\'t disappear after posting.', 1], - 'Auto Hide QR': [true, 'Automatically hide the quick reply when posting.', 1], + 'Auto Hide QR': [true, 'Automatically hide the quick reply when posting.', 2], + 'Open Post in New Tab': [true, 'Open new threads in a new tab, and open replies in a new tab if you\'re not already in the thread.', 1], 'Remember QR Size': [false, 'Remember the size of the Quick reply.', 1], 'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.', 1], 'Randomize Filename': [false, 'Set the filename to a random timestamp within the past year. Disabled on /f/.', 1], @@ -292,15 +378,13 @@ Config = (function() { 'Posting Success Notifications': [true, 'Show notifications on successful post creation or file uploading.', 1], 'Auto-load captcha': [false, 'Automatically load the captcha in the QR even if your post is empty.', 1], 'Post on Captcha Completion': [false, 'Submit the post immediately when the captcha is completed.', 1], - 'Captcha Fixes': [true, 'Make captcha easier to use, especially with the keyboard.'], - 'Use Recaptcha v1': [false, 'Use the old text version of Recaptcha in the post form.'], - 'Use Recaptcha v1 in Reports': [false, 'Use the text captcha in the report window.'], - 'Force Noscript Captcha': [false, 'Use the non-Javascript fallback captcha even if Javascript is enabled (Recaptcha v2 only).'], + 'Force Noscript Captcha': [false, 'Use the non-Javascript fallback captcha even if Javascript is enabled.'], 'Pass Link': [false, 'Add a 4chan Pass login link to the bottom of the page.'] }, 'Quote Links': { 'Quote Backlinks': [true, 'Add quote backlinks.'], 'OP Backlinks': [true, 'Add backlinks to the OP.', 1], + 'Bottom Backlinks': [false, 'Place backlinks at the bottom of posts.', 1], 'Quote Inlining': [true, 'Inline quoted post on click.'], 'Inline Cross-thread Quotes Only': [false, 'Don\'t inline quote links when the posts are visible in the thread.', 1], 'Quote Hash Navigation': [false, 'Include an extra link after quotes for autoscrolling to quoted posts.', 1], @@ -324,6 +408,7 @@ Config = (function() { 'Expand spoilers': [true, 'Expand all images along with spoilers.'], 'Expand videos': [true, 'Expand all images also expands videos.'], 'Expand from here': [false, 'Expand all images only from current position to thread end.'], + 'Expand thread only': [false, 'In index, expand all images only within the current thread.'], 'Advance on contract': [false, 'Advance to next post when contracting an expanded image.'] }, gallery: { @@ -338,17 +423,23 @@ Config = (function() { threadWatcher: { 'Current Board': [false, 'Only show watched threads from the current board.'], 'Auto Update Thread Watcher': [true, 'Periodically check status of watched threads.'], - 'Auto Watch': [false, 'Automatically watch threads you start.'], - 'Auto Watch Reply': [false, 'Automatically watch threads you reply to.'], + 'Auto Watch': [true, 'Automatically watch threads you start.'], + 'Auto Watch Reply': [true, 'Automatically watch threads you reply to.'], 'Auto Prune': [false, 'Automatically remove dead threads.'], - 'Show Unread Count': [true, 'Show number of unread posts in watched threads.'] + 'Show Page': [true, 'Show what page watched threads are on.'], + 'Show Unread Count': [true, 'Show number of unread posts in watched threads.'], + 'Show Site Prefix': [true, 'When multiple sites are shown in the thread watcher, add a prefix to board names to distinguish them.'], + 'Require OP Quote Link': [false, 'For purposes of thread watcher highlighting, only consider posts with a quote link to the OP as replies to the OP.'] }, filter: { + general: '', postID: "# Highlight dubs on [s4s]:\n#/(\\d)\\1$/;highlight;top:no;boards:s4s", name: "# Filter any namefags:\n#/^(?!Anonymous$)/", uniqueID: "# Filter a specific ID:\n#/Txhvk1Tl/", tripcode: "# Filter any tripfag\n#/^!/", capcode: "# Set a custom class for mods:\n#/Mod$/;highlight:mod;op:yes\n# Set a custom class for admins:\n#/Admin$/;highlight:admin;op:yes", + pass: "# Filter anyone using since4pass:\n#/./", + email: '', subject: "# Filter Generals on /v/:\n#/general/i;boards:v;op:only", comment: "# Filter Stallman copypasta on /g/:\n#/what you\'re refer+ing to as linux/i;boards:g\n# Filter posts with 20 or more quote links:\n#/(?:>>\\d(?:(?!>>\\d)[^])*){20}/\n# Filter posts like T H I S / H / I / S:\n#/^>?\\s?\\w\\s?(\\w)\\s?(\\w)\\s?(\\w).*$[\\s>]+\\1[\\s>]+\\2[\\s>]+\\3/im", flag: '', @@ -357,7 +448,7 @@ Config = (function() { filesize: '', MD5: '' }, - sauces: "# Reverse image search:\nhttps://www.google.com/searchbyimage?image_url=%IMG&safe=off\n#https://www.yandex.com/images/search?rpt=imageview&img_url=%IMG\n#//tineye.com/search?url=%IMG\n\n# Specialized reverse image search:\n//iqdb.org/?url=%IMG\nhttps://whatanime.ga/?auto&url=%IMG;text:wait\n#//3d.iqdb.org/?url=%IMG\n#//saucenao.com/search.php?url=%IMG\n\n# \"View Same\" in archives:\nhttp://eye.swfchan.com/search/?q=%name;types:swf\n#https://desustorage.org/_/search/image/%sMD5/\n#https://archive.4plebs.org/_/search/image/%sMD5/\n#https://boards.fireden.net/_/search/image/%sMD5/\n#https://foolz.fireden.net/_/search/image/%sMD5/\n\n# Other tools:\n#http://regex.info/exif.cgi?imgurl=%URL\n#//imgops.com/%URL;types:gif,jpg,png\n#//www.gif-explode.com/%URL;types:gif", + sauces: "# Known filename formats:\nhttps://www.pixiv.net/member_illust.php?mode=medium&illust_id=%$1;regexp:/^(\\d+)_p\\d+/\njavascript:void(open(\"https://www.deviantart.com/\"+%$1.replace(/_/g,\"-\")+\"/art/\"+parseInt(%$2,36)));regexp:/^\\w+_by_(\\w+)[_-]d([\\da-z]{6})\\b/\nhttps://imgur.com/%$1;regexp:/^(?![a-zA-Z][a-z]{6})(?![A-Z]{7})(?!\\d{7})([\\da-zA-Z]{7})(?: \\(\\d+\\))?\\.\\w+$/\nhttps://flickr.com/photo.gne?id=%$1;regexp:/^(\\d+)_[\\da-f]{10}(?:_\\w)*\\b/\nhttps://www.facebook.com/photo.php?fbid=%$1;regexp:/^\\d+_(\\d+)_\\d+_[no]\\b/\n\n# Reverse image search:\nhttps://www.google.com/searchbyimage?sbisrc=4chanx&image_url=%IMG&safe=off\nhttps://yandex.com/images/search?rpt=imageview&url=%IMG\n#//tineye.com/search?url=%IMG\n#//www.bing.com/images/search?q=imgurl:%IMG&view=detailv2&iss=sbi#enterInsights\n#https://lens.google.com/uploadbyurl?url=%IMG;text:lens\n\n# Specialized reverse image search:\n//iqdb.org/?url=%IMG\nhttps://trace.moe/?auto&url=%IMG;text:wait\n#//3d.iqdb.org/?url=%IMG\n#//saucenao.com/search.php?url=%IMG\n\n# \"View Same\" in archives:\nhttp://eye.swfchan.com/search/?q=%name;types:swf\n#https://desuarchive.org/_/search/image/%sMD5/\n#https://archive.4plebs.org/_/search/image/%sMD5/\n#https://boards.fireden.net/_/search/image/%sMD5/\n#https://foolz.fireden.net/_/search/image/%sMD5/\n\n# Other tools:\n#http://exif.regex.info/exif.cgi?imgurl=%URL\n#//imgops.com/start?url=%URL;types:gif,jpg,png\n#//www.gif-explode.com/%URL;types:gif", FappeT: { werk: false }, @@ -366,10 +457,12 @@ Config = (function() { 'Index Mode': 'paged', 'Previous Index Mode': 'paged', 'Index Size': 'small', - 'Show Replies': true, - 'Pin Watched Threads': false, - 'Anchor Hidden Threads': true, - 'Refreshed Navigation': false + 'Show Replies': [true, 'Show replies in the index, and also in the catalog if "Catalog hover expand" is checked.'], + 'Catalog Hover Expand': [false, 'Expand the comment and show more details when you hover over a thread in the catalog.'], + 'Catalog Hover Toggle': [true, 'Turn "Catalog hover expand" on and off by clicking in the catalog.'], + 'Pin Watched Threads': [false, 'Move watched threads to the start of the index.'], + 'Anchor Hidden Threads': [true, 'Move hidden threads to the end of the index.'], + 'Refreshed Navigation': [false, 'Refresh index when navigating through pages.'] }, Header: { 'Fixed Header': true, @@ -383,20 +476,23 @@ Config = (function() { 'Custom Board Navigation': true }, archives: { - archiveLists: 'https://mayhemydg.github.io/archives.json/archives.json', + archiveLists: 'https://4chenz.github.io/archives.json/archives.json', lastarchivecheck: 0, archiveAutoUpdate: true }, - boardnav: "[ toggle-all ]\na-replace\nc-replace\ng-replace\nk-replace\nv-replace\nvg-replace\nvr-replace\nck-replace\nco-replace\nfit-replace\njp-replace\nmu-replace\nsp-replace\ntv-replace\nvp-replace\n[external-text:\"FAQ\",\"https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions\"]", + externalCatalogURLs: "//catalog.neet.tv/%board/;boards:4chan.org: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", + boardnav: "[ toggle-all ]\n[current-index-text:\"Index\"\ncurrent-catalog-text:\"Catalog\"\ncurrent-expired-text:\"Expired\"\ncurrent-archive-text:\"Archive\"]\n[external-text:\"FAQ\",\"https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions\"]", QR: { 'QR.personas': "#options:\"sage\";boards:jp;always", sjisPreview: false }, - jsWhitelist: 'http://s.4cdn.org\nhttps://s.4cdn.org\nhttp://www.google.com\nhttps://www.google.com\nhttps://www.gstatic.com\nhttp://cdn.mathjax.org\nhttps://cdn.mathjax.org\n\'self\'\n\'unsafe-inline\'\n\'unsafe-eval\'', + jsWhitelist: '', captchaLanguage: '', time: '%m/%d/%y(%a)%H:%M:%S', + timeLocale: '', backlink: '>>%id', - fileInfo: '%l (%p%s, %r%g)', + pastedname: 'file', + fileInfo: '%l %d (%p%s, %r%g)', favicon: 'ferongr', usercss: "/* Board title rice */\ndiv.boardTitle {\n font-weight: 400 !important;\n}\n:root.yotsuba div.boardTitle {\n font-family: sans-serif !important;\n text-shadow: 1px 1px 1px rgba(100,0,0,0.6);\n}\n:root.yotsuba-b div.boardTitle {\n font-family: sans-serif !important;\n text-shadow: 1px 1px 1px rgba(105,10,15,0.6);\n}\n:root.photon div.boardTitle {\n font-family: sans-serif !important;\n text-shadow: 1px 1px 1px rgba(0,74,153,0.6);\n}\n:root.tomorrow div.boardTitle {\n font-family: sans-serif !important;\n text-shadow: 1px 1px 1px rgba(167,170,168,0.6);\n}\n", hotkeys: { @@ -412,15 +508,27 @@ Config = (function() { 'Math tags': ['Alt+m', 'Insert math tags.'], 'SJIS tags': ['Alt+a', 'Insert SJIS tags.'], 'Toggle sage': ['Alt+s', 'Toggle sage in options field.'], + 'Toggle Cooldown': ['Alt+Comma', 'Toggle custom cooldown timer.'], + 'Post from URL': ['Alt+l', 'Post from URL.'], + 'Add new post': ['Alt+n', 'Add new post to the QR dump list.'], 'Submit QR': ['Ctrl+Enter', 'Submit post.'], 'Watch': ['w', 'Watch thread.'], 'Update': ['r', 'Update the thread / refresh the index.'], 'Update thread watcher': ['Shift+r', 'Manually refresh thread watcher.'], + 'Toggle thread watcher': ['t', 'Toggle visibility of thread watcher.'], + 'Toggle threading': ['Shift+t', 'Toggle threading.'], + 'Mark thread read': ['Ctrl+0', 'Mark thread read from index (requires "Unread Line in Index").'], 'Expand image': ['Shift+e', 'Expand selected image.'], 'Expand images': ['e', 'Expand all images.'], 'Open Gallery': ['g', 'Opens the gallery.'], + 'Next Gallery Image': ['Right', 'Go to the next image in gallery mode.'], + 'Previous Gallery Image': ['Left', 'Go to the previous image in gallery mode.'], + 'Advance Gallery': ['Enter', 'Go to next image or, if Autoplay is off, play video.'], 'Pause': ['p', 'Pause/play videos in the gallery.'], 'Slideshow': ['Ctrl+Right', 'Toggle the gallery slideshow mode.'], + 'Rotate image clockwise': ['Shift+Right', 'Rotate image clockwise in gallery.'], + 'Rotate image anticlockwise': ['Shift+Left', 'Rotate image anticlockwise in gallery.'], + 'Download Gallery Image': ['Shift+j', 'Download current image in gallery.'], 'fappeTyme': ['f', 'Toggle Fappe Tyme.'], 'werkTyme': ['Shift+w', 'Toggle Werk Tyme.'], 'Front page': ['1', 'Jump to front page.'], @@ -442,6 +550,7 @@ Config = (function() { 'Previous reply': ['k', 'Select previous reply.'], 'Deselect reply': ['Shift+d', 'Deselect reply.'], 'Hide': ['x', 'Hide thread.'], + 'Quick Filter MD5': ['5', 'Add the MD5 of the selected image to the filter list.'], 'Previous Post Quoting You': ['Alt+Up', 'Scroll to the previous post that quotes you.'], 'Next Post Quoting You': ['Alt+Down', 'Scroll to the next post that quotes you.'] }, @@ -455,13 +564,26 @@ Config = (function() { 'Auto Update': [true, 'Automatically fetch new posts.'], 'Optional Increase': [false, 'Increase the intervals between updates on threads without new posts.'] }, - 'Interval': 30 + 'Interval': 5 }, customCooldown: 0, customCooldownEnabled: true, 'Thread Quotes': false, 'Max Replies': 1000, - 'Autohiding Scrollbar': false + 'Autohiding Scrollbar': false, + position: { + 'embedding.position': 'top: 50px; right: 0px;', + 'thread-stats.position': 'bottom: 0px; right: 0px;', + 'updater.position': 'bottom: 0px; left: 0px;', + 'thread-watcher.position': 'top: 50px; left: 0px;', + 'qr.position': 'top: 50px; right: 0px;' + }, + fourchanImageHost: 'i.4cdn.org', + hiddenPSAList: [{}], + knownBanners: '0.jpg,1.jpg,2.jpg,4.jpg,6.jpg,7.jpg,8.jpg,9.jpg,10.jpg,11.jpg,12.jpg,13.jpg,14.jpg,16.jpg,17.jpg,18.jpg,19.jpg,20.jpg,21.jpg,22.jpg,24.jpg,25.jpg,26.jpg,28.jpg,29.jpg,33.jpg,38.jpg,39.jpg,43.jpg,44.jpg,45.jpg,46.jpg,47.jpg,52.jpg,54.jpg,57.jpg,59.jpg,60.jpg,61.jpg,64.jpg,66.jpg,67.jpg,69.jpg,71.jpg,72.jpg,76.jpg,77.jpg,81.jpg,82.jpg,83.jpg,84.jpg,88.jpg,90.jpg,91.jpg,96.jpg,98.jpg,99.jpg,100.jpg,104.jpg,106.jpg,116.jpg,119.jpg,137.jpg,140.jpg,148.jpg,149.jpg,150.jpg,154.jpg,156.jpg,157.jpg,158.jpg,159.jpg,161.jpg,162.jpg,164.jpg,165.jpg,166.jpg,167.jpg,168.jpg,169.jpg,170.jpg,171.jpg,172.jpg,173.jpg,174.jpg,175.jpg,176.jpg,178.jpg,179.jpg,180.jpg,181.jpg,182.jpg,183.jpg,186.jpg,189.jpg,190.jpg,192.jpg,193.jpg,194.jpg,197.jpg,198.jpg,200.jpg,201.jpg,202.jpg,203.jpg,205.jpg,206.jpg,207.jpg,208.jpg,210.jpg,213.jpg,214.jpg,215.jpg,216.jpg,218.jpg,219.jpg,220.jpg,221.jpg,222.jpg,223.jpg,224.jpg,227.jpg,0.png,1.png,2.png,3.png,5.png,6.png,9.png,10.png,11.png,12.png,14.png,16.png,19.png,20.png,21.png,22.png,23.png,24.png,26.png,27.png,28.png,29.png,30.png,31.png,32.png,33.png,34.png,37.png,39.png,40.png,41.png,42.png,43.png,44.png,45.png,48.png,49.png,50.png,51.png,52.png,53.png,57.png,58.png,59.png,64.png,66.png,67.png,68.png,69.png,70.png,71.png,72.png,76.png,78.png,79.png,81.png,82.png,85.png,86.png,87.png,89.png,95.png,98.png,100.png,101.png,102.png,105.png,106.png,107.png,109.png,110.png,111.png,112.png,113.png,114.png,115.png,116.png,118.png,119.png,120.png,121.png,122.png,123.png,126.png,128.png,130.png,134.png,136.png,138.png,139.png,140.png,142.png,145.png,146.png,149.png,150.png,151.png,152.png,153.png,154.png,155.png,156.png,157.png,158.png,159.png,160.png,163.png,164.png,165.png,166.png,167.png,168.png,169.png,170.png,171.png,172.png,173.png,174.png,178.png,179.png,180.png,181.png,182.png,184.png,186.png,188.png,190.png,192.png,193.png,194.png,195.png,196.png,197.png,198.png,200.png,202.png,203.png,205.png,206.png,207.png,209.png,212.png,213.png,214.png,216.png,217.png,218.png,219.png,220.png,221.png,222.png,223.png,224.png,225.png,226.png,229.png,231.png,232.png,233.png,234.png,235.png,237.png,238.png,239.png,240.png,241.png,242.png,244.png,245.png,246.png,247.png,248.png,249.png,250.png,253.png,254.png,255.png,256.png,257.png,258.png,259.png,260.png,262.png,268.png,0.gif,1.gif,2.gif,3.gif,4.gif,5.gif,6.gif,7.gif,8.gif,9.gif,10.gif,12.gif,13.gif,14.gif,15.gif,16.gif,18.gif,19.gif,20.gif,21.gif,22.gif,23.gif,24.gif,28.gif,29.gif,30.gif,33.gif,34.gif,35.gif,36.gif,37.gif,39.gif,40.gif,42.gif,44.gif,45.gif,46.gif,48.gif,50.gif,52.gif,54.gif,55.gif,57.gif,58.gif,59.gif,60.gif,61.gif,63.gif,64.gif,66.gif,67.gif,68.gif,69.gif,70.gif,72.gif,73.gif,75.gif,76.gif,77.gif,78.gif,80.gif,81.gif,82.gif,83.gif,86.gif,87.gif,88.gif,92.gif,93.gif,94.gif,95.gif,96.gif,97.gif,98.gif,99.gif,100.gif,101.gif,102.gif,103.gif,104.gif,105.gif,106.gif,108.gif,109.gif,110.gif,111.gif,112.gif,113.gif,115.gif,116.gif,117.gif,118.gif,119.gif,120.gif,122.gif,123.gif,124.gif,127.gif,129.gif,130.gif,131.gif,134.gif,135.gif,136.gif,138.gif,139.gif,141.gif,144.gif,146.gif,148.gif,149.gif,153.gif,154.gif,155.gif,157.gif,158.gif,159.gif,160.gif,161.gif,162.gif,164.gif,166.gif,167.gif,168.gif,169.gif,170.gif,171.gif,172.gif,173.gif,174.gif,175.gif,176.gif,177.gif,178.gif,181.gif,182.gif,183.gif,185.gif,186.gif,187.gif,188.gif,189.gif,190.gif,191.gif,192.gif,193.gif,195.gif,196.gif,197.gif,200.gif,201.gif,202.gif,203.gif,204.gif,205.gif,206.gif,207.gif,208.gif,209.gif,210.gif,211.gif,212.gif,213.gif,214.gif,215.gif,216.gif,217.gif,219.gif,220.gif,221.gif,222.gif,224.gif,225.gif,226.gif,227.gif,228.gif,230.gif,232.gif,233.gif,234.gif,235.gif,238.gif,240.gif,241.gif,243.gif,244.gif,245.gif,246.gif,247.gif,249.gif,250.gif,251.gif,253.gif', + passMessageClosed: false, + 'Prerequest Captcha': false, + 'PSAseen': [[]] }; return Config; @@ -472,12 +594,12 @@ CSS = { boards: "/*!\n\ - * Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome\n\ + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome\n\ * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)\n\ */\n\ @font-face {\n\ font-family: FontAwesome;\n\ - src: url('data:application/font-woff;base64,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') format('woff');\n\ + src: url('data:application/font-woff;base64,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') format('woff');\n\ font-weight: 400;\n\ font-style: normal;\n\ }\n\ @@ -1005,7 +1127,7 @@ boards: .fa-optin-monster:before {content: \"\\f23c\";}\n\ .fa-opencart:before {content: \"\\f23d\";}\n\ .fa-expeditedssl:before {content: \"\\f23e\";}\n\ -.fa-battery-4:before, .fa-battery-full:before {content: \"\\f240\";}\n\ +.fa-battery-4:before, .fa-battery:before, .fa-battery-full:before {content: \"\\f240\";}\n\ .fa-battery-3:before, .fa-battery-three-quarters:before {content: \"\\f241\";}\n\ .fa-battery-2:before, .fa-battery-half:before {content: \"\\f242\";}\n\ .fa-battery-1:before, .fa-battery-quarter:before {content: \"\\f243\";}\n\ @@ -1115,6 +1237,47 @@ boards: .fa-themeisle:before {content: \"\\f2b2\";}\n\ .fa-google-plus-circle:before, .fa-google-plus-official:before {content: \"\\f2b3\";}\n\ .fa-fa:before, .fa-font-awesome:before {content: \"\\f2b4\";}\n\ +.fa-handshake-o:before {content: \"\\f2b5\";}\n\ +.fa-envelope-open:before {content: \"\\f2b6\";}\n\ +.fa-envelope-open-o:before {content: \"\\f2b7\";}\n\ +.fa-linode:before {content: \"\\f2b8\";}\n\ +.fa-address-book:before {content: \"\\f2b9\";}\n\ +.fa-address-book-o:before {content: \"\\f2ba\";}\n\ +.fa-vcard:before, .fa-address-card:before {content: \"\\f2bb\";}\n\ +.fa-vcard-o:before, .fa-address-card-o:before {content: \"\\f2bc\";}\n\ +.fa-user-circle:before {content: \"\\f2bd\";}\n\ +.fa-user-circle-o:before {content: \"\\f2be\";}\n\ +.fa-user-o:before {content: \"\\f2c0\";}\n\ +.fa-id-badge:before {content: \"\\f2c1\";}\n\ +.fa-drivers-license:before, .fa-id-card:before {content: \"\\f2c2\";}\n\ +.fa-drivers-license-o:before, .fa-id-card-o:before {content: \"\\f2c3\";}\n\ +.fa-quora:before {content: \"\\f2c4\";}\n\ +.fa-free-code-camp:before {content: \"\\f2c5\";}\n\ +.fa-telegram:before {content: \"\\f2c6\";}\n\ +.fa-thermometer-4:before, .fa-thermometer:before, .fa-thermometer-full:before {content: \"\\f2c7\";}\n\ +.fa-thermometer-3:before, .fa-thermometer-three-quarters:before {content: \"\\f2c8\";}\n\ +.fa-thermometer-2:before, .fa-thermometer-half:before {content: \"\\f2c9\";}\n\ +.fa-thermometer-1:before, .fa-thermometer-quarter:before {content: \"\\f2ca\";}\n\ +.fa-thermometer-0:before, .fa-thermometer-empty:before {content: \"\\f2cb\";}\n\ +.fa-shower:before {content: \"\\f2cc\";}\n\ +.fa-bathtub:before, .fa-s15:before, .fa-bath:before {content: \"\\f2cd\";}\n\ +.fa-podcast:before {content: \"\\f2ce\";}\n\ +.fa-window-maximize:before {content: \"\\f2d0\";}\n\ +.fa-window-minimize:before {content: \"\\f2d1\";}\n\ +.fa-window-restore:before {content: \"\\f2d2\";}\n\ +.fa-times-rectangle:before, .fa-window-close:before {content: \"\\f2d3\";}\n\ +.fa-times-rectangle-o:before, .fa-window-close-o:before {content: \"\\f2d4\";}\n\ +.fa-bandcamp:before {content: \"\\f2d5\";}\n\ +.fa-grav:before {content: \"\\f2d6\";}\n\ +.fa-etsy:before {content: \"\\f2d7\";}\n\ +.fa-imdb:before {content: \"\\f2d8\";}\n\ +.fa-ravelry:before {content: \"\\f2d9\";}\n\ +.fa-eercast:before {content: \"\\f2da\";}\n\ +.fa-microchip:before {content: \"\\f2db\";}\n\ +.fa-snowflake-o:before {content: \"\\f2dc\";}\n\ +.fa-superpowers:before {content: \"\\f2dd\";}\n\ +.fa-wpexplorer:before {content: \"\\f2de\";}\n\ +.fa-meetup:before {content: \"\\f2e0\";}\n\ .fa::before {\n\ font-family: FontAwesome;\n\ font-weight: 400;\n\ @@ -1190,13 +1353,11 @@ boards: font: 13px sans-serif;\n\ outline: none;\n\ transition: color .25s, border-color .25s;\n\ - transition: color .25s, border-color .25s;\n\ }\n\ -.field::-moz-placeholder,\n\ -.field:hover::-moz-placeholder {\n\ - color: #AAA !important;\n\ - font-size: 13px !important;\n\ - opacity: 1.0 !important;\n\ +.field::-moz-placeholder {\n\ + color: #AAA;\n\ + font-size: 13px;\n\ + opacity: 1;\n\ }\n\ .captch-img:hover,\n\ .field:hover {\n\ @@ -1225,10 +1386,10 @@ a[href=\"javascript:;\"] {\n\ .warning {\n\ color: red;\n\ }\n\ -#boardNavDesktop, #boardNavMobile {\n\ +:root.sw-yotsuba #boardNavDesktop, :root.sw-yotsuba #boardNavMobile {\n\ display: none !important;\n\ }\n\ -:root.hide-bottom-board-list #boardNavDesktopFoot {\n\ +:root.hide-bottom-board-list $site$boardListBottom {\n\ display: none;\n\ }\n\ body.hasDropDownNav{\n\ @@ -1241,61 +1402,137 @@ body.hasDropDownNav{\n\ border-radius: 3px;\n\ padding: 0px 2px;\n\ }\n\ +[hidden] {\n\ + display: none !important;\n\ +}\n\ /* 4chan style fixes */\n\ -.opContainer, .op {\n\ - display: block !important;\n\ - overflow: visible !important;\n\ +/* overrides 4chan CSS on div.opContainer, div.op */\n\ +:root.sw-yotsuba .opContainer, :root.sw-yotsuba .op {\n\ + display: block;\n\ + overflow: visible;\n\ }\n\ -.reply > .file > .fileText {\n\ +:root.sw-yotsuba .reply > .file > .fileText {\n\ margin: 0 20px;\n\ }\n\ -.hashlink::before {\n\ - content: ' ';\n\ - visibility: hidden;\n\ -}\n\ -.inline + .hashlink,\n\ -[hidden] {\n\ - display: none !important;\n\ +:root.sw-yotsuba #arc-list span.quote {\n\ + color: #789922;\n\ }\n\ -.fileText a {\n\ +:root.sw-yotsuba .fileText a {\n\ unicode-bidi: -moz-isolate;\n\ unicode-bidi: -webkit-isolate;\n\ }\n\ -#g-recaptcha {\n\ +:root.sw-yotsuba #g-recaptcha {\n\ min-height: 78px;\n\ height: auto;\n\ }\n\ -:root:not(.js-enabled) #postForm {\n\ +:root.sw-yotsuba:not(.js-enabled) #postForm {\n\ display: table;\n\ }\n\ -#captchaContainerAlt td:nth-child(2) {\n\ +:root.sw-yotsuba #captchaContainerAlt td:nth-child(2) {\n\ display: table-cell !important;\n\ }\n\ -canvas#tegaki-canvas {\n\ +:root.sw-yotsuba canvas#tegaki-canvas {\n\ background: none;\n\ }\n\ /* Disable obnoxious captcha fade-in. */\n\ -body > div:last-of-type {\n\ +:root.sw-yotsuba > body > div:last-of-type {\n\ transition: none !important;\n\ }\n\ /* Fix captcha scrolling to top of page. */\n\ -body > div[style*=\" top: -10000px;\"] {\n\ +:root.sw-yotsuba > body > div[style*=\" top: -10000px;\"] {\n\ visibility: hidden !important;\n\ }\n\ +/* Make long filenames wrap properly: https://github.com/ccd0/4chan-x/issues/1082 */\n\ +:root.sw-yotsuba .post > .file {\n\ + /* currently nonstandard but may be added: https://lists.w3.org/Archives/Public/www-style/2016Mar/0352.html, https://bugzilla.mozilla.org/show_bug.cgi?id=1296042 */\n\ + word-break: break-word;\n\ +}\n\ +:root.sw-yotsuba:not(.ua-webkit):not(.ua-blink) .fileText {\n\ + word-wrap: break-word;\n\ + max-width: calc(100vw - 90px);\n\ +}\n\ +:root.sw-yotsuba > body.is_catalog .thread > a > img {\n\ + display: inline-block;\n\ +}\n\ +/* Links to NSFW boards */\n\ +:root.sw-yotsuba .nwsb {\n\ + display: inline;\n\ +}\n\ +:root.sw-yotsuba .fileText {\n\ + max-width: auto;\n\ + white-space: normal;\n\ +}\n\ /* Ads */\n\ -:root:not(.ads-loaded) .ad-cnt,\n\ -:root:not(.ads-loaded) .ad-plea,\n\ -:root:not(.ads-loaded) hr.abovePostForm,\n\ -:root:not(.ads-loaded) .ad-plea-bottom + hr {\n\ +:root.sw-yotsuba .ad-cnt > *, :root.sw-yotsuba .adg-rects > *, :root.sw-yotsuba .bsa-cnt {\n\ + height: auto !important;\n\ +}\n\ +:root.sw-yotsuba:not(.ads-loaded) hr.abovePostForm,\n\ +:root.sw-yotsuba:not(.ads-loaded) .adg-rects > hr,\n\ +:root.sw-yotsuba #adg-ol + hr,\n\ +:root.sw-yotsuba .danbo-slot:empty {\n\ display: none;\n\ }\n\ -hr + div.center:not(.ad-cnt):not(.topad):not(.middlead):not(.bottomad) {\n\ +:root.sw-yotsuba .adg-rects {\n\ + margin: 0;\n\ + font-size: 0;\n\ +}\n\ +:root.sw-yotsuba div.center[style] {\n\ display: none !important;\n\ }\n\ +/* Tinyboard / vichan conflicts */\n\ +#menu > .hide-thread-link {\n\ + width: auto;\n\ + height: auto;\n\ + overflow: visible;\n\ + background-image: none;\n\ +}\n\ +#menu label.entry {\n\ + display: block;\n\ +}\n\ +#fourchanx-settings label {\n\ + display: inline;\n\ +}\n\ +.intro a[href=\"javascript:;\"],\n\ +#menu a {\n\ + margin: 0;\n\ +}\n\ +.gal-buttons.gal-buttons a {\n\ + font-size: inherit;\n\ +}\n\ +:root.sw-tinyboard.fixed.top-header:not(.autohide) .boardlist,\n\ +:root.sw-tinyboard.fixed.top-header:not(.autohide) .bar.top {\n\ + position: static;\n\ +}\n\ +:root.sw-tinyboard.fixed.top-header:not(.autohide) div.pages.top {\n\ + top: auto;\n\ + bottom: 0;\n\ +}\n\ +:root.sw-tinyboard.fixed.top-header.autohide .boardlist,\n\ +:root.sw-tinyboard.fixed.top-header.autohide .bar.top {\n\ + z-index: 3;\n\ +}\n\ +/* Tinyboard site style conflicts */\n\ +:root[data-host=\"fufufu.moe\"].fixed.top-header:not(.autohide) div.pages.top {\n\ + top: 26px;\n\ + bottom: auto;\n\ +}\n\ +:root[data-host=\"merorin.com\"].fixed.top-header:not(.autohide) span.settings {\n\ + top: 26px;\n\ +}\n\ +:root[data-host=\"fufufu.moe\"]:not(.fixed) #header-bar {\n\ + margin-top: 38px;\n\ +}\n\ +:root[data-host=\"lainchan.org\"]:not(.fixed) #header-bar {\n\ + margin-top: 17px;\n\ +}\n\ +:root[data-host=\"smuglo.li\"]:not(.fixed) #header-bar {\n\ + margin-top: 8px;\n\ +}\n\ /* Anti-autoplay */\n\ audio.controls-added {\n\ display: block;\n\ margin: auto;\n\ + white-space: normal;\n\ }\n\ :root.anti-autoplay div.embed {\n\ position: static;\n\ @@ -1304,14 +1541,12 @@ audio.controls-added {\n\ text-align: center;\n\ }\n\ :root.anti-autoplay .autoplay-removed {\n\ - display: block !important;\n\ visibility: visible !important;\n\ min-width: 640px;\n\ - min-height: 390px;\n\ + min-height: 360px;\n\ }\n\ /* fixed, z-index */\n\ #overlay,\n\ -#fourchanx-settings,\n\ #qp, #ihover,\n\ #navlinks, .fixed #header-bar,\n\ :root.float #updater,\n\ @@ -1319,11 +1554,8 @@ audio.controls-added {\n\ #qr {\n\ position: fixed;\n\ }\n\ -#fourchanx-settings {\n\ - z-index: 999;\n\ -}\n\ #overlay {\n\ - z-index: 900;\n\ + z-index: 999;\n\ }\n\ #qp, #ihover {\n\ z-index: 60;\n\ @@ -1490,56 +1722,57 @@ audio.controls-added {\n\ #toggleMsgBtn {\n\ display: none !important;\n\ }\n\ -.current {\n\ +.current,\n\ +:root.sw-yotsuba div#boardNavDesktopFoot a.current {\n\ font-weight: bold;\n\ }\n\ @media (min-width: 1300px) {\n\ - :root.fixed:not(.centered-links) #header-bar {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #header-bar {\n\ white-space: nowrap;\n\ display: -webkit-flex;\n\ display: flex;\n\ -webkit-align-items: center;\n\ align-items: center;\n\ }\n\ - :root.fixed:not(.centered-links) #board-list {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #board-list {\n\ -webkit-flex: auto;\n\ flex: auto;\n\ }\n\ - :root.fixed:not(.centered-links) #full-board-list {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list {\n\ display: -webkit-flex;\n\ display: flex;\n\ }\n\ - :root.fixed:not(.centered-links) .hide-board-list-container {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) .hide-board-list-container {\n\ -webkit-flex: none;\n\ flex: none;\n\ margin-right: 5px;\n\ }\n\ - :root.fixed:not(.centered-links) #full-board-list > .boardList {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList {\n\ -webkit-flex: auto;\n\ flex: auto;\n\ display: -webkit-flex;\n\ display: flex;\n\ width: 0px; /* XXX Fixes Edge not shrinking the board list below default size when needed */\n\ }\n\ - :root.fixed:not(.centered-links) #full-board-list > .boardList > a,\n\ - :root.fixed:not(.centered-links) #full-board-list > .boardList > span:not(.space):not(.spacer) {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > a,\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > span:not(.space):not(.spacer) {\n\ -webkit-flex: none;\n\ flex: none;\n\ padding: .17em;\n\ margin: -.17em -.32em;\n\ }\n\ - :root.fixed:not(.centered-links) #full-board-list > .boardList > span {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > span {\n\ pointer-events: none;\n\ }\n\ - :root.fixed:not(.centered-links) #full-board-list > .boardList > span.space {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > span.space {\n\ -webkit-flex: 0 .63 .63em;\n\ flex: 0 .63 .63em;\n\ }\n\ - :root.fixed:not(.centered-links) #full-board-list > .boardList > span.spacer {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > span.spacer {\n\ -webkit-flex: 0 .38 .38em;\n\ flex: 0 .38 .38em;\n\ }\n\ - :root.fixed:not(.centered-links) #shortcuts {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #shortcuts {\n\ float: initial;\n\ -webkit-flex: none;\n\ flex: none;\n\ @@ -1566,6 +1799,9 @@ audio.controls-added {\n\ left: 0;\n\ visibility: visible;\n\ }\n\ +#notifications:empty {\n\ + display: none;\n\ +}\n\ :root.fixed.top-header:not(.gallery-open) #header-bar #notifications,\n\ :root.fixed.top-header #header-bar.autohide #notifications {\n\ position: absolute;\n\ @@ -1629,6 +1865,8 @@ audio.controls-added {\n\ }\n\ #overlay {\n\ background-color: rgba(0, 0, 0, .5);\n\ + display: -webkit-flex;\n\ + display: flex;\n\ top: 0;\n\ left: 0;\n\ height: 100%;\n\ @@ -1643,16 +1881,16 @@ audio.controls-added {\n\ width: 900px;\n\ max-width: 100%;\n\ margin: auto;\n\ - padding: 3px;\n\ - top: 50%;\n\ - left: 50%;\n\ - -moz-transform: translate(-50%, -50%);\n\ - -webkit-transform: translate(-50%, -50%);\n\ - transform: translate(-50%, -50%);\n\ + padding: 5px;\n\ + display: -webkit-flex;\n\ + display: flex;\n\ + -webkit-flex-direction: column;\n\ + flex-direction: column;\n\ }\n\ #fourchanx-settings > nav {\n\ - padding: 2px 2px 0;\n\ - height: 15px;\n\ + padding: 2px 2px 8px;\n\ + display: -webkit-flex;\n\ + display: flex;\n\ }\n\ #fourchanx-settings > nav a {\n\ text-decoration: underline;\n\ @@ -1663,20 +1901,16 @@ audio.controls-added {\n\ margin: 0;\n\ }\n\ .section-container {\n\ + -webkit-flex: 1;\n\ + flex: 1;\n\ + position: relative;\n\ overflow: auto;\n\ - position: absolute;\n\ - top: 2.1em;\n\ - right: 5px;\n\ - bottom: 5px;\n\ - left: 5px;\n\ padding-right: 5px;\n\ + overscroll-behavior: contain;\n\ }\n\ .sections-list {\n\ - padding: 0 3px;\n\ - float: left;\n\ -}\n\ -.credits {\n\ - float: right;\n\ + -webkit-flex: 1;\n\ + flex: 1;\n\ }\n\ .export, .import, .reset {\n\ cursor: pointer;\n\ @@ -1743,6 +1977,9 @@ div[data-checked=\"false\"] > .suboption-list {\n\ border-left: 1px solid;\n\ border-bottom: 1px solid;\n\ }\n\ +#fourchanx-settings .section-main p {\n\ + margin: .5em 0 0;\n\ +}\n\ .section-filter ul {\n\ padding: 0;\n\ }\n\ @@ -1756,8 +1993,20 @@ div[data-checked=\"false\"] > .suboption-list {\n\ .section-main a, .section-filter a, .section-advanced a {\n\ text-decoration: underline;\n\ }\n\ +#sauce-doc-expand:not(:checked) ~ #sauce-doc {\n\ + max-height: 130px;\n\ + overflow: auto;\n\ +}\n\ +#sauce-doc > label {\n\ + float: right;\n\ + margin: 0 5px;\n\ +}\n\ +/* XXX for OneeChan */\n\ +#sauce-doc-expand + .riceCheck {\n\ + display: none;\n\ +}\n\ .section-sauce textarea {\n\ - height: 350px;\n\ + height: 430px;\n\ }\n\ .section-advanced .field[name=\"boardnav\"] {\n\ width: 100%;\n\ @@ -1765,7 +2014,9 @@ div[data-checked=\"false\"] > .suboption-list {\n\ .section-advanced textarea {\n\ height: 150px;\n\ }\n\ -.section-advanced textarea[name=\"archiveLists\"] {\n\ +.section-advanced textarea[name=\"archiveLists\"],\n\ +.section-advanced textarea[name=\"externalCatalogURLs\"],\n\ +.section-advanced textarea[name=\"knownBanners\"] {\n\ height: 75px;\n\ }\n\ .section-advanced .archive-cell {\n\ @@ -1784,6 +2035,12 @@ div[data-checked=\"false\"] > .suboption-list {\n\ font-style: normal;\n\ font-size: 11px;\n\ }\n\ +.favicon-preview > img {\n\ + vertical-align: middle;\n\ +}\n\ +.favicon-preview > img:nth-of-type(3n+1) {\n\ + margin-left: 4px;\n\ +}\n\ .section-keybinds .field {\n\ font-family: monospace;\n\ }\n\ @@ -1799,8 +2056,8 @@ div[data-checked=\"false\"] > .suboption-list {\n\ }\n\ #fourchanx-settings textarea {\n\ font-family: monospace;\n\ - min-width: 100%;\n\ - max-width: 100%;\n\ + width: 100%;\n\ + resize: vertical;\n\ }\n\ #fourchanx-settings code {\n\ color: #000;\n\ @@ -1814,8 +2071,8 @@ div[data-checked=\"false\"] > .suboption-list {\n\ #fourchanx-settings p {\n\ margin: 1em 0px;\n\ }\n\ -.unscroll {\n\ - overflow: hidden;\n\ +#fourchanx-settings table {\n\ + margin: auto;\n\ }\n\ /* Index */\n\ :root.index-loading .navLinks:not(.json-index),\n\ @@ -1853,9 +2110,26 @@ div[data-checked=\"false\"] > .suboption-list {\n\ #index-search:not([data-searching]) + #index-search-clear {\n\ display: none;\n\ }\n\ -#index-mode, #index-sort, #index-size {\n\ +#index-options {\n\ float: right;\n\ }\n\ +#lastlong-options {\n\ + display: inline-block;\n\ + vertical-align: middle;\n\ + height: 28px;\n\ + margin: -14px 0;\n\ +}\n\ +#lastlong-options > input {\n\ + padding: 0;\n\ + border: 0 !important;\n\ + text-align: center;\n\ + background: transparent;\n\ + display: block;\n\ + font-size: 12px;\n\ + height: 12px;\n\ + width: 30px;\n\ + margin: 1px 0;\n\ +}\n\ .summary {\n\ text-decoration: none;\n\ }\n\ @@ -1864,34 +2138,78 @@ div[data-checked=\"false\"] > .suboption-list {\n\ text-align: center;\n\ }\n\ .catalog-thread {\n\ - display: -webkit-inline-flex;\n\ - display: inline-flex;\n\ - text-align: left;\n\ - -webkit-flex-direction: column;\n\ - flex-direction: column;\n\ - -webkit-align-items: center;\n\ - align-items: center;\n\ - margin: 0 2px 5px;\n\ + display: inline-block;\n\ + -moz-box-sizing: border-box;\n\ + box-sizing: border-box;\n\ + border: 1px solid transparent;\n\ word-wrap: break-word;\n\ vertical-align: top;\n\ position: relative;\n\ }\n\ -.catalog-thread > a {\n\ - flex-shrink: 0;\n\ - -webkit-flex-shrink: 0;\n\ - position: relative;\n\ +/* overrides 4chan CSS on div.thread */\n\ +.catalog-thread.catalog-thread {\n\ + margin: 2px;\n\ }\n\ -.catalog-small .catalog-thread {\n\ +.catalog-small > .catalog-thread {\n\ width: 165px;\n\ - max-height: 320px;\n\ + height: 320px;\n\ }\n\ -.catalog-large .catalog-thread {\n\ +.catalog-large > .catalog-thread {\n\ width: 270px;\n\ - max-height: 410px;\n\ + height: 410px;\n\ +}\n\ +:root.catalog-hover-expand .catalog-thread:hover {\n\ + z-index: 1;\n\ +}\n\ +.catalog-container {\n\ + position: absolute;\n\ + top: -4px;\n\ + left: 0;\n\ + right: 0;\n\ + bottom: 0;\n\ +}\n\ +.catalog-container:not(:hover),\n\ +:root:not(.catalog-hover-expand) .catalog-container {\n\ + overflow: hidden;\n\ +}\n\ +.catalog-post {\n\ + position: absolute;\n\ + top: 4px;\n\ + left: 0;\n\ + right: 0;\n\ + border: 1px solid transparent;\n\ + padding-top: 20px;\n\ +}\n\ +/* overrides inline CSS from Index.cb.hoverAdjust */\n\ +:root:not(.catalog-hover-expand) .catalog-post {\n\ + left: 0 !important;\n\ + right: 0 !important;\n\ +}\n\ +/* overrides 4chan CSS on div.post */\n\ +.catalog-post.catalog-post {\n\ + margin: -21px -1px -1px;\n\ + overflow: visible;\n\ +}\n\ +.catalog-thread.noFile > * > .catalog-post {\n\ + margin-top: -7px;\n\ + padding-top: 6px;\n\ +}\n\ +:root.catalog-hover-expand .catalog-container:hover > .catalog-post {\n\ + margin-left: -61px;\n\ + margin-right: -61px;\n\ +}\n\ +:root.catalog-hover-expand .catalog-container:hover > * > :not(.catalog-replies) {\n\ + padding-left: 2px;\n\ + padding-right: 2px;\n\ +}\n\ +.catalog-link {\n\ + display: block;\n\ + position: relative;\n\ }\n\ .catalog-thumb {\n\ border-radius: 2px;\n\ box-shadow: 0 0 5px rgba(0, 0, 0, .25);\n\ + vertical-align: top;\n\ }\n\ .catalog-thumb.spoiler-file {\n\ width: 100px;\n\ @@ -1916,45 +2234,144 @@ div[data-checked=\"false\"] > .suboption-list {\n\ padding-left: 2px;\n\ }\n\ .catalog-stats > .menu-button {\n\ - text-align: center;\n\ font-weight: normal;\n\ }\n\ .catalog-stats > .menu-button > i::before {\n\ line-height: 11px;\n\ }\n\ .catalog-stats {\n\ - -webkit-flex-shrink: 0;\n\ - flex-shrink: 0;\n\ - cursor: help;\n\ font-size: 10px;\n\ font-weight: 700;\n\ - margin-top: 2px;\n\ + padding-top: 2px;\n\ }\n\ -.catalog-thread > .subject {\n\ - -webkit-flex-shrink: 0;\n\ - flex-shrink: 0;\n\ - -webkit-align-self: stretch;\n\ - align-self: stretch;\n\ - font-weight: 700;\n\ - line-height: 1;\n\ - text-align: center;\n\ +.catalog-stats > [title] {\n\ + cursor: help;\n\ }\n\ -.catalog-thread > .comment {\n\ - -webkit-flex-shrink: 1;\n\ - flex-shrink: 1;\n\ - -webkit-align-self: stretch;\n\ - align-self: stretch;\n\ +.catalog-post > .postMessage {\n\ + margin: 0;\n\ + padding-bottom: .3em;\n\ +}\n\ +.catalog-container:not(:hover) > * > .file,\n\ +.catalog-container:not(:hover) > * > .postInfo > :not(.subject),\n\ +.catalog-container:not(:hover) > * > .catalog-replies,\n\ +.catalog-container:not(:hover) .extra-linebreak,\n\ +.catalog-container:not(:hover) .abbr,\n\ +:root:not(.catalog-hover-expand) .catalog-container > * > .file,\n\ +:root:not(.catalog-hover-expand) .catalog-container > * > .postInfo > :not(.subject),\n\ +:root:not(.catalog-hover-expand) .catalog-container > * > .catalog-replies,\n\ +:root:not(.catalog-hover-expand) .catalog-container .extra-linebreak,\n\ +:root:not(.catalog-hover-expand) .catalog-container .abbr,\n\ +.catalog-thread > .catalog-container > :not(.catalog-post),\n\ +.catalog-post > .file > :not(.fileText),\n\ +.catalog-post > * > .fileText > :not(:first-child),\n\ +.catalog-post > .postInfo > :not(.subject):not(.nameBlock):not(.dateTime),\n\ +.catalog-post > .postInfo > .nameBlock > .contact-links,\n\ +.catalog-post > * > * > .posteruid,\n\ +.catalog-post > * > * > .postJumper,\n\ +:root.bottom-backlinks .catalog-post > .container,\n\ +.post:not(.catalog-post) > .catalog-link,\n\ +.post:not(.catalog-post) > .catalog-stats,\n\ +.post:not(.catalog-post) > .catalog-replies {\n\ + display: none;\n\ +}\n\ +.catalog-post > .file {\n\ + position: absolute;\n\ + left: 0;\n\ + right: 0;\n\ + top: 0;\n\ + min-height: 20px;\n\ + background-color: inherit;\n\ +}\n\ +.catalog-post > * > .fileText {\n\ + position: relative;\n\ + padding: 2px;\n\ + background-color: inherit;\n\ +}\n\ +.catalog-small .catalog-post > * .fileText {\n\ + font-size: 10px;\n\ +}\n\ +.catalog-post > * > .fileText:not(:hover) {\n\ + white-space: nowrap;\n\ overflow: hidden;\n\ - text-align: center;\n\ + text-overflow: ellipsis;\n\ }\n\ -/* /tg/ dice rolls */\n\ -.board_tg .catalog-thread > .comment > b {\n\ - font-weight: normal;\n\ +.catalog-post > * > .fileText:hover {\n\ + z-index: 1;\n\ }\n\ -.catalog-code {\n\ - background-color: #FFF;\n\ +/* overrides 4chan CSS on div.post div.postInfo */\n\ +.catalog-post > .postInfo.postInfo {\n\ + width: auto;\n\ +}\n\ +.catalog-post > * > .subject {\n\ + display: block;\n\ +}\n\ +.catalog-post > * > .dateTime {\n\ display: inline-block;\n\ + font-style: italic;\n\ +}\n\ +:root.catalog-hover-expand .catalog-container:hover > * > * > .nameBlock,\n\ +:root.catalog-hover-expand .catalog-container:hover > * > * > .dateTime,\n\ +:root.catalog-hover-expand .catalog-container:hover > * > .postMessage:not(:empty) {\n\ + padding-top: .3em;\n\ +}\n\ +.catalog-post .extra-linebreak {\n\ + content: ''; /* makes this work in Blink/WebKit */\n\ + display: block;\n\ + margin-top: .3em;\n\ +}\n\ +.catalog-reply {\n\ + text-align: left;\n\ + white-space: nowrap;\n\ + border-top: 1px solid transparent;\n\ + display: -webkit-flex;\n\ + display: flex;\n\ + -webkit-flex-direction: row;\n\ + flex-direction: row;\n\ + -webkit-align-items: stretch;\n\ + align-items: stretch;\n\ +}\n\ +.catalog-reply > * {\n\ + padding: 3px;\n\ + overflow: hidden;\n\ + -webkit-flex: none;\n\ + flex: none;\n\ +}\n\ +.catalog-reply > span {\n\ + font-style: italic;\n\ + font-weight: bold;\n\ +}\n\ +.catalog-reply-excerpt {\n\ + -webkit-flex: 1 1 auto;\n\ + flex: 1 1 auto;\n\ +}\n\ +.catalog-post .prettyprinted {\n\ max-width: 100%;\n\ + -moz-box-sizing: border-box;\n\ + box-sizing: border-box;\n\ +}\n\ +.catalog-post .MathJax_Display {\n\ + text-align: center !important;\n\ +}\n\ +.catalog-container:not(:hover) .exif,\n\ +:root:not(.catalog-hover-expand) .catalog-container .exif {\n\ + display: none !important;\n\ +}\n\ +.catalog-post > * > .exif {\n\ + border-collapse: collapse;\n\ +}\n\ +:root.catalog-hover-expand .catalog-container:hover .exif[style*=\"display: block;\"] {\n\ + display: inline-block !important;\n\ +}\n\ +.catalog-post > * > .exif,\n\ +.catalog-post > * > .exif > tbody {\n\ + background-color: inherit;\n\ +}\n\ +.catalog-post > * > .exif,\n\ +.catalog-post > * > .exif td {\n\ + min-width: 0;\n\ +}\n\ +.catalog-post > * > .exif td {\n\ + padding-top: 1px;\n\ }\n\ :root.hats-enabled .catalog-thread::after {\n\ content: '';\n\ @@ -1962,37 +2379,56 @@ div[data-checked=\"false\"] > .suboption-list {\n\ position: absolute;\n\ background-size: contain;\n\ }\n\ -:root.hats-enabled .catalog-small .catalog-thread::after {\n\ - left: -10px;\n\ - top: -65px;\n\ - width: 100px;\n\ - height: 100px;\n\ +:root.hats-enabled .catalog-small > .catalog-thread::after {\n\ + left: -8px;\n\ + top: -59px;\n\ + width: 96px;\n\ + height: 96px;\n\ +}\n\ +:root.hats-enabled:not(.werkTyme) .catalog-small > .catalog-thread:not(.noFile)::after {\n\ + left: calc(67px - .3px * var(--tn-w));\n\ }\n\ -:root.hats-enabled .catalog-large .catalog-thread::after {\n\ +:root.hats-enabled .catalog-large > .catalog-thread::after {\n\ left: -15px;\n\ - top: -105px;\n\ + top: -98px;\n\ width: 160px;\n\ height: 160px;\n\ }\n\ +:root.hats-enabled:not(.werkTyme) .catalog-large > .catalog-thread:not(.noFile)::after {\n\ + left: calc(110px - .5px * var(--tn-w));\n\ +}\n\ +/* Copy Text Link's textarea element */\n\ +textarea.copy-text-element {\n\ + height: 0;\n\ + width: 0;\n\ + position: absolute;\n\ + top: -10000px;\n\ +}\n\ /* Announcement Hiding */\n\ -:root.hide-announcement #globalMessage {\n\ +:root.hide-announcement $site$psa {\n\ display: none;\n\ }\n\ -span.hide-announcement {\n\ - font-size: 11px;\n\ - position: relative;\n\ - bottom: 5px;\n\ -}\n\ -.globalMessage, h2, h3 {\n\ - color: inherit !important;\n\ - font-size: 13px;\n\ - font-weight: 100;\n\ +.hide-announcement-button {\n\ + opacity: 0.4;\n\ + float: left;\n\ }\n\ /* Unread */\n\ -#unread-line {\n\ +.unread-line {\n\ margin: 0;\n\ border-color: rgb(255,0,0);\n\ }\n\ +.unread-line + br {\n\ + display: none;\n\ +}\n\ +.unread-mark-read {\n\ + float: right;\n\ + clear: both;\n\ + width: 100%;\n\ + text-align: right;\n\ +}\n\ +:not(.unread-thread) > .unread-mark-read {\n\ + display: none;\n\ +}\n\ /* Thread Updater */\n\ #updater {\n\ background: none;\n\ @@ -2001,10 +2437,11 @@ span.hide-announcement {\n\ }\n\ #updater > .move {\n\ position: absolute;\n\ - left: 0;\n\ top: -5px;\n\ - width: 100%;\n\ - height: 5px;\n\ + bottom: -5px;\n\ + left: -5px;\n\ + right: -5px;\n\ + z-index: -1;\n\ }\n\ #updater > div:last-child {\n\ text-align: center;\n\ @@ -2072,12 +2509,11 @@ span.hide-announcement {\n\ -webkit-flex-direction: row;\n\ flex-direction: row;\n\ }\n\ +#watched-threads .watcher-page,\n\ #watched-threads .watcher-unread {\n\ -webkit-flex: 0 0 auto;\n\ flex: 0 0 auto;\n\ -}\n\ -#watched-threads .watcher-unread::after {\n\ - content: \"\\00a0\";\n\ + margin-right: 2px;\n\ }\n\ #watched-threads .watcher-title {\n\ overflow: hidden;\n\ @@ -2085,12 +2521,15 @@ span.hide-announcement {\n\ -webkit-flex: 0 1 auto;\n\ flex: 0 1 auto;\n\ }\n\ +#watched-threads .watcher-title:not(:first-child) {\n\ + margin-left: 2px;\n\ +}\n\ +.replies-quoting-you > a, #watcher-link.replies-quoting-you, .last-page > a > .watcher-page {\n\ + color: #F00;\n\ +}\n\ #thread-watcher a {\n\ text-decoration: none;\n\ }\n\ -:root:not(.toggleable-watcher) #thread-watcher .move > .close {\n\ - display: none;\n\ -}\n\ #thread-watcher .move > .close {\n\ position: absolute;\n\ right: 0px;\n\ @@ -2127,17 +2566,24 @@ span.hide-announcement {\n\ cursor: pointer;\n\ }\n\ /* Quote */\n\ -.catalog-thread > .comment > span.quote, #arc-list span.quote {\n\ - color: #789922;\n\ +.hashlink::before {\n\ + content: ' ';\n\ + visibility: hidden;\n\ }\n\ -:root:not(.catalog-mode) .deadlink {\n\ +.inline + .hashlink {\n\ + display: none !important;\n\ +}\n\ +:root.resurrect-quotes .deadlink {\n\ text-decoration: none !important;\n\ }\n\ +.catalog-post .qmark-ct {\n\ + display: none;\n\ +}\n\ .backlink.deadlink:not(.forwardlink),\n\ .quotelink.deadlink:not(.forwardlink) {\n\ text-decoration: underline !important;\n\ }\n\ -.inlined {\n\ +:root:not(.catalog-mode) .inlined {\n\ opacity: .5;\n\ }\n\ #qp input, .forwarded {\n\ @@ -2158,11 +2604,25 @@ span.hide-announcement {\n\ .postNum + .container::before {\n\ content: \" \";\n\ }\n\ +:root.bottom-backlinks .container {\n\ + display: block;\n\ + clear: both;\n\ + margin: 0 4px;\n\ +}\n\ +:root.bottom-backlinks .backlink {\n\ + font-size: 90%;\n\ +}\n\ .inline {\n\ border: 1px solid;\n\ display: table;\n\ margin: 2px 0;\n\ }\n\ +.container ~ .inline {\n\ + margin-left: 20px;\n\ +}\n\ +:root.catalog-mode .inline {\n\ + display: none;\n\ +}\n\ .inline .post {\n\ border: 0 !important;\n\ background-color: transparent !important;\n\ @@ -2200,7 +2660,7 @@ span.hide-announcement {\n\ .expanded-image > .post > .file > .fileThumb > img[data-md5] {\n\ display: none;\n\ }\n\ -.full-image {\n\ +.full-image[data-file-i-d] {\n\ display: none;\n\ cursor: pointer;\n\ }\n\ @@ -2230,6 +2690,13 @@ span.hide-announcement {\n\ .fileThumb > .warning {\n\ clear: both;\n\ }\n\ +#ihover {\n\ + pointer-events: none;\n\ + /* XXX https://code.google.com/p/chromium/issues/detail?id=168840, https://bugs.webkit.org/show_bug.cgi?id=94158 */\n\ + max-height: 95vh;\n\ + max-height: calc(100vh - 25px);\n\ + max-width: 100vw;\n\ +}\n\ /* WEBM Metadata */\n\ .webm-title > a::before {\n\ content: \"title\";\n\ @@ -2262,22 +2729,29 @@ input[name=\"Default Volume\"] {\n\ margin: 0px;\n\ }\n\ /* Fappe and Werk Tyme */\n\ -:root.fappeTyme .thread > .noFile,\n\ -:root.fappeTyme .threadContainer > .noFile {\n\ +:root.fappeTyme $site$replyOriginal.noFile,\n\ +:root.fappeTyme $site$replyOriginal.noFile + br {\n\ display: none;\n\ }\n\ -:root.werkTyme .postContainer:not(.noFile) .fileThumb,\n\ +:root.werkTyme $site$thumbLink,\n\ +:root.werkTyme $site$file$thumb,\n\ :root.werkTyme .catalog-thumb:not(.deleted-file):not(.no-file),\n\ :root:not(.werkTyme) .werkTyme-filename {\n\ display: none;\n\ }\n\ .werkTyme-filename {\n\ font-weight: bold;\n\ + font-size: 110%;\n\ }\n\ -:root.werkTyme .catalog-thread > a {\n\ +:root.werkTyme .catalog-link {\n\ + box-shadow: 0 0 5px rgba(0, 0, 0, .25);\n\ + padding: 8px;\n\ text-align: center;\n\ - -webkit-align-self: stretch;\n\ - align-self: stretch;\n\ +}\n\ +:root.werkTyme .catalog-thumb {\n\ + box-shadow: none;\n\ + padding: 0;\n\ + vertical-align: middle;\n\ }\n\ .indicator {\n\ background: rgba(255,0,0,0.8);\n\ @@ -2308,41 +2782,46 @@ input[name=\"Default Volume\"] {\n\ .qphl {\n\ outline: 2px solid rgba(216, 94, 49, .8);\n\ }\n\ -:root.highlight-you .quotesYou.opContainer,\n\ -:root.highlight-you .quotesYou > .reply {\n\ +:root.highlight-you .quotesYou$site$highlightable$op,\n\ +:root.highlight-you .quotesYou$site$highlightable$reply {\n\ border-left: 3px solid rgba(221, 0, 0, .8);\n\ }\n\ -:root.highlight-own .yourPost.opContainer,\n\ -:root.highlight-own .yourPost > .reply {\n\ +:root.highlight-own .yourPost$site$highlightable$op,\n\ +:root.highlight-own .yourPost$site$highlightable$reply {\n\ border-left: 3px dashed rgba(221, 0, 0, .8);\n\ }\n\ -.filter-highlight.opContainer,\n\ -.filter-highlight > .reply {\n\ +.filter-highlight$site$highlightable$op,\n\ +.filter-highlight$site$highlightable$reply {\n\ box-shadow: inset 5px 0 rgba(221, 0, 0, .5);\n\ }\n\ -:root.highlight-own .yourPost > div.sideArrows,\n\ -:root.highlight-you .quotesYou > div.sideArrows,\n\ -.filter-highlight > div.sideArrows {\n\ +:root.highlight-own .yourPost > $site$sideArrows,\n\ +:root.highlight-you .quotesYou > $site$sideArrows,\n\ +.filter-highlight > $site$sideArrows {\n\ color: rgba(221, 0, 0, .8);\n\ }\n\ -:root.highlight-own .yourPost.opContainer::after,\n\ -:root.highlight-you .quotesYou.opContainer::after,\n\ -.filter-highlight.opContainer::after {\n\ +:root.highlight-own .yourPost$site$highlightable$op::after,\n\ +:root.highlight-you .quotesYou$site$highlightable$op::after,\n\ +.filter-highlight$site$highlightable$op::after {\n\ content: \"\";\n\ display: block;\n\ clear: both;\n\ }\n\ -.filter-highlight .catalog-thumb,\n\ -.filter-highlight .werkTyme-filename {\n\ +:root:not(.werkTyme) .catalog-thread.filter-highlight .catalog-thumb,\n\ +:root.werkTyme .catalog-thread.filter-highlight:not(:hover),\n\ +:root.werkTyme:not(.catalog-hover-expand) .catalog-thread.filter-highlight,\n\ +:root.werkTyme.catalog-hover-expand .catalog-thread.filter-highlight > .catalog-container:hover > .catalog-post,\n\ +:root.catalog $site$catalog$thread.filter-highlight$site$highlightable$catalog {\n\ box-shadow: 0 0 3px 3px rgba(255, 0, 0, .5);\n\ }\n\ -.catalog-thread.watched .catalog-thumb,\n\ -.catalog-thread.watched .werkTyme-filename {\n\ +:root:not(.werkTyme) .catalog-thread.watched .catalog-thumb,\n\ +:root:root.werkTyme .catalog-thread.watched:not(:hover),\n\ +:root:root.werkTyme:not(.catalog-hover-expand) .catalog-thread.watched,\n\ +:root.werkTyme.catalog-hover-expand .catalog-thread.watched > .catalog-container:hover > .catalog-post {\n\ border: 2px solid rgba(255, 0, 0, .75);\n\ }\n\ /* Spoiler text */\n\ -:root.reveal-spoilers s,\n\ -:root.reveal-spoilers s > a {\n\ +:root.reveal-spoilers $site$spoiler,\n\ +:root.reveal-spoilers $site$spoiler > a {\n\ color: white !important;\n\ }\n\ :root.reveal-spoilers .removed-spoiler::before {\n\ @@ -2358,6 +2837,13 @@ input[name=\"Default Volume\"] {\n\ margin-right: 4px;\n\ padding: 2px;\n\ }\n\ +$site$infoRoot a.hide-reply-button {\n\ + margin-right: 6px;\n\ + padding: 0;\n\ +}\n\ +.replacedSideArrows {\n\ + float: left;\n\ +}\n\ .hide-thread-button:not(:hover),\n\ .hide-reply-button:not(:hover) {\n\ opacity: 0.4;\n\ @@ -2369,19 +2855,41 @@ input[name=\"Default Volume\"] {\n\ }\n\ .hide-thread-button {\n\ margin-top: -1px;\n\ + width: 11px;\n\ }\n\ -.stub ~ * {\n\ +.stub ~ :not(.threadDivider) {\n\ display: none !important;\n\ }\n\ .stub input {\n\ display: inline-block;\n\ }\n\ -.thread[hidden] + hr {\n\ +$site$thread[hidden] + hr {\n\ + display: none;\n\ +}\n\ +:root.reply-hide $site$sideArrows {\n\ display: none;\n\ }\n\ -:root.reply-hide div.sideArrows {\n\ +:root.sw-yotsuba.thread-hide .party-hat {\n\ + left: 19px;\n\ +}\n\ +/* Anonymize */\n\ +:root.anonymize $site$info$name,\n\ +:root.sw-yotsuba.anonymize .post-author:not([class*=capcode]) {\n\ + font-size: 0;\n\ +}\n\ +:root.anonymize $site$info$tripcode,\n\ +:root.sw-yotsuba.anonymize .n-pu {\n\ display: none;\n\ }\n\ +:root.anonymize $site$info$name::before,\n\ +:root.sw-yotsuba.anonymize .post-author:not([class*=capcode])::before {\n\ + content: \"Anonymous\";\n\ + font-size: 10pt;\n\ +}\n\ +:root.sw-yotsuba.anonymize .flashListing .name::before,\n\ +:root.sw-yotsuba.anonymize .post-last > .post-author:not([class*=capcode])::before {\n\ + font-size: 9pt;\n\ +}\n\ /* QR */\n\ :root.hide-original-post-form #togglePostFormLink,\n\ #qr.autohide:not(.focus):not(:hover):not(:active) > form,\n\ @@ -2464,8 +2972,8 @@ input[name=\"Default Volume\"] {\n\ #qr.reply-to-thread input[data-name=\"sub\"]:not(.force-show),\n\ body:not(.board_f) #qr select[name=\"filetag\"],\n\ #qr.reply-to-thread select[name=\"filetag\"],\n\ -body:not(.board_jp) #sjis-toggle,\n\ -body:not(.board_sci) #tex-preview-button,\n\ +#qr:not(.has-sjis) #sjis-toggle,\n\ +#qr:not(.has-math) #tex-preview-button,\n\ #qr.tex-preview .textarea > :not(#tex-preview),\n\ #qr:not(.tex-preview) #tex-preview {\n\ display: none;\n\ @@ -2506,11 +3014,12 @@ input.field.tripped:not(:hover):not(:focus) {\n\ text-shadow: none !important;\n\ }\n\ #qr textarea {\n\ - min-width: 100%;\n\ + min-width: 300px;\n\ resize: both;\n\ }\n\ .field {\n\ -moz-box-sizing: border-box;\n\ + box-sizing: border-box;\n\ margin: 0px;\n\ padding: 2px 4px 3px;\n\ }\n\ @@ -2518,22 +3027,6 @@ input.field.tripped:not(:hover):not(:focus) {\n\ position: relative;\n\ top: 2px;\n\ }\n\ -/* Recaptcha v1 */\n\ -.captcha-img {\n\ - margin: 0px;\n\ - text-align: center;\n\ - background-image: #fff;\n\ - font-size: 0px;\n\ - min-height: 59px;\n\ - min-width: 302px;\n\ -}\n\ -.captcha-input {\n\ - width: 100%;\n\ - margin: 1px 0 0;\n\ -}\n\ -#qr.captcha-v1 #qr-captcha-iframe {\n\ - display: none;\n\ -}\n\ /* Recaptcha v2 */\n\ #qr .captcha-root {\n\ position: relative;\n\ @@ -2542,14 +3035,18 @@ input.field.tripped:not(:hover):not(:focus) {\n\ margin: auto;\n\ width: 304px;\n\ }\n\ -/* scrollable with scroll bar hidden; prevents scroll on space press */\n\ -:root.ua-blink #qr .captcha-container > div {\n\ +/* XXX scrollable with scroll bar hidden; prevents scroll on space press */\n\ +:root.ua-blink #qr .captcha-container > div,\n\ +:root.ua-edge #qr .captcha-container > div {\n\ overflow: hidden;\n\ }\n\ -:root.ua-blink #qr .captcha-container > div > div:first-of-type {\n\ +:root.ua-blink #qr .captcha-container > div > div:first-of-type,\n\ +:root.ua-edge #qr .captcha-container > div > div:first-of-type {\n\ overflow-y: scroll;\n\ overflow-x: hidden;\n\ - padding-right: 15px;\n\ + padding-right: 30px;\n\ + height: 99%;\n\ + width: 100%;\n\ }\n\ #qr .captcha-counter {\n\ display: block;\n\ @@ -2563,6 +3060,7 @@ input.field.tripped:not(:hover):not(:focus) {\n\ }\n\ #qr .captcha-counter > a {\n\ pointer-events: auto;\n\ + display: inline-block; /* XXX https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8851747/ */\n\ }\n\ #qr:not(.captcha-open) .captcha-counter > a {\n\ display: block;\n\ @@ -2776,6 +3274,7 @@ input[type=\"checkbox\"]:checked ~ .checkbox-letter {\n\ }\n\ .qr-preview {\n\ -moz-box-sizing: border-box;\n\ + box-sizing: border-box;\n\ counter-increment: thumbnails;\n\ cursor: move;\n\ display: inline-block;\n\ @@ -2856,7 +3355,8 @@ a:only-of-type > .remove {\n\ position: absolute;\n\ bottom: 20px;\n\ right: 10px;\n\ - -moz-transform: translateY(-50%);\n\ + -webkit-transform: translateY(-50%);\n\ + transform: translateY(-50%);\n\ }\n\ .textarea {\n\ position: relative;\n\ @@ -2875,6 +3375,13 @@ a:only-of-type > .remove {\n\ #char-count.warning {\n\ color: red;\n\ }\n\ +#split-post {\n\ + font-size: 8pt;\n\ + position: absolute;\n\ + bottom: 2px;\n\ + left: 2px;\n\ + cursor: pointer;\n\ +}\n\ /* Menu */\n\ .menu-button:not(.fa-bars) {\n\ display: inline-block;\n\ @@ -2889,7 +3396,7 @@ a:only-of-type > .remove {\n\ margin: 2px;\n\ vertical-align: middle;\n\ }\n\ -.post .menu-button,\n\ +.postInfo > .menu-button,\n\ #thread-watcher .menu-button {\n\ width: 18px;\n\ height: 15px;\n\ @@ -2898,6 +3405,7 @@ a:only-of-type > .remove {\n\ #menu {\n\ position: fixed;\n\ outline: none;\n\ + font-weight: normal;\n\ }\n\ #menu, .submenu {\n\ border-radius: 3px;\n\ @@ -2981,6 +3489,9 @@ a:only-of-type > .remove {\n\ cursor: text !important;\n\ }\n\ /* Embedding */\n\ +.embedder:not(.embedded) > span {\n\ + display: none;\n\ +}\n\ #embedding {\n\ padding: 1px 4px 1px 4px;\n\ position: fixed;\n\ @@ -3116,6 +3627,10 @@ a:only-of-type > .remove {\n\ overflow-x: scroll !important;\n\ }\n\ .gal-image a {\n\ + display: -webkit-flex;\n\ + display: flex;\n\ + -webkit-align-items: flex-start;\n\ + align-items: flex-start;\n\ margin: auto;\n\ line-height: 0;\n\ max-width: 100%;\n\ @@ -3124,6 +3639,11 @@ a:only-of-type > .remove {\n\ width: 100%;\n\ height: 100%;\n\ }\n\ +.gal-image img,\n\ +.gal-image video {\n\ + -webkit-flex: none;\n\ + flex: none;\n\ +}\n\ .gal-fit-width .gal-image img,\n\ .gal-fit-width .gal-image video {\n\ max-width: 100%;\n\ @@ -3180,59 +3700,86 @@ a:only-of-type > .remove {\n\ bottom: 2px;\n\ vertical-align: baseline;\n\ }\n\ -.gal-buttons,\n\ -.gal-name,\n\ -.gal-count {\n\ +.gal-labels {\n\ position: fixed;\n\ - right: 195px;\n\ + bottom: 6px;\n\ + display: -webkit-flex;\n\ + display: flex;\n\ + -webkit-flex-direction: column;\n\ + flex-direction: column;\n\ + -webkit-align-items: flex-end;\n\ + align-items: flex-end;\n\ }\n\ -.gal-hide-thumbnails .gal-buttons,\n\ -.gal-hide-thumbnails .gal-count,\n\ -.gal-hide-thumbnails .gal-name {\n\ - right: 44px;\n\ +:root:not(.show-sauce) .gal-sauce {\n\ + display: none;\n\ }\n\ -.gal-name {\n\ - bottom: 6px;\n\ +.gal-name,\n\ +.gal-count,\n\ +.gal-sauce {\n\ background: rgba(0,0,0,0.6) !important;\n\ border-radius: 3px;\n\ padding: 1px 5px 2px 5px;\n\ + margin-top: 3px;\n\ + color: #ffffff !important;\n\ text-decoration: none !important;\n\ - color: white !important;\n\ +}\n\ +.gal-sauce a {\n\ + color: #ffffff !important;\n\ }\n\ .gal-name:hover,\n\ -.gal-buttons a:hover {\n\ +.gal-buttons a:hover,\n\ +.gal-sauce a:hover {\n\ color: rgb(95, 95, 101) !important;\n\ }\n\ :root.gal-pdf .gal-buttons a:hover {\n\ color: rgb(204, 204, 204) !important;\n\ }\n\ -.gal-count {\n\ - bottom: 27px;\n\ - background: rgba(0,0,0,0.6) !important;\n\ - border-radius: 3px;\n\ - padding: 1px 5px 2px 5px;\n\ - color: #ffffff !important;\n\ +.gal-buttons,\n\ +.gal-labels {\n\ + position: fixed;\n\ + right: 195px;\n\ }\n\ -:root:not(.gal-fit-width):not(.gal-pdf) .gal-name {\n\ - bottom: 23px !important;\n\ +.gal-hide-thumbnails .gal-buttons,\n\ +.gal-hide-thumbnails .gal-labels {\n\ + right: 44px;\n\ }\n\ -:root:not(.gal-fit-width):not(.gal-pdf) .gal-count {\n\ - bottom: 44px !important;\n\ +:root:not(.gal-fit-width):not(.gal-pdf) .gal-labels {\n\ + bottom: 23px !important;\n\ }\n\ :root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-buttons,\n\ -:root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-name,\n\ -:root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-count {\n\ +:root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-labels {\n\ right: 178px !important;\n\ }\n\ -:root.gal-hide-thumbnails:.gal-fit-height:not(.gal-pdf) .gal-buttons,\n\ -:root.gal-hide-thumbnails:.gal-fit-height:not(.gal-pdf) .gal-name,\n\ -:root.gal-hide-thumbnails:.gal-fit-height:not(.gal-pdf) .gal-count {\n\ +:root.gal-hide-thumbnails.gal-fit-height:not(.gal-pdf) .gal-buttons,\n\ +:root.gal-hide-thumbnails.gal-fit-height:not(.gal-pdf) .gal-labels {\n\ right: 28px !important;\n\ }\n\ :root.gallery-open.fixed #header-bar:not(.autohide),\n\ :root.gallery-open.fixed #header-bar:not(.autohide) #shortcuts .fa::before {\n\ visibility: hidden;\n\ }\n\ +/* Mod Contact Links */\n\ +.contact-links {\n\ + margin-left: 2px;\n\ +}\n\ +.move-note > a {\n\ + text-decoration: underline;\n\ +}\n\ +.invisible {\n\ + font-size: 0;\n\ +}\n\ +/* PostJumper */\n\ +.postJumper > .prev,\n\ +.postJumper > .next {\n\ + font-size: 120%;\n\ +}\n\ +/* PSA */\n\ +.fcx-announcement {\n\ + text-align: center;\n\ +}\n\ +.fcx-announcement a {\n\ + text-decoration: underline;\n\ +}\n\ /* General */\n\ :root.yotsuba .dialog {\n\ background-color: #F0E0D6;\n\ @@ -3242,6 +3789,13 @@ a:only-of-type > .remove {\n\ :root.yotsuba .field.focus {\n\ border-color: #EA8;\n\ }\n\ +/* 4chan style fixes */\n\ +:root.yotsuba.highlight-you .quotesYou$site$highlightable$reply {\n\ + border-left: 3px solid rgba(221, 0, 0, .8) !important;\n\ +}\n\ +:root.yotsuba.highlight-own .yourPost$site$highlightable$reply {\n\ + border-left: 3px dashed rgba(221, 0, 0, .8) !important;\n\ +}\n\ /* Header */\n\ :root.yotsuba #header-bar.dialog {\n\ background-color: rgba(240,224,214,0.98);\n\ @@ -3262,6 +3816,16 @@ a:only-of-type > .remove {\n\ :root.yotsuba .suboption-list > div:last-of-type {\n\ background-color: #F0E0D6;\n\ }\n\ +/* Catalog */\n\ +:root.yotsuba.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #F0E0D6;\n\ +}\n\ +:root.yotsuba.werkTyme .catalog-thread:not(:hover),\n\ +:root.yotsuba.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.yotsuba.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.yotsuba.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #D9BFB7;\n\ +}\n\ /* Quote */\n\ :root.yotsuba .backlink.deadlink {\n\ color: #00E !important;\n\ @@ -3299,8 +3863,12 @@ a:only-of-type > .remove {\n\ :root.yotsuba .focused.entry {\n\ background: rgba(255, 255, 255, .33);\n\ }\n\ +/* Unread */\n\ +:root.yotsuba .unread-mark-read {\n\ + background-color: rgba(240,224,214,0.5);\n\ +}\n\ /* Thread Watcher */\n\ -:root.yotsuba .replies-quoting-you > a, :root.yotsuba #watcher-link.disabled.replies-quoting-you {\n\ +:root.yotsuba .replies-quoting-you > a, :root.yotsuba #watcher-link.replies-quoting-you, :root.yotsuba .last-page > a > .watcher-page {\n\ color: #F00;\n\ }\n\ /* Watcher Favicon */\n\ @@ -3317,6 +3885,13 @@ a:only-of-type > .remove {\n\ :root.yotsuba-b .field.focus {\n\ border-color: #98E;\n\ }\n\ +/* 4chan style fixes */\n\ +:root.yotsuba-b.highlight-you .quotesYou$site$highlightable$reply {\n\ + border-left: 3px solid rgba(221, 0, 0, .8) !important;\n\ +}\n\ +:root.yotsuba-b.highlight-own .yourPost$site$highlightable$reply {\n\ + border-left: 3px dashed rgba(221, 0, 0, .8) !important;\n\ +}\n\ /* Header */\n\ :root.yotsuba-b #header-bar.dialog {\n\ background-color: rgba(214,218,240,0.98);\n\ @@ -3337,6 +3912,16 @@ a:only-of-type > .remove {\n\ :root.yotsuba-b .suboption-list > div:last-of-type {\n\ background-color: #D6DAF0;\n\ }\n\ +/* Catalog */\n\ +:root.yotsuba-b.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #D6DAF0;\n\ +}\n\ +:root.yotsuba-b.werkTyme .catalog-thread:not(:hover),\n\ +:root.yotsuba-b.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.yotsuba-b.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.yotsuba-b.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #B7C5D9;\n\ +}\n\ /* Quote */\n\ :root.yotsuba-b .backlink.deadlink {\n\ color: #34345C !important;\n\ @@ -3374,8 +3959,12 @@ a:only-of-type > .remove {\n\ :root.yotsuba-b .focused.entry {\n\ background: rgba(255, 255, 255, .33);\n\ }\n\ +/* Unread */\n\ +:root.yotsuba-b .unread-mark-read {\n\ + background-color: rgba(214,218,240,0.5);\n\ +}\n\ /* Thread Watcher */\n\ -:root.yotsuba-b .replies-quoting-you > a, :root.yotsuba-b #watcher-link.disabled.replies-quoting-you {\n\ +:root.yotsuba-b .replies-quoting-you > a, :root.yotsuba-b #watcher-link.replies-quoting-you {\n\ color: #F00;\n\ }\n\ /* Watcher Favicon */\n\ @@ -3412,6 +4001,16 @@ a:only-of-type > .remove {\n\ :root.futaba .suboption-list > div:last-of-type {\n\ background-color: #F0E0D6;\n\ }\n\ +/* Catalog */\n\ +:root.futaba.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #F0E0D6;\n\ +}\n\ +:root.futaba.werkTyme .catalog-thread:not(:hover),\n\ +:root.futaba.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.futaba.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.futaba.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #D9BFB7;\n\ +}\n\ /* Quote */\n\ :root.futaba .backlink.deadlink {\n\ color: #00E !important;\n\ @@ -3424,6 +4023,10 @@ a:only-of-type > .remove {\n\ :root.futaba .indicator {\n\ color: #F0E0D6;\n\ }\n\ +/* Anonymize */\n\ +:root.futaba.anonymize $site$info$name::before {\n\ + font-size: 12pt;\n\ +}\n\ /* QR */\n\ .futaba #dump-list::-webkit-scrollbar-thumb {\n\ background-color: #F0E0D6;\n\ @@ -3449,8 +4052,12 @@ a:only-of-type > .remove {\n\ :root.futaba .focused.entry {\n\ background: rgba(255, 255, 255, .33);\n\ }\n\ +/* Unread */\n\ +:root.futaba .unread-mark-read {\n\ + background-color: rgba(240,224,214,0.5);\n\ +}\n\ /* Thread Watcher */\n\ -:root.futaba .replies-quoting-you > a, :root.futaba #watcher-link.disabled.replies-quoting-you {\n\ +:root.futaba .replies-quoting-you > a, :root.futaba #watcher-link.replies-quoting-you, :root.futaba .last-page > a > .watcher-page {\n\ color: #F00;\n\ }\n\ /* Watcher Favicon */\n\ @@ -3487,6 +4094,16 @@ a:only-of-type > .remove {\n\ :root.burichan .suboption-list > div:last-of-type {\n\ background-color: #D6DAF0;\n\ }\n\ +/* Catalog */\n\ +:root.burichan.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #D6DAF0;\n\ +}\n\ +:root.burichan.werkTyme .catalog-thread:not(:hover),\n\ +:root.burichan.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.burichan.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.burichan.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #B7C5D9;\n\ +}\n\ /* Quote */\n\ :root.burichan .backlink.deadlink {\n\ color: #34345C !important;\n\ @@ -3499,6 +4116,10 @@ a:only-of-type > .remove {\n\ :root.burichan .indicator {\n\ color: #D6DAF0;\n\ }\n\ +/* Anonymize */\n\ +:root.burichan.anonymize $site$info$name::before {\n\ + font-size: 12pt;\n\ +}\n\ /* QR */\n\ .burichan #dump-list::-webkit-scrollbar-thumb {\n\ background-color: #D6DAF0;\n\ @@ -3524,8 +4145,12 @@ a:only-of-type > .remove {\n\ :root.burichan .focused.entry {\n\ background: rgba(255, 255, 255, .33);\n\ }\n\ +/* Unread */\n\ +:root.burichan .unread-mark-read {\n\ + background-color: rgba(214,218,240,0.5);\n\ +}\n\ /* Thread Watcher */\n\ -:root.burichan .replies-quoting-you > a, :root.burichan #watcher-link.disabled.replies-quoting-you {\n\ +:root.burichan .replies-quoting-you > a, :root.burichan #watcher-link.replies-quoting-you, :root.burichan .last-page > a > .watcher-page {\n\ color: #F00;\n\ }\n\ /* Watcher Favicon */\n\ @@ -3538,6 +4163,16 @@ a:only-of-type > .remove {\n\ background-color: #282A2E;\n\ border-color: #111;\n\ }\n\ +/* 4chan style fixes */\n\ +:root.tomorrow #arc-list span.quote {\n\ + color: #B5BD68;\n\ +}\n\ +:root.tomorrow.highlight-you .quotesYou$site$highlightable$reply {\n\ + border-left: 3px solid rgba(145, 182, 214, .8) !important;\n\ +}\n\ +:root.tomorrow.highlight-own .yourPost$site$highlightable$reply {\n\ + border-left: 3px dashed rgba(145, 182, 214, .8) !important;\n\ +}\n\ /* Header */\n\ :root.tomorrow #header-bar.dialog {\n\ background-color: rgba(40,42,46,0.9);\n\ @@ -3562,13 +4197,16 @@ a:only-of-type > .remove {\n\ background-color: #282A2E;\n\ }\n\ /* Catalog */\n\ -:root.tomorrow .catalog-code {\n\ - background-color: rgba(255, 255, 255, 0.1);\n\ +:root.tomorrow.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #282A2E;\n\ }\n\ -/* Quote */\n\ -:root.tomorrow .catalog-thread > .comment > span.quote, :root.tomorrow #arc-list span.quote {\n\ - color: #B5BD68;\n\ +:root.tomorrow.werkTyme .catalog-thread:not(:hover),\n\ +:root.tomorrow.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.tomorrow.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.tomorrow.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #111;\n\ }\n\ +/* Quote */\n\ :root.tomorrow .backlink.deadlink {\n\ color: #81A2BE !important;\n\ }\n\ @@ -3584,29 +4222,33 @@ a:only-of-type > .remove {\n\ :root.tomorrow .qphl {\n\ outline: 2px solid rgba(145, 182, 214, .8);\n\ }\n\ -:root.tomorrow.highlight-you .quotesYou.opContainer,\n\ -:root.tomorrow.highlight-you .quotesYou > .reply {\n\ +:root.tomorrow.highlight-you .quotesYou$site$highlightable$op,\n\ +:root.tomorrow.highlight-you .quotesYou$site$highlightable$reply {\n\ border-left: 3px solid rgba(145, 182, 214, .8);\n\ }\n\ -:root.tomorrow.highlight-own .yourPost.opContainer,\n\ -:root.tomorrow.highlight-own .yourPost > .reply {\n\ +:root.tomorrow.highlight-own .yourPost$site$highlightable$op,\n\ +:root.tomorrow.highlight-own .yourPost$site$highlightable$reply {\n\ border-left: 3px dashed rgba(145, 182, 214, .8);\n\ }\n\ -:root.tomorrow .opContainer.filter-highlight,\n\ -:root.tomorrow .filter-highlight > .reply {\n\ +:root.tomorrow .filter-highlight$site$highlightable$op,\n\ +:root.tomorrow .filter-highlight$site$highlightable$reply {\n\ box-shadow: inset 5px 0 rgba(145, 182, 214, .5);\n\ }\n\ -:root.tomorrow.highlight-own .yourPost > div.sideArrows,\n\ -:root.tomorrow.highlight-you .quotesYou > div.sideArrows,\n\ -:root.tomorrow .filter-highlight > div.sideArrows {\n\ +:root.tomorrow.highlight-own .yourPost > $site$sideArrows,\n\ +:root.tomorrow.highlight-you .quotesYou > $site$sideArrows,\n\ +:root.tomorrow .filter-highlight > $site$sideArrows {\n\ color: rgb(155, 185, 210);\n\ }\n\ -:root.tomorrow .filter-highlight .catalog-thumb,\n\ -:root.tomorrow .filter-highlight .werkTyme-filename {\n\ +:root.tomorrow .catalog-thread.filter-highlight .catalog-thumb,\n\ +:root.tomorrow.werkTyme .catalog-thread.filter-highlight:not(:hover),\n\ +:root.tomorrow.werkTyme:not(.catalog-hover-expand) .catalog-thread.filter-highlight,\n\ +:root.tomorrow.werkTyme.catalog-hover-expand .catalog-thread.filter-highlight > .catalog-container:hover > .catalog-post {\n\ box-shadow: 0 0 3px 3px rgba(64, 192, 255, .7);\n\ }\n\ :root.tomorrow .catalog-thread.watched .catalog-thumb,\n\ -:root.tomorrow .catalog-thread.watched .werkTyme-filename {\n\ +:root.tomorrow.werkTyme .catalog-thread.watched:not(:hover),\n\ +:root.tomorrow.werkTyme:not(.catalog-hover-expand) .catalog-thread.watched,\n\ +:root.tomorrow.werkTyme.catalog-hover-expand .catalog-thread.watched > .catalog-container:hover > .catalog-post {\n\ border: 2px solid rgb(64, 192, 255);\n\ }\n\ /* QR */\n\ @@ -3669,11 +4311,14 @@ a:only-of-type > .remove {\n\ background: rgba(0, 0, 0, .33);\n\ }\n\ /* Unread */\n\ -:root.tomorrow #unread-line {\n\ +:root.tomorrow .unread-line {\n\ border-color: rgb(197, 200, 198);\n\ }\n\ +:root.tomorrow .unread-mark-read {\n\ + background-color: rgba(40,42,46,0.5);\n\ +}\n\ /* Thread Watcher */\n\ -:root.tomorrow .replies-quoting-you > a, :root.tomorrow #watcher-link.disabled.replies-quoting-you {\n\ +:root.tomorrow .replies-quoting-you > a, :root.tomorrow #watcher-link.replies-quoting-you, :root.tomorrow .last-page > a > .watcher-page {\n\ color: #F00 !important;\n\ }\n\ /* Watcher Favicon */\n\ @@ -3690,6 +4335,16 @@ a:only-of-type > .remove {\n\ :root.photon .field.focus {\n\ border-color: #EA8;\n\ }\n\ +/* 4chan style fixes */\n\ +:root.photon #arc-list tr:nth-of-type(odd) span.quote {\n\ + color: #C0E17A;\n\ +}\n\ +:root.photon.highlight-you .quotesYou$site$highlightable$reply {\n\ + border-left: 3px solid rgba(221, 0, 0, .8) !important;\n\ +}\n\ +:root.photon.highlight-own .yourPost$site$highlightable$reply {\n\ + border-left: 3px dashed rgba(221, 0, 0, .8) !important;\n\ +}\n\ /* Header */\n\ :root.photon #header-bar.dialog {\n\ background-color: rgba(221,221,221,0.98);\n\ @@ -3711,13 +4366,16 @@ a:only-of-type > .remove {\n\ background-color: #DDD;\n\ }\n\ /* Catalog */\n\ -:root.photon .catalog-code {\n\ - background-color: rgba(150, 150, 150, 0.2);\n\ +:root.photon.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #DDD;\n\ }\n\ -/* Quote */\n\ -:root.photon #arc-list tr:nth-of-type(odd) span.quote {\n\ - color: #C0E17A;\n\ +:root.photon.werkTyme .catalog-thread:not(:hover),\n\ +:root.photon.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.photon.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.photon.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #CCC;\n\ }\n\ +/* Quote */\n\ :root.photon .backlink.deadlink {\n\ color: #F60 !important;\n\ }\n\ @@ -3754,8 +4412,12 @@ a:only-of-type > .remove {\n\ :root.photon .focused.entry {\n\ background: rgba(255, 255, 255, .33);\n\ }\n\ +/* Unread */\n\ +:root.photon .unread-mark-read {\n\ + background-color: rgba(221,221,221,0.5);\n\ +}\n\ /* Thread Watcher */\n\ -:root.photon .replies-quoting-you > a, :root.photon #watcher-link.disabled.replies-quoting-you {\n\ +:root.photon .replies-quoting-you > a, :root.photon #watcher-link.replies-quoting-you, :root.photon .last-page > a > .watcher-page {\n\ color: #00F !important;\n\ }\n\ /* Watcher Favicon */\n\ @@ -3763,72 +4425,271 @@ a:only-of-type > .remove {\n\ {\n\ background-image: url(\"data:image/svg+xml,\");\n\ }\n\ +/* General */\n\ +:root.spooky .dialog {\n\ + background-color: #171526;\n\ + border-color: #707070;\n\ +}\n\ +:root.spooky .field:focus,\n\ +:root.spooky .field.focus {\n\ + border-color: #98E;\n\ +}\n\ +/* 4chan style fixes */\n\ +:root.spooky #arc-list span.quote {\n\ + color: #634C2C;\n\ +}\n\ +:root.spooky.highlight-you .quotesYou$site$highlightable$reply {\n\ + border-left: 3px solid rgba(145, 182, 214, .8) !important;\n\ +}\n\ +:root.spooky.highlight-own .yourPost$site$highlightable$reply {\n\ + border-left: 3px dashed rgba(145, 182, 214, .8) !important;\n\ +}\n\ +/* Header */\n\ +:root.spooky #header-bar.dialog {\n\ + background-color: rgba(23,21,38,0.98);\n\ +}\n\ +:root.spooky:not(.fixed) #header-bar, :root.spooky #notifications {\n\ + font-size: 9pt;\n\ +}\n\ +:root.spooky #header-bar, :root.spooky #notifications {\n\ + color: #C49756;\n\ +}\n\ +:root.spooky #board-list a, :root.spooky #shortcuts a {\n\ + color: #FE9600;\n\ +}\n\ +:root.spooky.shortcut-icons .native-settings {\n\ + background-image: url('//s.4cdn.org/image/favicon-ws.ico');\n\ +}\n\ +/* Settings */\n\ +:root.spooky #fourchanx-settings fieldset, :root.spooky .section-main div::before {\n\ + border-color: #707070;\n\ +}\n\ +:root.spooky .suboption-list > div:last-of-type {\n\ + background-color: #171526;\n\ +}\n\ +/* Catalog */\n\ +:root.spooky.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #171526;\n\ +}\n\ +:root.spooky.werkTyme .catalog-thread:not(:hover),\n\ +:root.spooky.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.spooky.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.spooky.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #707070;\n\ +}\n\ +/* Quote */\n\ +:root.spooky .backlink.deadlink {\n\ + color: #FE9600 !important;\n\ +}\n\ +:root.spooky .inline {\n\ + border-color: #707070;\n\ + background-color: rgba(255, 255, 255, .14);\n\ +}\n\ +/* Fappe and Werk Tyme */\n\ +:root.spooky .indicator {\n\ + color: #171526;\n\ +}\n\ +/* Highlighting */\n\ +:root.spooky .qphl {\n\ + outline: 2px solid rgba(145, 182, 214, .8);\n\ +}\n\ +:root.spooky.highlight-you .quotesYou$site$highlightable$op,\n\ +:root.spooky.highlight-you .quotesYou$site$highlightable$reply {\n\ + border-left: 3px solid rgba(145, 182, 214, .8);\n\ +}\n\ +:root.spooky.highlight-own .yourPost$site$highlightable$op,\n\ +:root.spooky.highlight-own .yourPost$site$highlightable$reply {\n\ + border-left: 3px dashed rgba(145, 182, 214, .8);\n\ +}\n\ +:root.spooky .filter-highlight$site$highlightable$op,\n\ +:root.spooky .filter-highlight$site$highlightable$reply {\n\ + box-shadow: inset 5px 0 rgba(145, 182, 214, .5);\n\ +}\n\ +:root.spooky.highlight-own .yourPost > $site$sideArrows,\n\ +:root.spooky.highlight-you .quotesYou > $site$sideArrows,\n\ +:root.spooky .filter-highlight > $site$sideArrows {\n\ + color: rgb(155, 185, 210);\n\ +}\n\ +/* QR */\n\ +.spooky #dump-list::-webkit-scrollbar-thumb {\n\ + background-color: #171526;\n\ + border-color: #707070;\n\ +}\n\ +:root.spooky .qr-preview {\n\ + background-color: rgba(0, 0, 0, .15);\n\ +}\n\ +:root.spooky #qr .field {\n\ + background-color: rgb(26, 27, 29);\n\ + color: rgb(197,200,198);\n\ + border-color: rgb(40, 41, 42);\n\ +}\n\ +:root.spooky #qr .field:focus,\n\ +:root.spooky #qr .field.focus {\n\ + border-color: rgb(254, 150, 0) !important;\n\ + background-color: rgb(30,32,36);\n\ +}\n\ +:root.spooky .persona button {\n\ + background: linear-gradient(to bottom, #2E3035, #222427) no-repeat;\n\ + color: rgb(197,200,198);\n\ + border-color: rgb(40, 41, 42);\n\ + outline: none;\n\ +}\n\ +:root.spooky .persona button::-moz-focus-inner {\n\ + border: none;\n\ +}\n\ +:root.spooky .persona button:focus {\n\ + border-color: rgb(254, 150, 0);\n\ +}\n\ +:root.spooky #qr.sjis-preview #sjis-toggle,\n\ +:root.spooky #qr.tex-preview #tex-preview-button {\n\ + background: rgb(26, 27, 29);\n\ +}\n\ +:root.spooky #qr select,\n\ +:root.spooky #file-n-submit > input,\n\ +:root.spooky #qr-draw-button {\n\ + border-color: rgb(40, 41, 42);\n\ +}\n\ +:root.spooky #qr-filename {\n\ + color: rgb(197,200,198);\n\ +}\n\ +:root.spooky .qr-link {\n\ + border-color: rgb(8, 6, 23) rgb(8, 6, 23) rgb(0, 0, 8);\n\ + background: linear-gradient(#262435, #171526) repeat scroll 0% 0% transparent;\n\ +}\n\ +:root.spooky .qr-link:hover {\n\ + background: #1A1829;\n\ +}\n\ +/* Menu */\n\ +:root.spooky #menu {\n\ + color: #FE9600;\n\ +}\n\ +:root.spooky .entry {\n\ + font-size: 10pt;\n\ +}\n\ +:root.spooky .focused.entry {\n\ + background: rgba(255, 255, 255, .33);\n\ +}\n\ +/* Unread */\n\ +:root.spooky .unread-line {\n\ + border-color: rgb(197, 200, 198);\n\ + visibility: visible;\n\ + opacity: 1;\n\ +}\n\ +:root.spooky .unread-mark-read {\n\ + background-color: rgba(23,21,38,0.5);\n\ +}\n\ +/* Thread Watcher */\n\ +:root.spooky .replies-quoting-you > a, :root.spooky #watcher-link.replies-quoting-you, :root.spooky .last-page > a > .watcher-page {\n\ + color: #F00 !important;\n\ +}\n\ +/* Watcher Favicon */\n\ +:root.spooky .watch-thread-link\n\ +{\n\ + background-image: url(\"data:image/svg+xml,\");\n\ +}\n\ /* Link Title Favicons */\n\ -.linkify.audio {\n\ +.linkify.audio::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAitJREFUOE9jYCAWKJWwavr0KyXWb/FIbDtUFFyzJx6nVofE2Xo5nXsj0rqPNSR0nVkR2Hjmgmfd+U9Otdf+m5Vf/6+SfeU/R9ChVVgNYDRtlfJuuPA/rPfe/4QpD/6nznj0P27Kw/9unff/69Xf+69c/+C/SO7N/0z+OAxgMmmRCe++/r9i3ev/KWvf/vdY8PK/bt/9/wrNV3/IN5y/IVt1YqNg4pGTTP4HsbuA2bhZ2qvpyn+xjIObxAp3VwqlrgngLFyryVy5nhPmZJHANS2cwYexG8BmVC/pWn3hP4NZlzWuQDJI3dIiFnUUuwEsQAOcq87jNcC7fHeLUtJxHF4AGmBWeAavAWH1+1rUUk7giAWjOknllON4DXAs2NEiG4/DBQxAF/CFHfrPYI4jDFSLuJVjNrUJhB/B7gIGo1pJRt99GAZYJK7wLJ1z7Xzl4vu/7aqv/GRBj0bjqAX2qb0nJ7mXH17C4HcUxQA+hymWtSue/C5a9up/9Ozn/7Vr7v1nRY7GqMb91T3b3v6vWvPmf/S0p/9ZQk+DDLCBRSOz06Jqk+o7/21nvfqvsebDf7kZL/5zBaxphkezd+OFn7HzXvz3Wvjmv9a8N//5Ek//ZTBpVYUrMG2X5wjcdl68+uI/wa5Lr3hSNjczGFeywOVZ/bbcVGp//F9izfv/Ql03f3P4LC/HSEQquYwMFnUCDJ7dzBhyjGZNQpye89M5gpfnMvtNUyE2h4PUAQBovvT7lyNljwAAAABJRU5ErkJggg==') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.clyp {\n\ +.linkify.bitchute::before {\n\ + content: \"\";\n\ + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAadQTFRFAAAAwzw8xDs7cY6O0iws0ysrtF9f0Sws1CwsyzU1zTIy1igoyzQ01icnY7i4t0hI0S4u0ysr1Soq1ikp1ikp1Soq0ysr0S4uu0VFzjEx1Csr1ygo2Ccn2Ccn1ygo1CoqzjExzjAw1Skp2Ccn2Ccn1ikp0TAwxzY21Soq1SoqyzQ00iws1ygo1ygo0yws1Soq1Skp1igo1igo1igo1igo1igo1igo1Coq1Soq0C0t1ygo1ygo0S4unV5e1Csr1ygo1ygo1CsruUdHxzg41Skp1ygo1CsryTU10C4u1igo1ycn1ygo0i0txjo60S0t1ikp1ygo1ikp1Cws1Coq1Coq0yws0S0tyzQ00iws1Soq0ysr0i0txDs72Ccn2CUl2CYm2CQk3EFB2S8v2zw82jY24FZW3D0931FR3EBA3UND8LS04FVV7qys4V9f4WBg+erq766u9t7e7qqq2Ckp54KC9+Pj6pSU+Ojo5XNz9NHR6YqK8bu765ub5G5u9M3N6ImJ88vL5XV165eX3UVF6pWV3UhI2Soq2jU12Coq2jQ02Cgo2Sws////FaxLuAAAAF10Uk5TAAAAAAAAAAAAAAAAAAAAASJnoLy9oWolAhBz1vr72XgTGKf8/a4cCpuiDVvz9mS6xOvy9vzg6aGsPOToRAFv9fh2Awm07XgIMd765UEDOsfemVhhY00nBommbCkEI8horgAAAL5JREFUKBUFwbFKA0EUQNF7387sMq4EmzRpLSSdIBYKFv6Af2prnSYkRT4gWFgkCBJQ0EIFdcZzBCeqqh4qdk7VW2ChPusw02sAYKU7z7wEAAA2piQKFbrWSHazc1J0XWs5pdxPDykcVX+7Y9UxUsSo+s7PibqPFBRV/C5qi4i/UkrJrc7L47Bt4ZWnUaMCAE9GSrtKBQD2fR+bnAEAeOn7dUTOwApe35bDsPz0zsniQlV98IN0tJ3f6P0XAMA/kxou7OXCdnoAAAAASUVORK5CYII=') center left no-repeat!important;\n\ + padding-left: 18px;\n\ +}\n\ +.linkify.clyp::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAwUExURTSY22ey5E2l4KbS75rM7Y3F64C/6f///8zl9nS45r/f9PL5/UGe3bPY8Vqr4v///wNjrzUAAAABYktHRA8YugDZAAAAB3RJTUUH4AINEi85AIH95AAAAE9JREFUCNdjYMAGGBWgDGYHCM2a3hkAZmi0dzSBaKaO9o5moCqmLiCjYzNQyw4QowIodQzI6E0AKcpo72gE6+Jyb1kAMehUA9RktgdYbQYAjGIVNGGXBJkAAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.dailymotion {\n\ +.linkify.dailymotion::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAnUExURQBk3ff6/trp+kKO5wZt3xx54q7P9Ozz/IS17zOG5WKh653E8sbc9/GbbcoAAABZSURBVAjXY2BAASyhDhAGc9oECMOjyAAiESEEYrBYpLWBGcwHxcvBjDDxHelghpF0yDQwY3kVgweEUeEQDWbMEepqAjO8FMsLIeYsU8o+BrbCdWboTAe4AwALXxWGjW41FwAAAABJRU5ErkJggg==') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.gfycat {\n\ +.linkify.gfycat::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAjVBMVEWn3gCo3gSr3w2t4BSu4Bav4Ri35C+45DK45DO55DXA50rA50vB50zC6E/D6FTF6VjG6VvL62vN7G/P7XbQ7XfW74vY8JDa8ZTe8qDe8qLf86Pi9Kzj9K7k9LHp9sDp98Lq98Ps+Mr0++L5/O75/fD6/fH6/fL6/fP7/fT7/fb8/ff8/vj8/vn+/v7///91X4cfAAAAcklEQVR42o3M2xKBUACF4aVQckrIuRJK6H//x2sme4/MuPDfre9i6c/Cc3U5Dj87BuAxsXvGu6JvIIXEHRWwNHCHQNrCzkAFkbSBg4EM8i+Yw7PXBa3zRfuxVyf/Bis7nKwGKAcWxgC8prI5Sc315OlnDfzpDar2S9/oAAAAAElFTkSuQmCC') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.gist {\n\ +.linkify.gist::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABblBMVEXc3NykpKTW1tbb29ugoKCdnZ0AAAACAgIEDRcKCgoMDAwODg4QIzYRDAoTExMUDwwVAg0WICsaEw8aGhoiCBklGxUmERwwKCQ7LSU7Ozs8LSZFLyNINi1JNyxJNy1KSklMOi5VR1FXV1daQTRkZGRseYZwU0F4eHh7dnR8bWV/YE6IdGiKcGCKkJaNgYeNjY2RdGOScWCUcWCZmZmhoaGkpKSoqKirfmaurq6xsbG1tbW6urq+vr7AbmzBb23CwsLGxsbHx8fHyMjJycnJysrMzMzOiYbPi4fQ0NDRoYbT09PU1NTW1tbY2NjZqIzZ2dnb29vd3d3f39/i4uLktZrk5OTl5eXm5ubn5+fo6Ojq6urs7OzttKLu7u7wuqbw8PDx8fHz8/P4+Pj5+fn7uZj8vpz9ya79ybD/tZf/upr/wZ//w6H/xKH/xaL/xrH/yqj/y7T/zqv/z7D/07D/17n/2Lv/2Lz/3L//38n/4Mk3Q/ZuAAAABnRSTlMSFcbGzc5MNKFvAAAA1klEQVQoz2NgYPZHAswMDEwRSclwkBTBxOARn4gE4j0YXBOiJNUDg7y8Ar1UlOITXBkcY73Z2Li42dg42dn4wmIdGeyjQ7nZoEA4PNqewSZKlw0O9KJsGKwjBdl4ZeWkJGQUhNjEIq0ZrMI5+D0ri7Jz8itCRAXCrRgsQ3mUy+xicrPSbfO0REItGSyCVaVL3ONSU9LcCtQUgy0YzIJ85M1LizMzCsv9xF2CzBhMAwN99TV1DI0MtDWcAgNNGUycA5CAswkDi5kDwrMOZiwMjKzGSICVEQDhZj0UQV7PewAAAABJRU5ErkJggg==') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.image {\n\ +.linkify.image::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAs5JREFUOE+lk/tvi1EYx98/xT8gW4REIpGFMEQWl2FiM9ZMZhm2xRAyOsmujFFmdFRHu0tWm87UypxStr69zPauN5e5rHVp3IYhbOvHy+wHEQlxkm+ek+d8nm9OznkeSfrfldmgJC7QyUlTymsJTfuTZ25z4HdWYwyLreYhtpgekGPw0+kKvo1Eo+IXRSIiEhkWZuc9tqnsJD9EqTUopCxjSGTpB0iueczSo1HyW8cpsExQ1DbxI2pt45j9cXpexul4FEd79RnZphAa/SD7WvuFtO6UItbU9LC+YQxNI2w0wwYT5LRAdhOU3oBTIXC9gXP3oUSGgz2vST3gYHejR0jptT1C332f8yrUEYHrz8CgxDnpm6DKCUfc0KnmXa/AEVPPwnDcD0cvetA2uYRk67Ive/lpjO7YBO1PPuF8Df3vwf4cbNE4tqdw7YVq8HYyHx6FvhE1hkMEg8HDUqvFkjT4aIjMqkqyqkswDSrcfBfH+Q561YLAZ/B+BLda6FXlU/cPv0AoEPhuoP1h4Av7Wbh9E/Py15NWWUjeSR3nZDfeN+N0DY9hG/7K1eGP3P0S5/EYRFUF/IOTBrUXHPm9fT6mr1xEwupkZqxbzLyiDJYUZ5NSnkdqdSHpxyrYdFpPgdmAsdfJwPMI/Yr65bf7tZLGGBQ7DNdJWFtIYvoOZmbuZE7OXpIKKli86zAr9p9gTVktWTVnKTI2U95uRWe3U2IJUDbVB5p6hVm5x5m9Vc/cnedZUNzC8lILaQesZBy6hEZ3maKzgvJWFzVWD9XtXvVGQbSWASFtMATVRlJIKbOTWtlJXaeXepuPM1f6MNp9GLt8mLvvYLmp0OhQ2Fwvk6m7xaqDTvY0eYWUVtcnllXfYlGpnfklVuraHHg8HjxuN+6fktUHlWWZPaZeUo/ILK0UKttBcbNbSB9GP0yLxWJJUxoZGUn80zD9C/vXQ/4NHY10h3M1zmQAAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.installgentoo {\n\ +.linkify.installgentoo::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABcVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB3dIYAAAAAAAAAAAAbGh4BBAcCBgoBBgoCBwsCCQ/QzucCCA7MyuXZ1eUBBQmTh8fo5/i9svIAAADh3vQAAAACCA0CCQ8CCQ4DDBQbGCUDChDr6vgAAAAAAAAREBIDCxK6tdfe2fTv7/cDCxIDDBQEDRUHDhgMJjXk4PZdXWdLUFoUNEYOKDgSMUMRLUBneI4eTGj08/QmW3onW3rTzvfOx/giU3IiVHMkWHdEaYJobHv3+PokWHpua6TNy9xZgZ+1quz8/foQKj0XPFInWn0nW38tZ4o6fqg8gq48grA9hrU/i7pAhrNAiLdBjLtEjr1FksNIjr5Il8pImMtKWnNqhL97odKFqti5q/q5rPq60+nCt/vLw/vPx/jV0vHY0/rc1/rg2/vh3fzn4fzu6/vx8vf19Pv19Pz49/v5+Pv8/Pv8/fr9/vv+/frziVtUAAAAT3RSTlMABQYHCAoNDhARGRobL0ZOV1xdXV5fYGBmZnB0eX2MjZSaoaGio6mqqqustLq7zubo6Ojo6evt7u/x8fLy9/f4+Pj5+vr6+vr6+/39/v7+XKgUSwAAAMhJREFUKM9jYGDg4OZmZgABKINT1dBAhBHIYFMxMBIDisjbhoZbCTExsCu5hoeY8DEwcOkEx8fY6MqpucTGB0izglVEplcU5/gmRYWBVQDNMK+s0hN3SvMyBpsBNJxXw0NfwTEjVQZqHQMHj5RfWW5mliSEC7TPzK6yJD/bXZQRzGdXcisqLy309okA2Q4Eis4peQWmstqBCdGW/CABraC45ERBBs3A6Fh/AbAKTwsHa34QZW8NVsGuLqwswQSjQICTmYMFQaEDAAF8JHLfKGswAAAAAElFTkSuQmCC') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.liveleak {\n\ +.linkify.liveleak::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAlNJREFUGBkFwU2LVmUYAODrPu8Z5x1xSpRBXQyFoLsBE+wfiO5atJOgnf9DUPwFgtGinUgEaQsRhHYuMtpEiEWuG5iNjuOcj+c8z911xXcXL/68c3Dw1fzhg0QgEQAAEYGUKXFie9vxlSs/xk/rdavjGEkmkWSih65z4osv9GfOiK6LzEyZ2uGh4dUrmzs72ddlUUhkoiMr4PT167589Mh6c1N0nSRlqrX67dat+PDyZXRT19m5edPnt28rGFHxMcJ6d9fprS1/37tneP3aemPD1uamUydPOru3p5DdGOH0tWsu3LhhxIQJM2qEpRT/Pn3q/du3AhARSmvGTH0lplKMrVkiYpVpQaJlighzhDkzhmEA0fcWoqAfyaFW4zTlgCABxlrNmY4ylUzLsiREprFWc0T2M+ZSjKWY0AEaltZUjJixZJIpuk5pTWlNP2BYFvOyKJkCAKU1tTXHrZlqVWolUxdhxsfVSj9FmJfFMM9GdICGGa01HyMstYpMIFPJVNDPmYZSTOPoOEKHzNRlKpmWWh1j6TpLa2SKTKVWU6Z+Qolwdm/P9QcPZKa2LH69e9eIMs+WCL/cv2/98CGZPrt61am+V9APq1X89eyZ/968obVYaiXT4dGREgG+vnPHeHgYMsH2+fP+efEihtVKv7SWw/6+9/v7KYLMhIywTJPamvOXLomukyRsrNf+ePzYkpl9dJ3SWgSCSCQCfz5/7pMLF2yfO6eLiAQcHRz4/cmT+HR7O+Ob3d0fNt69+7a2BiICQCJbA0EgE5lpvbXl1OXL3/8Pfax4+6SjSukAAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.pastebin {\n\ +.linkify.pastebin::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAB1FBMVEUAAAAAAAAAAABWYWwAAABbY3BbYm5dZnFdZXJeZnMEBAQHCAhYYGpdZnFdZnBgaHIlJyomKCooKi09QkdESU5eZGtdYmhdYmleY2lrcXdqb3Rqb3Rqb3SSmJ+SlJeWmJutr7GtrrCWm6ChpKhbW1tmZmZvb290dHR3d3d4eHh5eXl6enp8fHx+gIJ/f3+CgoKDg4OEhISFhYWHh4eKioqKjI2Li4uMjIyOjo6Pj4+QkJCRkZGSkpKUlJSVl5mWlpaYmZqZm52ampqbm5ucnJydnZ2enp6fn5+hoaGioqKkpKSkpaalpaWmp6mmp6qnqauoqKioqquoqq2qqqqrrK2srKysra6srrCsrrGurq6vr6+wsLCxsbGysrKztLa0tLS1t7m2tra3t7e4uLi5ubm6urq7u7u8vLy9vb2+vr7AwMDAwsTBwcHExcfFxcXFxsnGxsbHx8fIyMjJycnMzMzNzc3Ozs7O0NLPz8/Q0NDR0dHR09XT09PV1dXV1dbV1tfV19rW1tbX19fX19jY2tzZ2dnZ2tva2tra3N3a3N7c3Nze3t7f39/f4OHg4ODi4uLl5+jm5ubs7Ozs7e3u7u7v7+/v8PDw8PDx8fHy8vLz8/P29vYSoLMZAAAAJHRSTlMABAUGCwsNHCAiLzMzMzZEYGJwgIuOnJycnqmqq9bc3+/w8fkZ0N/uAAAA/klEQVQoU2NgYGDl5YMDdgYGBmZZ3964CYFtIR3e9Q7K/AwMHI55KfaFmcHWMy3K3MwlGRg4wz0zdYpcorRbNbL0LaWAAp3ts2umV8wo6MupTauQBgqUG03VL7W3sfZSb1erAgm02M+yzYrVCXUy6zapAQlUx/dEdyX3J3ZHVUYVywAF8o2rDNN1Go2jzGLMokAC2QbuSc42mXmaOXop9iAtCXrJ5qXWjT59Abl2ESJAAX/tSIMMiyrrqQ3T6uS5gQK6kSqpqkUermGTexQFmYACflqR+hlWZSamzQpCLEDPsSmVVDT1TJw0JUhOAMRnYOARFRMTE5cQF+ZiBPIAII5B3EVG0b4AAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.soundcloud {\n\ - background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABsklEQVQ4y5WTy2pUQRCGv2rbzDjJeAlIBmOyipGIIJqFEBDElwh4yULGeRFXPoEIBl/AvQ/gC2RnxCAoxijiwks852S6+3dxzslcHJCpTXVX11/Xv0097gLPgVNMJxnQNfX4zsqleWbnpoMf/oa9d988MM9MC/rp+E0a+A0dsVobMNMCOO8B6McRoABJI+A6gJmN3D2A8jgEBCEkSEMBrcrsDAzDWWn3AjgKFaDMmgRqniGFgsaDp1jrLOngDf1XT1D+A1dFc4MKAkkiCVKjjVu7g9+4Rzx4i1u6hjXbuMWr0O5QPNvCu7IaCZwEKQukLGDrm5x8uI0tr6MkiGlkiv7yLfzN+6S5i6QsIMABkEfcxhbWWYMkVAOjxvYAjc3HNHrbKI9VBQBFwF25XQKSBjqIf1YBuAurEMrczgDygD6/x2LCpFLXLUyQ+PoldphhBhYfIX09XU1+Flaukz7uYqs3SHs7cG4BmTsmkBUF9mmXEwa28BNLPaQPLepuNcbGSWQquQC2/Kdcox1FUGkcB0ykck1nA2+wTzMs8stGnP4rbWGw74EuS/GFQWfK7/wF6P4F7fzIAYkdmdEAAAAASUVORK5CYII=') center left no-repeat!important;\n\ +.linkify.peertube::before {\n\ + content: \"\";\n\ + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABIFBMVEUhHyAAAABzPBnxaA3CWBEnJSYbGRptbW16enpzc3PTayWhb04hHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyApIh+0UhMfHiBWMhvsZg7zaQ0hHyAhHyAXHCHzaQ3xaA3xaA3xaA3xaA0hHyAhHyDxaA3xaA3xaA3xaA3xaA3xaA0oJickIiMdGxwUEhPxaA3xaA3xaA1sbGxwcHB3d3eFhYXxaA3xaA3xaA1zc3Nzc3Nzc3Nzc3Nzc3PxaA3xaA3xaA1zc3Nzc3NtdHjxaA3xaA1yc3STcFnvaA/yaAxzc3N4c2/FbDFzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3MhHyDxaA1zc3MAAAAfljyVAAAAXHRSTlMAAAAAAAAAAAAAAAAZkjMBHOLXYArj8p0u2VsJ1XaGL/OhKyXc1WEN2gwk2/SjKgEYiS4B/tYFGosqAdleAxzj12ML9Z8s850rJWbYeYMs1F8Koiri1V0MGZY0AYbIBFIAAAABYktHRAH/Ai3eAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4wYXFBUVX81QWQAAAKxJREFUGNNVz9UWgkAQANDBtdbu7lZsxe7ubpH//wxBPKDzNvdMAmi0Oj0QQgAYjCazBX7BStvsDqHoAzTtdLklf+Dx+vwICRAIhsKRaCyOvpAwJ6Up8pXOZHOIAFm+UCzJEQuvMhWrIFBUa/WGkodmq40Ad7q9/kDFwnA05lpYYCbT2ZykFvxQDhhmuVpvcvxaHra7vfp72KflcMSYEOB0vlyx+By+3R9PMSfe+P0enM1454kAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDYtMjRUMDM6MjE6MjEtMDc6MDDse6MAAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTA2LTI0VDAzOjIxOjIxLTA3OjAwnSYbvAAAAABJRU5ErkJggg==') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.twitchtv {\n\ - background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAYUExURf///2RBpWRBpWRBpWRBpWRBpWRBpf///+zQyUYAAAAGdFJOUwFdZX0lTzs4r5oAAAABYktHRAcWYYjrAAAAB3RJTUUH4AINEi42iSXRNAAAAD1JREFUCNdjYEiDAAZGGIMtjQEEUBlMCWoEGci6mGEMsxQgIy0BiB3AjLS0FAYQIw0kwABipoI1AhkBQBIAFCIXxiHgq80AAAAASUVORK5CYII=') center left no-repeat!important;\n\ +.linkify.soundcloud::before {\n\ + content: \"\";\n\ + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABsklEQVQ4y5WTy2pUQRCGv2rbzDjJeAlIBmOyipGIIJqFEBDElwh4yULGeRFXPoEIBl/AvQ/gC2RnxCAoxijiwks852S6+3dxzslcHJCpTXVX11/Xv0097gLPgVNMJxnQNfX4zsqleWbnpoMf/oa9d988MM9MC/rp+E0a+A0dsVobMNMCOO8B6McRoABJI+A6gJmN3D2A8jgEBCEkSEMBrcrsDAzDWWn3AjgKFaDMmgRqniGFgsaDp1jrLOngDf1XT1D+A1dFc4MKAkkiCVKjjVu7g9+4Rzx4i1u6hjXbuMWr0O5QPNvCu7IaCZwEKQukLGDrm5x8uI0tr6MkiGlkiv7yLfzN+6S5i6QsIMABkEfcxhbWWYMkVAOjxvYAjc3HNHrbKI9VBQBFwF25XQKSBjqIf1YBuAurEMrczgDygD6/x2LCpFLXLUyQ+PoldphhBhYfIX09XU1+Flaukz7uYqs3SHs7cG4BmTsmkBUF9mmXEwa28BNLPaQPLepuNcbGSWQquQC2/Kdcox1FUGkcB0ykck1nA2+wTzMs8stGnP4rbWGw74EuS/GFQWfK7/wF6P4F7fzIAYkdmdEAAAAASUVORK5CYII=') center left no-repeat!important;\n\ + padding-left: 18px;\n\ +}\n\ +.linkify.streamable::before {\n\ + content: \"\";\n\ + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABXFBMVEUPkPoNj/qExv0PkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoNj/oPkPoNj/oNj/qExv0PkPpruvwPkPornfoVk/opnPpnufwPkPqExv0Nj/oPkPoNj/oPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoOj/opnPsVk/oMjvoOkPoTkfo6pPsblfo3ovva7v7////v9/5Sr/whmPry+f5htvze8P7W7P5itvyl1v0imPu84P3o9P50v/zN6P73+/8lmvs8pfs+pfsKjvr9/v9EqfsNj/oom/v8/v9nufxAp/tJq/sQkPrb7v6t2f0IjPoclvr6/f9luPwUkvrp9f7h8f5ruvy/4f4kmftpuvwxoPum1v32+/8jmfpMrPvu9/7z+f9UsPs7pPv8/f/4/P9oufwalfpDqPsMj/ounvtVsPsnm/qzfQQ9AAAALXRSTlMAAAAggMzw0IYkBPb4iAamsgZ+jPwogpDO1vTYlPoulL4KivyUCiqO1PL01i67tUAWAAAAAWJLR0Q4oAel1gAAAAd0SU1FB+MGFxMuDXVcMbIAAADdSURBVBjTY2AAAmYWVjY2dg5OBgZGJiCXi4VbFwx4ePlAAlz8unAgIAgUENJFAsJMDMw8unp6+gaGRsYmpoa6IqIMYrp6ZuYWllbW5hY2toZ64gwSurp29g6OTs4urm7uHrqSDGy6nhZeet5WPr5+/gGBelJAgSCLYL+Q0LBw3YjIKKAAu250TGxcvE1CYlJySqquNAOHrl9aukVGZla2RU6uoZ4MA6esrl9evnWBYWFRMdBaOQYGXmSHyQNdyieA4CsogjzHpyQL4SqrqIJ9y8Cgpq6hqaWtogPyPgDmvSxRxBWM9AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxOS0wNi0yNFQwMjo0NjoxMy0wNzowMCKUvXUAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTktMDYtMjRUMDI6NDY6MTMtMDc6MDBTyQXJAAAAAElFTkSuQmCC') center left no-repeat!important;\n\ + padding-left: 18px;\n\ +}\n\ +.linkify.twitchtv::before {\n\ + content: \"\";\n\ + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAYUExURf///2RBpWRBpWRBpWRBpWRBpWRBpf///+zQyUYAAAAGdFJOUwFdZX0lTzs4r5oAAAABYktHRAcWYYjrAAAAB3RJTUUH4AINEi42iSXRNAAAAD1JREFUCNdjYEiDAAZGGIMtjQEEUBlMCWoEGci6mGEMsxQgIy0BiB3AjLS0FAYQIw0kwABipoI1AhkBQBIAFCIXxiHgq80AAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.twitter {\n\ +.linkify.twitter::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAEsUExURf///1Cf21Gg3FGi31Gh3VKj4FGh3lKj4VKk4lKl41Ol5FOn51Sp6VSo6FOn5lCf21Gg3FGh3VGi31Gi31Gh3lGg3FGg3FGg3FGg3FGh3lGg3FGi31Kk4lKj4FGh3lGi31Kk4lGh3lGg3FGh3lOm5FOm5VGi31Kj4VSo6FGi31Gh3VGg3FKj4FOn51Gi31So6FWr7VOl5FGi31On51Sq6lKk4lOo51Sp6VOm5FSq61Ws7VOn51Oo51Sq61Ol5FOm5FSq61Wr7VOo51On51Sr7FWs7VSp6lGg3FGh3VOm5FWr7VSp6lKj4VOm5FSo6FSr7FWs7VWs7VWr7VSq6lOo51Om5FOo51So6FOm5VOl5FSq61Ws7VSr7FSp6lSp6VWs7lWr7VKk4lSq6v///6E3MNsAAABVdFJOUwAAAAAAAAAAAAAAAAAAAB0Ii+3xnBVTJhfsMKb+qTEp9GwBF/7lLAbo0m4pLkUTdvk2Ev3+EZnOBo/3Z8ffCRzH/D0OqPxiLnvx3UI8m9n1++GwXQZNS29BAAAAAWJLR0QAiAUdSAAAAAd0SU1FB+ACDRIwBwy67tEAAADKSURBVBjTY2BAB4xogIGRH8IQEBQSFhEVE2eQkJQC8ZmkQ8PCI2Rk5RjkIxUUlRgZlVWioqNjYlXVGNQ14iI1tbR14qLj4+MTdJkZ9PQNosJCE0OjgPz4KEMWBiPjhPiEmKQokIJ4E1MmBmazhHg4MGdlYmCzsLSC8ROsmRkZmFht4Eps7ViADmOzd4DyHZ2YmYACTOzOLmATXd04mIBOd/eQ9owFCXh5c7KB/MLi4+vnHxAYFBzCwcYEEmBi5uLm4eHl42RmAnsSAMZBLgZiFUQ5AAAAAElFTkSuQmCC') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.video {\n\ +.linkify.video::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAxgDGAP8nNqN7AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gMZBjQQLEEqGwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAA5SURBVDjLY2AYaMDIwMDwn1JD/lPCZhpwL+B1wf///ykzgBhDiAoDfIYQZQAjIyP5BuDTPJqQqAQAvW0ZAMk8+EEAAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.vimeo {\n\ +.linkify.vidlii::before {\n\ + content: \"\";\n\ + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAACvlBMVEUCWv8HXf8AWv8AAAD///8AVP+bqP8AWv8AVO4AOqUAGkgAyf8APa0AL4QABAsASdEAVv8AUv8AUv8AVP8AWP8BWv8JXv8RYv8QYv8DW/8DXP8xdv9RiP9Af/8IXf8AKP8KXv8JXf8NYf8aaf8ATP0UZP0AVf8AT/8AT/8AVv8ATedvnPVAf/8AT/sYZvl0o/8PYf8udf8aa/8FXf8AVf8AOrRBe/Nvn/8AUv0aaPkXZ/8ATv8AKYQZYuwIXf8ca/wTZP8ASP8AED0HUNwZaf8xdPwDWv8AAAAAQMRcjvQAU/8AMZssb/Jmmf8AU/8AJXsRW+dSif8AUv8AAAAASdQtdP8ATv8AQ/8AQv8APbtKgfQud/8XZ/8TZP8FXP8AKIIcZO4wdP8AF08KU95tnv4gafhZi/Rnl/ZzofcocP8AAAAAQ8Q4efRwnvVmlvVcjvgrcfsAQsQAOK0APrwAQcUEStMLXPgDWv8AHE8APLEARdIAQ80ASeEAVf8AOJkAAAAAAAAAAAAABBMAJJIAY/9rmP+vxv90n/+buPv29/7C1P+zx/n///2Crv/7+fjs8f++z/f///3l6fX9/f/L2fj9/P5ilv9Nh/3h6f6vyf/D0vT///2lwP/Z5Pf3+P9OiP9klvr9/Puzyf+QsPX//fnW4v/k6vfv8/86ev94pfj///uRtf/y8vby9f9Fgv9EgPzt7/jj7f8mcf+eufj///x1pP/Z4fT///52pf9Uivv09fnV4v8ncf64yvj7+/6vxPX///yyyf9ynvr6+vvG1/8ocv3O2/fz9v53ofX8+/nb5v+YuPz//vy0yv8vdP3e5/fn7v/p7PX09//b5P7///6eu/9Df/zq7vjc5//I1vT//v3+/v////9+q/9Tivnn6fPy8/rW4fzI1/2qwv6YtPT8+fX39/jz9PqJrveTsvqfpuxrAAAAhXRSTlMAAAAAAAAAAAAAAAAAAAAABSlERA45nrSzP3TZ7e12Ao2LusMcrJYhFwaR/uhCwP/x5tZzBWHy+n3OvA8u17jmpwgPrOz5jAF2+3FA7PdYG8fuPQaX5jAGAV/39MCmdy/e/RGz/vj5/f/rAXj4//z13n52i5qmmFQ1lqOQaTgIBAYKEAYAKGjtAgAAAKNJREFUGBkFwT0uRGEYBtD3ZJ77uT8iGrXCAixCr7OCyRA2oCKqiUYkOgoJwhqUbMAKLEChVYhk4pxswvcWfFGVEbYtuJutqir9Ibc0uh0+V+mf5gY69yN2PzKJiTjCg8qa3uLRAJpKM9AMoL1VOi9zJ4CQ9z0jwHX+RAwAURUxAMSB/L7u35wCGlKaHrDkPGVmwhlc6FN6l1iHKxupn+djAPgHrEwa+qrzy0oAAAAASUVORK5CYII=') center left no-repeat!important;\n\ + padding-left: 18px;\n\ +}\n\ +.linkify.vimeo::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAYFBMVEUAAAAIdZUKh6sLlLkLmr4LmsAMp88NrdYVW3MZj7Acstkrt9s1e5E7vN5EfI9JvdtKwuBijp5kpbl30eiDt8aG1uqRr7qTyNehxM+k4PCy3enB3OTg6Ovv9PXw+fz////L9U5WAAAAAXRSTlMAQObYZgAAAIFJREFUeNplz90OwiAMBWAQpAoyxclkP3je/y0H2AQXz0WT8100rRD6kNI9/cRroemQL3hXhoujZYj4OHoAmBvYGcBISwbWBvfXCrytnIDUQMkbsBpagMA7zhtQdyTFQAmIG7IkYniiZuh3XGsPqoOZkMOJOpAcLqUzNFGGu/57fwc1hgtp0mVSyQAAAABJRU5ErkJggg==') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.vine {\n\ +.linkify.vine::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAHCUExURQAAAAC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+j////54tRLAAAACUdFJOUwAAAEK+9/e+QQIDAwEqzskfUZmUNHz2mrT++V1w+f5tCanNFUDwfEjtjAyyvg027Hki27QMBJzaHE/1+FkNsN0iZvv6bxyAlB589lQeyud0KB8PQO+ZBUrc+eXgcRG/3CoATe316Wxw/P6BAgBt+fp4IAwh0d4zM9q7Fm76qi605EMSrvfX/PRtAivF9IAJNMLxhA2KYlJ9AAAAAWJLR0SVCGB6gwAAAAd0SU1FB+ACDRI2MOJd7FgAAADrSURBVBjTLY9VWwJgGEPfiYWBha2YYHcHditgd3d3odjdivvBfgK727nYsyPiCrw03j6+fv6AaAMCgyAI1lElJBQSFh6hBxDJqOiY2Lh4SEKiIQlITmFqWrqRJkhGJrOA7Bzm5uUXsBBSVMySUpSVs6KyqrqmFmKuY30D0NjU3NLa1t6h9jvZ1Q30WGi19fb1KzAwyKFhYGSUY+MTkwpMTXNmFpibX+Di0rICWFldW9/A5tb2zu7ePtTrg0MeHePklPYzuDRw7uDF5RWvbwC32O0d7x8en55f4DHF6xv5/vHp6f/k6/vH+evuf1LAObptvSvrAAAAAElFTkSuQmCC') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.vocaroo {\n\ +.linkify.vocaroo::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAw9JREFUOE9jYMABuMwYmCyTJKUCGlSnFSy02TTzeOyCiQcDViX26qVz2TAyYtWmEMwuoZ3M7V40LcB79pHkc0svpvzY8jD//87nxf+3Pyn8v/ZO8v+VNyP/2mZJumI1QCWSI8232Hjumitlfw5+qPp/9l8TCt76JP//xkdx/wsXWCzjtWFkwTCkbWFe9plPk/+ga4Txz/xt/D/hkN//gMXif21a+NbyWjIwoRiy6GDT5rP/mlFsPfyp5n/NpOj/22+0gMUXXIz/H7hC/L/bFKFbPDZMrHAD5H35OPt2J9zacDv/f3V7xv9FhwrBGubsT/1//Pjx/1GJ/mD+/nfl/1v3Ovy3KRJNQbHdOlXCvOO03/+pm1P/v3v37n90hhtYw9HPtf8Xb2v937cmHswHeWPRxYj/LvkK3igGKARwicTO07118H3V/5kbi/4vPZMJtK3s/6YH2f+Pfq1B8VbjWrdnMu5s4nAD9CNFhKwz5DTUvLl419zKvAcLtG1P84BRl/b/5M/6/6f/NPzf/qzo84yj0Uus0xUU4Zor54bm9+4OfZG02OCuoAMTb9ZkC9ull1Nvrr2Z+XvRpaRfc65H/68F+jl9svEhzyLFWoccWVc+eyTHq/twydjlKRln7jX9bNMkMJnbhoFRL1xCqmKx6/yi2fYXa/c5/e846PV/5fW0/7OPx/yfcjzop34ulxdGGvDuU8mMXaX507lBuiN6ueadmQeT/p/93vf/1O+G//sP5fw/eL3o/5JLif8zVxs+Tlir9S26UyeFQQvJGBE7FvaFZ9LfN+1y+WjbItSb3GmXvXd15v8zroH/HxgE/D+aGPx/18vi/z07PeZNPRKxe/Kh0Ae8toxscCO4zBkYXArk9C1SxJUYjBkYPPIVtbbuTftz3cz//2O9wP/75iSAXdO72/dt2HL5F6YlfBW4MiJYXMiBiW3t7azHBx+V/t89N+H/8a+1//e9K/9attDp5LQjYX8SuvVL8RoAkmxa65299Erq1FnHo0qrl7t4BddriIs4MrM3rfWcFd+pGwVSAwBZ0bKP8yrZPAAAAABJRU5ErkJggg==') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.youtube {\n\ +.linkify.youtube::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAMCAYAAABr5z2BAAABIklEQVQoz53LvUrDUBjG8bOoOammSf1IoBSvoCB4JeIqOHgBLt6AIMRBBQelWurQ2kERnMRBsBUcIp5FJSBI5oQsJVkkUHh8W0o5nhaFHvjBgef/Mq+Q46RJBMkI/vE+aOus956tnEswIZe1LV0QyJ5sE2GzgZfVMtRNIdiDpccEssdlB1mW4bvTwdvWJtRdErM7U+8S/FJykCRJX5qm+KpVce8UMNLRLbulz4iSjTAMh6Iowsd5BeNadp3nUF0VlxAEwZBotXC0Usa4ll3meZdA1iguwvf9vpvDA2wvmKgYGtSud8suDB4TyGr2PF49D/vra9jRZ1BVdknMzgwuCGSnZEObwu6sBnVTCHZiaC7BhFx2PKdxUidiAH/4lLo9Mv0DELVs9qsOHXwAAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ @@ -3850,12 +4711,56 @@ report: }\n\ #captchaContainerAlt td:nth-child(2) {\n\ display: table-cell !important;\n\ -}\n", +}\n\ +/* Archive reports */\n\ +#archive-report {\n\ + padding: 3px;\n\ +}\n\ +#archive-report-enabled {\n\ + vertical-align: middle;\n\ +}\n\ +#archive-report > label {\n\ + display: block;\n\ +}\n\ +#archive-report-reason {\n\ + display: block;\n\ + width: 98%;\n\ +}\n\ +.archive-report-success {\n\ + color: green;\n\ +}\n\ +.archive-report-error {\n\ + color: red;\n\ +}", www: "#captcha-cnt {\n\ height: auto;\n\ -}\n" +}\n\ +:root:not(.js-enabled) #form {\n\ + display: block;\n\ +}\n\ +#bd > div[style], #bd > div[style] > * {\n\ + height: auto !important;\n\ + margin: 0 !important;\n\ + font-size: 0;\n\ +}\n", + +sub: function(css) { + var variables = { + site: g.SITE.selectors + }; + return css.replace(/\$[\w\$]+/g, function(name) { + var words = name.slice(1).split('$'); + var sel = variables; + for (var i = 0; i < words.length; i++) { + if (typeof sel !== 'object') return ':not(*)'; + sel = $.getOwn(sel, words[i]); + } + if (typeof sel !== 'string') return ':not(*)'; + return sel; + }); +} }; @@ -3917,104 +4822,180 @@ $ = (function() { } }; + $.dict = function() { + return Object.create(null); + }; + + $.dict.clone = function(obj) { + var arr, i, j, key, map, ref, val; + if (typeof obj !== 'object' || obj === null) { + return obj; + } else if (obj instanceof Array) { + arr = []; + for (i = j = 0, ref = obj.length; j < ref; i = j += 1) { + arr.push($.dict.clone(obj[i])); + } + return arr; + } else { + map = Object.create(null); + for (key in obj) { + val = obj[key]; + map[key] = $.dict.clone(val); + } + return map; + } + }; + + $.dict.json = function(str) { + return $.dict.clone(JSON.parse(str)); + }; + + $.hasOwn = function(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); + }; + + $.getOwn = function(obj, key) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + return obj[key]; + } else { + return void 0; + } + }; + $.ajax = (function() { - var lastModified; - lastModified = {}; - return function(url, options, extra) { - var err, event, form, i, len, r, ref, ref1, type, upCallbacks, whenModified; + var pageXHR; + try { + pageXHR = window.wrappedJSObject && !XMLHttpRequest.wrappedJSObject ? XPCNativeWrapper(window.wrappedJSObject.XMLHttpRequest) : XMLHttpRequest; + } catch (error) { + pageXHR = XMLHttpRequest; + } + return function(url, options) { + var err, form, headers, key, onloadend, onprogress, r, ref, responseType, timeout, type, value, withCredentials; if (options == null) { options = {}; } - if (extra == null) { - extra = {}; + if (options.responseType == null) { + options.responseType = 'json'; } - type = extra.type, whenModified = extra.whenModified, upCallbacks = extra.upCallbacks, form = extra.form; - url = url.replace(/^((?:https?:)?\/\/(?:\w+\.)?4c(?:ha|d)n\.org)\/adv\//, '$1//adv/'); - r = new XMLHttpRequest(); - type || (type = form && 'post' || 'get'); + options.type || (options.type = options.form && 'post' || 'get'); + url = url.replace(/^((?:https?:)?\/\/(?:\w+\.)?(?:4chan|4channel|4cdn)\.org)\/adv\//, '$1//adv/'); + onloadend = options.onloadend, timeout = options.timeout, responseType = options.responseType, withCredentials = options.withCredentials, type = options.type, onprogress = options.onprogress, form = options.form, headers = options.headers; + r = new pageXHR(); try { r.open(type, url, true); - if (whenModified) { - if (((ref = lastModified[whenModified]) != null ? ref[url] : void 0) != null) { - r.setRequestHeader('If-Modified-Since', lastModified[whenModified][url]); - } - $.on(r, 'load', function() { - return (lastModified[whenModified] || (lastModified[whenModified] = {}))[url] = r.getResponseHeader('Last-Modified'); - }); - } - if (/\.json$/.test(url)) { - if (options.responseType == null) { - options.responseType = 'json'; - } - } - $.extend(r, options); - $.extend(r.upload, upCallbacks); + ref = headers || {}; + for (key in ref) { + value = ref[key]; + r.setRequestHeader(key, value); + } + $.extend(r, { + onloadend: onloadend, + timeout: timeout, + responseType: responseType, + withCredentials: withCredentials + }); + $.extend(r.upload, { + onprogress: onprogress + }); $.on(r, 'error', function() { if (!r.status) { - return c.error("4chan X failed to load: " + url); + return c.warn("4chan X failed to load: " + url); } }); r.send(form); - } catch (_error) { - err = _error; + } catch (error) { + err = error; if (err.result !== 0x805e0006) { throw err; } - ref1 = ['error', 'loadend']; - for (i = 0, len = ref1.length; i < len; i++) { - event = ref1[i]; - r["on" + event] = options["on" + event]; - $.queueTask($.event, event, null, r); - } + r.onloadend = onloadend; + $.queueTask($.event, 'error', null, r); + $.queueTask($.event, 'loadend', null, r); } return r; }; })(); + $.lastModified = $.dict(); + + $.whenModified = function(url, bucket, cb, options) { + var ajax, headers, params, r, ref, t, timeout, url0; + if (options == null) { + options = {}; + } + timeout = options.timeout, ajax = options.ajax; + params = []; + if ($.engine === 'blink') { + params.push("s=" + bucket); + } + if (url.split('/')[2] === 'a.4cdn.org') { + params.push("t=" + (Date.now())); + } + url0 = url; + if (params.length) { + url += '?' + params.join('&'); + } + headers = $.dict(); + if ((t = (ref = $.lastModified[bucket]) != null ? ref[url0] : void 0) != null) { + headers['If-Modified-Since'] = t; + } + r = (ajax || $.ajax)(url, { + onloadend: function() { + var base; + ((base = $.lastModified)[bucket] || (base[bucket] = $.dict()))[url0] = this.getResponseHeader('Last-Modified'); + return cb.call(this); + }, + timeout: timeout, + headers: headers + }); + return r; + }; + (function() { var reqs; - reqs = {}; + reqs = $.dict(); $.cache = function(url, cb, options) { - var err, req, rm; - if (req = reqs[url]) { - if (req.readyState === 4) { + var ajax, onloadend, req; + if (options == null) { + options = {}; + } + ajax = options.ajax; + if ((req = reqs[url])) { + if (req.callbacks) { + req.callbacks.push(cb); + } else { $.queueTask(function() { - return cb.call(req, req.evt, true); + return cb.call(req, { + isCached: true + }); }); - } else { - req.callbacks.push(cb); } return req; } - rm = function() { - return delete reqs[url]; - }; - try { - if (!(req = $.ajax(url, options))) { - return; + onloadend = function() { + var fn1, j, len, ref; + if (!this.status) { + delete reqs[url]; } - } catch (_error) { - err = _error; - return; - } - $.on(req, 'load', function(e) { - var fn1, i, len, ref; - this.evt = e; ref = this.callbacks; fn1 = (function(_this) { return function(cb) { return $.queueTask(function() { - return cb.call(_this, e, false); + return cb.call(_this, { + isCached: false + }); }); }; })(this); - for (i = 0, len = ref.length; i < len; i++) { - cb = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + cb = ref[j]; fn1(cb); } return delete this.callbacks; + }; + req = (ajax || $.ajax)(url, { + onloadend: onloadend }); - $.on(req, 'abort error', rm); req.callbacks = [cb]; return reqs[url] = req; }; @@ -4030,12 +5011,16 @@ $ = (function() { $.cb = { checked: function() { - $.set(this.name, this.checked); - return Conf[this.name] = this.checked; + if ($.hasOwn(Conf, this.name)) { + $.set(this.name, this.checked); + return Conf[this.name] = this.checked; + } }, value: function() { - $.set(this.name, this.value.trim()); - return Conf[this.name] = this.value; + if ($.hasOwn(Conf, this.name)) { + $.set(this.name, this.value.trim()); + return Conf[this.name] = this.value; + } } }; @@ -4108,19 +5093,19 @@ $ = (function() { }; $.addClass = function() { - var className, classNames, el, i, len; + var className, classNames, el, j, len; el = arguments[0], classNames = 2 <= arguments.length ? slice.call(arguments, 1) : []; - for (i = 0, len = classNames.length; i < len; i++) { - className = classNames[i]; + for (j = 0, len = classNames.length; j < len; j++) { + className = classNames[j]; el.classList.add(className); } }; $.rmClass = function() { - var className, classNames, el, i, len; + var className, classNames, el, j, len; el = arguments[0], classNames = 2 <= arguments.length ? slice.call(arguments, 1) : []; - for (i = 0, len = classNames.length; i < len; i++) { - className = classNames[i]; + for (j = 0, len = classNames.length; j < len; j++) { + className = classNames[j]; el.classList.remove(className); } }; @@ -4150,13 +5135,13 @@ $ = (function() { }; $.nodes = function(nodes) { - var frag, i, len, node; + var frag, j, len, node; if (!(nodes instanceof Array)) { return nodes; } frag = $.frag(); - for (i = 0, len = nodes.length; i < len; i++) { - node = nodes[i]; + for (j = 0, len = nodes.length; j < len; j++) { + node = nodes[j]; frag.appendChild(node); } return frag; @@ -4195,19 +5180,19 @@ $ = (function() { }; $.on = function(el, events, handler) { - var event, i, len, ref; + var event, j, len, ref; ref = events.split(' '); - for (i = 0, len = ref.length; i < len; i++) { - event = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + event = ref[j]; el.addEventListener(event, handler, false); } }; $.off = function(el, events, handler) { - var event, i, len, ref; + var event, j, len, ref; ref = events.split(' '); - for (i = 0, len = ref.length; i < len; i++) { - event = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + event = ref[j]; el.removeEventListener(event, handler, false); } }; @@ -4230,6 +5215,7 @@ $ = (function() { } return root.dispatchEvent(new CustomEvent(event, { bubbles: true, + cancelable: true, detail: detail })); }; @@ -4243,8 +5229,8 @@ $ = (function() { return new CustomEvent('x', { detail: {} }); - } catch (_error) { - err = _error; + } catch (error) { + err = error; unsafeConstructors = { Object: unsafeWindow.Object, Array: unsafeWindow.Array @@ -4268,13 +5254,18 @@ $ = (function() { } return root.dispatchEvent(new CustomEvent(event, { bubbles: true, + cancelable: true, detail: clone(detail) })); }; } })(); - $.open = typeof GM_openInTab !== "undefined" && GM_openInTab !== null ? GM_openInTab : function(url) { + $.modifiedClick = function(e) { + return e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0; + }; + + $.open = (typeof GM !== "undefined" && GM !== null ? GM.openInTab : void 0) != null ? GM.openInTab : typeof GM_openInTab !== "undefined" && GM_openInTab !== null ? GM_openInTab : function(url) { return window.open(url, '_blank'); }; @@ -4324,23 +5315,23 @@ $ = (function() { } })(); - $.globalEval = function(code, data) { - var script; - script = $.el('script', { - textContent: code - }); - if (data) { - $.extend(script.dataset, data); - } - $.add(d.head || doc, script); - return $.rm(script); - }; - $.global = function(fn, data) { + var script; if (doc) { - return $.globalEval("(" + fn + ")();", data); + script = $.el('script', { + textContent: "(" + fn + ").call(document.currentScript.dataset);" + }); + if (data) { + $.extend(script.dataset, data); + } + $.add(d.head || doc, script); + $.rm(script); + return script.dataset; } else { - return fn(); + try { + fn.call(data); + } catch (error) {} + return data; } }; @@ -4363,6 +5354,34 @@ $ = (function() { return video.mozHasAudio || !!video.webkitAudioDecodedByteCount; }; + $.luma = function(rgb) { + return rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114; + }; + + $.unescape = function(text) { + if (text == null) { + return text; + } + return text.replace(/<[^>]*>/g, '').replace(/&(amp|#039|quot|lt|gt|#44);/g, function(c) { + return { + '&': '&', + ''': "'", + '"': '"', + '<': '<', + '>': '>', + ',': ',' + }[c]; + }); + }; + + $.isImage = function(url) { + return /\.(jpe?g|jfif|png|gif|bmp|webp|avif|jxl)$/i.test(url); + }; + + $.isVideo = function(url) { + return /\.(webm|mp4|ogv)$/i.test(url); + }; + $.engine = (function() { if (/Edge\//.test(navigator.userAgent)) { return 'edge'; @@ -4380,252 +5399,368 @@ $ = (function() { $.platform = 'userscript'; - try { - localStorage.getItem('x'); - $.hasStorage = true; - } catch (_error) { - $.hasStorage = false; - } + $.hasStorage = (function() { + try { + if (localStorage.getItem(g.NAMESPACE + 'hasStorage') === 'true') { + return true; + } + localStorage.setItem(g.NAMESPACE + 'hasStorage', 'true'); + return localStorage.getItem(g.NAMESPACE + 'hasStorage') === 'true'; + } catch (error) { + return false; + } + })(); $.item = function(key, val) { var item; - item = {}; + item = $.dict(); item[key] = val; return item; }; - $.syncing = {}; - - if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { - $.getValue = GM_getValue; - $.listValues = function() { - return GM_listValues(); - }; - } else if ($.hasStorage) { - $.getValue = function(key) { - return localStorage[key]; + $.oneItemSugar = function(fn) { + return function(key, val, cb) { + if (typeof key === 'string') { + return fn($.item(key, val), cb); + } else { + return fn(key, val); + } }; - $.listValues = function() { - var key, results; + }; + + $.syncing = $.dict(); + + $.securityCheck = function(data) { + if (location.protocol !== 'https:') { + return delete data['Redirect to HTTPS']; + } + }; + + if (((typeof GM !== "undefined" && GM !== null ? GM.deleteValue : void 0) != null) && window.BroadcastChannel && (typeof GM_addValueChangeListener === "undefined" || GM_addValueChangeListener === null)) { + $.syncChannel = new BroadcastChannel(g.NAMESPACE + 'sync'); + $.on($.syncChannel, 'message', function(e) { + var cb, key, ref, results, val; + ref = e.data; results = []; - for (key in localStorage) { - if (key.slice(0, g.NAMESPACE.length) === g.NAMESPACE) { - results.push(key); + for (key in ref) { + val = ref[key]; + if ((cb = $.syncing[key])) { + results.push(cb($.dict.json(JSON.stringify(val)), key)); } } return results; + }); + $.sync = function(key, cb) { + return $.syncing[key] = cb; }; - } else { - $.getValue = function() {}; - $.listValues = function() { - return []; - }; - } - - if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { - $.setValue = GM_setValue; - $.deleteValue = GM_deleteValue; - } else if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { - $.oldValue = {}; - $.setValue = function(key, val) { - GM_setValue(key, val); - if (key in $.syncing) { - $.oldValue[key] = val; - if ($.hasStorage) { - return localStorage[key] = val; - } + $.forceSync = function() {}; + $["delete"] = function(keys, cb) { + var key; + if (!(keys instanceof Array)) { + keys = [keys]; } - }; - $.deleteValue = function(key) { - GM_deleteValue(key); - if (key in $.syncing) { - delete $.oldValue[key]; - if ($.hasStorage) { - return localStorage.removeItem(key); + return Promise.all((function() { + var j, len, results; + results = []; + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + results.push(GM.deleteValue(g.NAMESPACE + key)); } - } - }; - if (!$.hasStorage) { - $.cantSync = true; - } - } else if ($.hasStorage) { - $.oldValue = {}; - $.setValue = function(key, val) { - if (key in $.syncing) { - $.oldValue[key] = val; - } - return localStorage[key] = val; - }; - $.deleteValue = function(key) { - if (key in $.syncing) { - delete $.oldValue[key]; - } - return localStorage.removeItem(key); + return results; + })()).then(function() { + var items, j, key, len; + items = $.dict(); + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + items[key] = void 0; + } + $.syncChannel.postMessage(items); + return typeof cb === "function" ? cb() : void 0; + }); }; - } else { - $.setValue = function() {}; - $.deleteValue = function() {}; - $.cantSync = $.cantSet = true; - } - - if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { - $.sync = function(key, cb) { - return $.syncing[key] = GM_addValueChangeListener(g.NAMESPACE + key, function(key2, oldValue, newValue, remote) { - if (remote) { - if (newValue !== void 0) { - newValue = JSON.parse(newValue); + $.get = $.oneItemSugar(function(items, cb) { + var key, keys; + keys = Object.keys(items); + return Promise.all((function() { + var j, len, results; + results = []; + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + results.push(GM.getValue(g.NAMESPACE + key)); + } + return results; + })()).then(function(values) { + var i, j, len, val; + for (i = j = 0, len = values.length; j < len; i = ++j) { + val = values[i]; + if (val) { + items[keys[i]] = $.dict.json(val); } - return cb(newValue, key); } + return cb(items); + }); + }); + $.set = $.oneItemSugar(function(items, cb) { + var key, val; + $.securityCheck(items); + return Promise.all((function() { + var results; + results = []; + for (key in items) { + val = items[key]; + results.push(GM.setValue(g.NAMESPACE + key, JSON.stringify(val))); + } + return results; + })()).then(function() { + $.syncChannel.postMessage(items); + return typeof cb === "function" ? cb() : void 0; + }); + }); + $.clear = function(cb) { + return GM.listValues().then(function(keys) { + return $["delete"](keys.map(function(key) { + return key.replace(g.NAMESPACE, ''); + }), cb); + })["catch"](function() { + return $["delete"](Object.keys(Conf).concat(['previousversion', 'QR Size', 'QR.persona']), cb); }); }; - $.forceSync = function() {}; - } else if ((typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) || $.hasStorage) { - $.sync = function(key, cb) { - key = g.NAMESPACE + key; - $.syncing[key] = cb; - return $.oldValue[key] = $.getValue(key); - }; - (function() { - var onChange; - onChange = function(arg) { - var cb, key, newValue; - key = arg.key, newValue = arg.newValue; - if (!(cb = $.syncing[key])) { - return; + } else { + if (typeof GM_deleteValue === "undefined" || GM_deleteValue === null) { + $.perProtocolSettings = true; + } + if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { + $.getValue = GM_getValue; + $.listValues = function() { + return GM_listValues(); + }; + } else if ($.hasStorage) { + $.getValue = function(key) { + return localStorage.getItem(key); + }; + $.listValues = function() { + var key, results; + results = []; + for (key in localStorage) { + if (key.slice(0, g.NAMESPACE.length) === g.NAMESPACE) { + results.push(key); + } } - if (newValue != null) { - if (newValue === $.oldValue[key]) { - return; + return results; + }; + } else { + $.getValue = function() {}; + $.listValues = function() { + return []; + }; + } + if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { + $.setValue = GM_setValue; + $.deleteValue = GM_deleteValue; + } else if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { + $.oldValue = $.dict(); + $.setValue = function(key, val) { + GM_setValue(key, val); + if (key in $.syncing) { + $.oldValue[key] = val; + if ($.hasStorage) { + return localStorage.setItem(key, val); } - $.oldValue[key] = newValue; - return cb(JSON.parse(newValue), key.slice(g.NAMESPACE.length)); - } else { - if ($.oldValue[key] == null) { - return; + } + }; + $.deleteValue = function(key) { + GM_deleteValue(key); + if (key in $.syncing) { + delete $.oldValue[key]; + if ($.hasStorage) { + return localStorage.removeItem(key); } + } + }; + if (!$.hasStorage) { + $.cantSync = true; + } + } else if ($.hasStorage) { + $.oldValue = $.dict(); + $.setValue = function(key, val) { + if (key in $.syncing) { + $.oldValue[key] = val; + } + return localStorage.setItem(key, val); + }; + $.deleteValue = function(key) { + if (key in $.syncing) { delete $.oldValue[key]; - return cb(void 0, key.slice(g.NAMESPACE.length)); } + return localStorage.removeItem(key); }; - $.on(window, 'storage', onChange); - return $.forceSync = function(key) { - key = g.NAMESPACE + key; - return onChange({ - key: key, - newValue: $.getValue(key) + } else { + $.setValue = function() {}; + $.deleteValue = function() {}; + $.cantSync = $.cantSet = true; + } + if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { + $.sync = function(key, cb) { + return $.syncing[key] = GM_addValueChangeListener(g.NAMESPACE + key, function(key2, oldValue, newValue, remote) { + if (remote) { + if (newValue !== void 0) { + newValue = $.dict.json(newValue); + } + return cb(newValue, key); + } }); }; - })(); - } else { - $.sync = function() {}; - $.forceSync = function() {}; - } - - $["delete"] = function(keys) { - var i, key, len; - if (!(keys instanceof Array)) { - keys = [keys]; - } - for (i = 0, len = keys.length; i < len; i++) { - key = keys[i]; - $.deleteValue(g.NAMESPACE + key); - } - }; - - $.get = function(key, val, cb) { - var items; - if (typeof cb === 'function') { - items = $.item(key, val); + $.forceSync = function() {}; + } else if ((typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) || $.hasStorage) { + $.sync = function(key, cb) { + key = g.NAMESPACE + key; + $.syncing[key] = cb; + return $.oldValue[key] = $.getValue(key); + }; + (function() { + var onChange; + onChange = function(arg) { + var cb, key, newValue; + key = arg.key, newValue = arg.newValue; + if (!(cb = $.syncing[key])) { + return; + } + if (newValue != null) { + if (newValue === $.oldValue[key]) { + return; + } + $.oldValue[key] = newValue; + return cb($.dict.json(newValue), key.slice(g.NAMESPACE.length)); + } else { + if ($.oldValue[key] == null) { + return; + } + delete $.oldValue[key]; + return cb(void 0, key.slice(g.NAMESPACE.length)); + } + }; + $.on(window, 'storage', onChange); + return $.forceSync = function(key) { + key = g.NAMESPACE + key; + return onChange({ + key: key, + newValue: $.getValue(key) + }); + }; + })(); } else { - items = key; - cb = val; + $.sync = function() {}; + $.forceSync = function() {}; } - return $.queueTask($.getSync, items, cb); - }; - - $.getSync = function(items, cb) { - var key, val2; - for (key in items) { - if ((val2 = $.getValue(g.NAMESPACE + key))) { - items[key] = JSON.parse(val2); + $["delete"] = function(keys) { + var j, key, len; + if (!(keys instanceof Array)) { + keys = [keys]; } - } - return cb(items); - }; - - $.set = function(keys, val, cb) { - var key, value; - if (typeof keys === 'string') { - $.setValue(g.NAMESPACE + keys, JSON.stringify(val)); - } else { - for (key in keys) { - value = keys[key]; - $.setValue(g.NAMESPACE + key, JSON.stringify(value)); + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + $.deleteValue(g.NAMESPACE + key); } - cb = val; - } - return typeof cb === "function" ? cb() : void 0; - }; - - $.clear = function(cb) { - var id; - $["delete"](Object.keys(Conf)); - $["delete"](['previousversion', 'AutoWatch', 'QR Size', 'captchas', 'QR.persona', 'hiddenPSA']); - $["delete"]((function() { - var i, len, ref, results; - ref = ['embedding', 'updater', 'thread-stats', 'thread-watcher', 'qr']; - results = []; - for (i = 0, len = ref.length; i < len; i++) { - id = ref[i]; - results.push(id + ".position"); + }; + $.get = $.oneItemSugar(function(items, cb) { + return $.queueTask($.getSync, items, cb); + }); + $.getSync = function(items, cb) { + var err, key, val2; + for (key in items) { + if ((val2 = $.getValue(g.NAMESPACE + key))) { + try { + items[key] = $.dict.json(val2); + } catch (error) { + err = error; + if (!/^(?:undefined)*$/.test(val2)) { + throw err; + } + } + } } - return results; - })()); - try { - $["delete"]($.listValues().map(function(key) { - return key.replace(g.NAMESPACE, ''); - })); - } catch (_error) {} - return typeof cb === "function" ? cb() : void 0; - }; + return cb(items); + }; + $.set = $.oneItemSugar(function(items, cb) { + $.securityCheck(items); + return $.queueTask(function() { + var key, value; + for (key in items) { + value = items[key]; + $.setValue(g.NAMESPACE + key, JSON.stringify(value)); + } + return typeof cb === "function" ? cb() : void 0; + }); + }); + $.clear = function(cb) { + $["delete"](Object.keys(Conf)); + $["delete"](['previousversion', 'QR Size', 'QR.persona']); + try { + $["delete"]($.listValues().map(function(key) { + return key.replace(g.NAMESPACE, ''); + })); + } catch (error) {} + return typeof cb === "function" ? cb() : void 0; + }; + } return $; }).call(this); $$ = (function() { - var slice = [].slice; + var $$, + slice = [].slice; - return function(selector, root) { + $$ = function(selector, root) { if (root == null) { root = d.body; } return slice.call(root.querySelectorAll(selector)); }; + return $$; + }).call(this); CrossOrigin = (function() { - var CrossOrigin; + var CrossOrigin, Request; CrossOrigin = { binary: function(url, cb, headers) { - var options, ref, workaround; + var fallback, gmOptions; if (headers == null) { - headers = {}; + headers = $.dict(); } - url = url.replace(/^((?:https?:)?\/\/(?:\w+\.)?4c(?:ha|d)n\.org)\/adv\//, '$1//adv/'); - workaround = $.engine === 'gecko' && (typeof GM_info !== "undefined" && GM_info !== null) && /^[0-2]\.|^3\.[01](?!\d)/.test(GM_info.version); - workaround || (workaround = /PaleMoon\//.test(navigator.userAgent)); - workaround || (workaround = (typeof GM_info !== "undefined" && GM_info !== null ? (ref = GM_info.script) != null ? ref.includeJSB : void 0 : void 0) != null); - options = { + url = url.replace(/^((?:https?:)?\/\/(?:\w+\.)?(?:4chan|4channel|4cdn)\.org)\/adv\//, '$1//adv/'); + fallback = function() { + return $.ajax(url, { + headers: headers, + responseType: 'arraybuffer', + onloadend: function() { + if (this.status && this.response) { + return cb(new Uint8Array(this.response), this.getAllResponseHeaders()); + } else { + return cb(null); + } + } + }); + }; + if (!(((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) != null) || (typeof GM_xmlhttpRequest !== "undefined" && GM_xmlhttpRequest !== null))) { + fallback(); + return; + } + gmOptions = { method: "GET", url: url, headers: headers, + responseType: 'arraybuffer', + overrideMimeType: 'text/plain; charset=x-user-defined', onload: function(xhr) { - var contentDisposition, contentType, data, i, r, ref1, ref2; - if (workaround) { + var data, i, r; + if (xhr.response instanceof ArrayBuffer) { + data = new Uint8Array(xhr.response); + } else { r = xhr.responseText; data = new Uint8Array(r.length); i = 0; @@ -4633,12 +5768,8 @@ CrossOrigin = (function() { data[i] = r.charCodeAt(i); i++; } - } else { - data = new Uint8Array(xhr.response); } - contentType = (ref1 = xhr.responseHeaders.match(/Content-Type:\s*(.*)/i)) != null ? ref1[1] : void 0; - contentDisposition = (ref2 = xhr.responseHeaders.match(/Content-Disposition:\s*(.*)/i)) != null ? ref2[1] : void 0; - return cb(data, contentType, contentDisposition); + return cb(data, xhr.responseHeaders); }, onerror: function() { return cb(null); @@ -4647,27 +5778,28 @@ CrossOrigin = (function() { return cb(null); } }; - if (workaround) { - options.overrideMimeType = 'text/plain; charset=x-user-defined'; - } else { - options.responseType = 'arraybuffer'; + try { + return ((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) || GM_xmlhttpRequest)(gmOptions); + } catch (error) { + return fallback(); } - return GM_xmlhttpRequest(options); }, file: function(url, cb) { - return CrossOrigin.binary(url, function(data, contentType, contentDisposition) { - var blob, match, mime, name, ref, ref1, ref2, ref3; + return CrossOrigin.binary(url, function(data, headers) { + var blob, contentDisposition, contentType, match, mime, name, ref, ref1, ref2, ref3, ref4; if (data == null) { return cb(null); } - name = (ref = url.match(/([^\/]+)\/*$/)) != null ? ref[1] : void 0; + name = (ref = url.match(/([^\/?#]+)\/*(?:$|[?#])/)) != null ? ref[1] : void 0; + contentType = (ref1 = headers.match(/Content-Type:\s*(.*)/i)) != null ? ref1[1] : void 0; + contentDisposition = (ref2 = headers.match(/Content-Disposition:\s*(.*)/i)) != null ? ref2[1] : void 0; mime = (contentType != null ? contentType.match(/[^;]*/)[0] : void 0) || 'application/octet-stream'; - match = (contentDisposition != null ? (ref1 = contentDisposition.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)) != null ? ref1[1] : void 0 : void 0) || (contentType != null ? (ref2 = contentType.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)) != null ? ref2[1] : void 0 : void 0); + match = (contentDisposition != null ? (ref3 = contentDisposition.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)) != null ? ref3[1] : void 0 : void 0) || (contentType != null ? (ref4 = contentType.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)) != null ? ref4[1] : void 0 : void 0); if (match) { name = match.replace(/\\"/g, '"'); } - if ((typeof GM_info !== "undefined" && GM_info !== null ? (ref3 = GM_info.script) != null ? ref3.includeJSB : void 0 : void 0) != null) { - mime = QR.typeFromExtension[name.match(/[^.]*$/)[0].toLowerCase()] || 'application/octet-stream'; + if (/^text\/plain;\s*charset=x-user-defined$/i.test(mime)) { + mime = $.getOwn(QR.typeFromExtension, name.match(/[^.]*$/)[0].toLowerCase()) || 'application/octet-stream'; } blob = new Blob([data], { type: mime @@ -4676,43 +5808,117 @@ CrossOrigin = (function() { return cb(blob); }); }, - json: (function() { - var callbacks, responses; - callbacks = {}; - responses = {}; - return function(url, cb) { - if (responses[url]) { - cb(responses[url]); - return; - } - if (callbacks[url]) { - callbacks[url].push(cb); - return; - } - callbacks[url] = [cb]; - return GM_xmlhttpRequest({ - method: "GET", - url: url + '', - onload: function(xhr) { - var j, len, ref, response; - response = JSON.parse(xhr.responseText); - ref = callbacks[url]; - for (j = 0, len = ref.length; j < len; j++) { - cb = ref[j]; - cb(response); + Request: Request = (function() { + function Request() {} + + Request.prototype.status = 0; + + Request.prototype.statusText = ''; + + Request.prototype.response = null; + + Request.prototype.responseHeaderString = null; + + Request.prototype.getResponseHeader = function(headerName) { + var header, i, j, key, len, ref, ref1, ref2, val; + if ((this.responseHeaders == null) && (this.responseHeaderString != null)) { + this.responseHeaders = $.dict(); + ref = this.responseHeaderString.split('\r\n'); + for (j = 0, len = ref.length; j < len; j++) { + header = ref[j]; + if ((i = header.indexOf(':')) >= 0) { + key = header.slice(0, i).trim().toLowerCase(); + val = header.slice(i + 1).trim(); + this.responseHeaders[key] = val; } - delete callbacks[url]; - return responses[url] = response; - }, - onerror: function() { - return delete callbacks[url]; - }, - onabort: function() { - return delete callbacks[url]; } - }); + } + return (ref1 = (ref2 = this.responseHeaders) != null ? ref2[headerName.toLowerCase()] : void 0) != null ? ref1 : null; + }; + + Request.prototype.abort = function() {}; + + Request.prototype.onloadend = function() {}; + + return Request; + + })(), + ajax: function(url, options) { + var gmOptions, gmReq, headers, onloadend, req, responseType, timeout; + if (options == null) { + options = {}; + } + onloadend = options.onloadend, timeout = options.timeout, responseType = options.responseType, headers = options.headers; + if (responseType == null) { + responseType = 'json'; + } + if (!(((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) != null) || (typeof GM_xmlhttpRequest !== "undefined" && GM_xmlhttpRequest !== null))) { + return $.ajax(url, options); + } + req = new CrossOrigin.Request(); + req.onloadend = onloadend; + gmOptions = { + method: 'GET', + url: url, + headers: headers, + timeout: timeout, + onload: function(xhr) { + var response; + try { + response = (function() { + switch (responseType) { + case 'json': + if (xhr.responseText) { + return JSON.parse(xhr.responseText); + } else { + return null; + } + break; + default: + return xhr.responseText; + } + })(); + $.extend(req, { + response: response, + status: xhr.status, + statusText: xhr.statusText, + responseHeaderString: xhr.responseHeaders + }); + } catch (error) {} + return req.onloadend(); + }, + onerror: function() { + return req.onloadend(); + }, + onabort: function() { + return req.onloadend(); + }, + ontimeout: function() { + return req.onloadend(); + } }; - })() + try { + gmReq = ((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) || GM_xmlhttpRequest)(gmOptions); + } catch (error) { + return $.ajax(url, options); + } + if (gmReq && typeof gmReq.abort === 'function') { + req.abort = function() { + try { + return gmReq.abort(); + } catch (error) {} + }; + } + return req; + }, + cache: function(url, cb) { + return $.cache(url, cb, { + ajax: CrossOrigin.ajax + }); + }, + permission: function(cb) { + return cb(); + } }; return CrossOrigin; @@ -4728,12 +5934,35 @@ Board = (function() { }; function Board(ID) { + var ref; this.ID = ID; + this.boardID = this.ID; + this.siteID = g.SITE.ID; this.threads = new SimpleDict(); this.posts = new SimpleDict(); + this.config = ((ref = BoardConfig.boards) != null ? ref[this.ID] : void 0) || {}; g.boards[this] = this; } + Board.prototype.cooldowns = function() { + var c, c2, i, key, len, ref; + c2 = (this.config || {}).cooldowns || {}; + c = { + thread: c2.threads || 0, + reply: c2.replies || 0, + image: c2.images || 0, + thread_global: 300 + }; + if (d.cookie.indexOf('pass_enabled=1') >= 0) { + ref = ['reply', 'image']; + for (i = 0, len = ref.length; i < len; i++) { + key = ref[i]; + c[key] = Math.ceil(c[key] / 2); + } + } + return c; + }; + return Board; })(); @@ -4752,6 +5981,8 @@ Callbacks = (function() { Callbacks.CatalogThread = new Callbacks('Catalog Thread'); + Callbacks.CatalogThreadNative = new Callbacks('Catalog Thread'); + function Callbacks(type) { this.type = type; this.keys = []; @@ -4766,19 +5997,26 @@ Callbacks = (function() { return this[name] = cb; }; - Callbacks.prototype.execute = function(node, keys) { + Callbacks.prototype.execute = function(node, keys, force) { var err, errors, i, len, name, ref, ref1, ref2; if (keys == null) { keys = this.keys; } + if (force == null) { + force = false; + } + if (node.callbacksExecuted && !force) { + return; + } + node.callbacksExecuted = true; for (i = 0, len = keys.length; i < len; i++) { name = keys[i]; try { if ((ref = this[name]) != null) { ref.call(node); } - } catch (_error) { - err = _error; + } catch (error) { + err = error; if (!errors) { errors = []; } @@ -4811,17 +6049,19 @@ CatalogThread = (function() { }; function CatalogThread(root, thread) { + var post; this.thread = thread; this.ID = this.thread.ID; this.board = this.thread.board; + post = this.thread.OP.nodes.post; this.nodes = { root: root, - thumb: $('.catalog-thumb', root), - icons: $('.catalog-icons', root), - postCount: $('.post-count', root), - fileCount: $('.file-count', root), - pageCount: $('.page-count', root), - comment: $('.comment', root) + thumb: $('.catalog-thumb', post), + icons: $('.catalog-icons', post), + postCount: $('.post-count', post), + fileCount: $('.file-count', post), + pageCount: $('.page-count', post), + replies: null }; this.thread.catalogView = this; } @@ -4834,6 +6074,34 @@ CatalogThread = (function() { }).call(this); +CatalogThreadNative = (function() { + var CatalogThreadNative; + + CatalogThreadNative = (function() { + CatalogThreadNative.prototype.toString = function() { + return this.ID; + }; + + function CatalogThreadNative(root) { + this.nodes = { + root: root, + thumb: $(g.SITE.selectors.catalog.thumb, root) + }; + this.siteID = g.SITE.ID; + this.boardID = this.nodes.thumb.parentNode.pathname.split(/\/+/)[1]; + this.board = g.boards[this.boardID] || new Board(this.boardID); + this.ID = this.threadID = +(root.dataset.id || root.id).match(/\d*$/)[0]; + this.thread = this.board.threads.get(this.ID) || new Thread(this.ID, this.board); + } + + return CatalogThreadNative; + + })(); + + return CatalogThreadNative; + +}).call(this); + Connection = (function() { var Connection, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; @@ -4861,15 +6129,15 @@ Connection = (function() { }; Connection.prototype.onMessage = function(e) { - var base, data, type, value; + var data, type, value; if (!(e.source === this.targetWindow() && e.origin === this.origin && typeof e.data === 'string' && e.data.slice(0, g.NAMESPACE.length) === g.NAMESPACE)) { return; } data = JSON.parse(e.data.slice(g.NAMESPACE.length)); for (type in data) { value = data[type]; - if (typeof (base = this.cb)[type] === "function") { - base[type](value); + if ($.hasOwn(this.cb, type)) { + this.cb[type](value); } } }; @@ -4887,13 +6155,13 @@ DataBoard = (function() { bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; DataBoard = (function() { - DataBoard.keys = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads', 'customTitles']; + DataBoard.keys = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads', 'watcherLastModified', 'customTitles']; - function DataBoard(key, sync, dontClean) { + function DataBoard(key1, sync, dontClean) { var init; - this.key = key; + this.key = key1; this.onSync = bind(this.onSync, this); - this.data = Conf[this.key]; + this.initData(Conf[this.key]); $.sync(this.key, this.onSync); if (!dontClean) { this.clean(); @@ -4910,71 +6178,208 @@ DataBoard = (function() { $.on(d, '4chanXInitFinished', init); } - DataBoard.prototype.save = function(cb) { - return $.set(this.key, this.data, cb); + DataBoard.prototype.initData = function(data1) { + var base, boards, lastChecked, name, ref; + this.data = data1; + if (this.data.boards) { + ref = this.data, boards = ref.boards, lastChecked = ref.lastChecked; + this.data['4chan.org'] = { + boards: boards, + lastChecked: lastChecked + }; + delete this.data.boards; + delete this.data.lastChecked; + } + return (base = this.data)[name = g.SITE.ID] || (base[name] = { + boards: $.dict() + }); }; - DataBoard.prototype["delete"] = function(arg) { - var boardID, postID, ref, threadID; - boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID; - $.forceSync(this.key); - if (postID) { - if (!((ref = this.data.boards[boardID]) != null ? ref[threadID] : void 0)) { - return; - } - delete this.data.boards[boardID][threadID][postID]; - this.deleteIfEmpty({ - boardID: boardID, - threadID: threadID - }); - } else if (threadID) { - if (!this.data.boards[boardID]) { - return; - } - delete this.data.boards[boardID][threadID]; - this.deleteIfEmpty({ - boardID: boardID - }); - } else { - delete this.data.boards[boardID]; + DataBoard.prototype.changes = []; + + DataBoard.prototype.save = function(change, cb) { + change(); + this.changes.push(change); + return $.get(this.key, { + boards: $.dict() + }, (function(_this) { + return function(items) { + var i, len, needSync, ref; + if (!_this.changes.length) { + return; + } + needSync = (items[_this.key].version || 0) > (_this.data.version || 0); + if (needSync) { + _this.initData(items[_this.key]); + ref = _this.changes; + for (i = 0, len = ref.length; i < len; i++) { + change = ref[i]; + change(); + } + } + _this.changes = []; + _this.data.version = (_this.data.version || 0) + 1; + return $.set(_this.key, _this.data, function() { + if (needSync) { + if (typeof _this.sync === "function") { + _this.sync(); + } + } + return typeof cb === "function" ? cb() : void 0; + }); + }; + })(this)); + }; + + DataBoard.prototype.forceSync = function(cb) { + return $.get(this.key, { + boards: $.dict() + }, (function(_this) { + return function(items) { + var change, i, len, ref; + if ((items[_this.key].version || 0) > (_this.data.version || 0)) { + _this.initData(items[_this.key]); + ref = _this.changes; + for (i = 0, len = ref.length; i < len; i++) { + change = ref[i]; + change(); + } + if (typeof _this.sync === "function") { + _this.sync(); + } + } + return typeof cb === "function" ? cb() : void 0; + }; + })(this)); + }; + + DataBoard.prototype["delete"] = function(arg, cb) { + var boardID, postID, siteID, threadID; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID; + siteID || (siteID = g.SITE.ID); + if (!this.data[siteID]) { + return; } - return this.save(); + return this.save((function(_this) { + return function() { + var ref; + if (postID) { + if (!((ref = _this.data[siteID].boards[boardID]) != null ? ref[threadID] : void 0)) { + return; + } + delete _this.data[siteID].boards[boardID][threadID][postID]; + return _this.deleteIfEmpty({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }); + } else if (threadID) { + if (!_this.data[siteID].boards[boardID]) { + return; + } + delete _this.data[siteID].boards[boardID][threadID]; + return _this.deleteIfEmpty({ + siteID: siteID, + boardID: boardID + }); + } else { + return delete _this.data[siteID].boards[boardID]; + } + }; + })(this), cb); }; DataBoard.prototype.deleteIfEmpty = function(arg) { - var boardID, threadID; - boardID = arg.boardID, threadID = arg.threadID; - $.forceSync(this.key); + var boardID, siteID, threadID; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID; + if (!this.data[siteID]) { + return; + } if (threadID) { - if (!Object.keys(this.data.boards[boardID][threadID]).length) { - delete this.data.boards[boardID][threadID]; + if (!Object.keys(this.data[siteID].boards[boardID][threadID]).length) { + delete this.data[siteID].boards[boardID][threadID]; return this.deleteIfEmpty({ + siteID: siteID, boardID: boardID }); } - } else if (!Object.keys(this.data.boards[boardID]).length) { - return delete this.data.boards[boardID]; + } else if (!Object.keys(this.data[siteID].boards[boardID]).length) { + return delete this.data[siteID].boards[boardID]; } }; - DataBoard.prototype.set = function(arg, cb) { - var base, base1, base2, boardID, postID, threadID, val; - boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID, val = arg.val; - $.forceSync(this.key); + DataBoard.prototype.set = function(data, cb) { + return this.save((function(_this) { + return function() { + return _this.setUnsafe(data); + }; + })(this), cb); + }; + + DataBoard.prototype.setUnsafe = function(arg) { + var base, base1, base2, base3, boardID, postID, siteID, threadID, val; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID, val = arg.val; + siteID || (siteID = g.SITE.ID); + (base = this.data)[siteID] || (base[siteID] = { + boards: $.dict() + }); if (postID !== void 0) { - ((base = ((base1 = this.data.boards)[boardID] || (base1[boardID] = {})))[threadID] || (base[threadID] = {}))[postID] = val; + return ((base1 = ((base2 = this.data[siteID].boards)[boardID] || (base2[boardID] = $.dict())))[threadID] || (base1[threadID] = $.dict()))[postID] = val; } else if (threadID !== void 0) { - ((base2 = this.data.boards)[boardID] || (base2[boardID] = {}))[threadID] = val; + return ((base3 = this.data[siteID].boards)[boardID] || (base3[boardID] = $.dict()))[threadID] = val; } else { - this.data.boards[boardID] = val; + return this.data[siteID].boards[boardID] = val; + } + }; + + DataBoard.prototype.extend = function(arg, cb) { + var boardID, postID, siteID, threadID, val; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID, val = arg.val; + return this.save((function(_this) { + return function() { + var key, oldVal, subVal; + oldVal = _this.get({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + postID: postID, + defaultValue: $.dict() + }); + for (key in val) { + subVal = val[key]; + if (typeof subVal === 'undefined') { + delete oldVal[key]; + } else { + oldVal[key] = subVal; + } + } + return _this.setUnsafe({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + postID: postID, + val: oldVal + }); + }; + })(this), cb); + }; + + DataBoard.prototype.setLastChecked = function(key) { + if (key == null) { + key = 'lastChecked'; } - return this.save(cb); + return this.save((function(_this) { + return function() { + return _this.data[key] = Date.now(); + }; + })(this)); }; DataBoard.prototype.get = function(arg) { - var ID, board, boardID, defaultValue, i, len, postID, thread, threadID, val; - boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID, defaultValue = arg.defaultValue; - if (board = this.data.boards[boardID]) { + var ID, board, boardID, defaultValue, i, len, postID, ref, siteID, thread, threadID, val; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID, defaultValue = arg.defaultValue; + siteID || (siteID = g.SITE.ID); + if (board = (ref = this.data[siteID]) != null ? ref.boards[boardID] : void 0) { if (threadID == null) { if (postID != null) { for (thread = i = 0, len = board.length; i < len; thread = ++i) { @@ -4994,53 +6399,66 @@ DataBoard = (function() { return val || defaultValue; }; - DataBoard.prototype.forceSync = function() { - return $.forceSync(this.key); - }; - DataBoard.prototype.clean = function() { - var boardID, now, ref, val; - $.forceSync(this.key); - ref = this.data.boards; + var boardID, now, ref, ref1, siteID, val; + siteID = g.SITE.ID; + ref = this.data[siteID].boards; for (boardID in ref) { val = ref[boardID]; this.deleteIfEmpty({ + siteID: siteID, boardID: boardID }); } now = Date.now(); - if ((this.data.lastChecked || 0) < now - 2 * $.HOUR) { - this.data.lastChecked = now; - for (boardID in this.data.boards) { + if (!((now - 2 * $.HOUR < (ref1 = this.data[siteID].lastChecked || 0) && ref1 <= now))) { + this.data[siteID].lastChecked = now; + for (boardID in this.data[siteID].boards) { this.ajaxClean(boardID); } } }; DataBoard.prototype.ajaxClean = function(boardID) { - return $.cache("//a.4cdn.org/" + boardID + "/threads.json", (function(_this) { - return function(e1) { - var ref; - if ((ref = e1.target.status) !== 200 && ref !== 404) { + var base, siteID, that, threadsList; + that = this; + siteID = g.SITE.ID; + threadsList = typeof (base = g.SITE.urls).threadsListJSON === "function" ? base.threadsListJSON({ + siteID: siteID, + boardID: boardID + }) : void 0; + if (!threadsList) { + return; + } + return $.cache(threadsList, function() { + var archiveList, base1, response1; + if (this.status !== 200) { + return; + } + archiveList = typeof (base1 = g.SITE.urls).archiveListJSON === "function" ? base1.archiveListJSON({ + siteID: siteID, + boardID: boardID + }) : void 0; + if (!archiveList) { + return that.ajaxCleanParse(boardID, this.response); + } + response1 = this.response; + return $.cache(archiveList, function() { + if (!(this.status === 200 || (!g.SITE.archivedBoardsKnown && this.status === 404))) { return; } - return $.cache("//a.4cdn.org/" + boardID + "/archive.json", function(e2) { - var ref1; - if ((ref1 = e2.target.status) !== 200 && ref1 !== 404) { - return; - } - return _this.ajaxCleanParse(boardID, e1.target.response, e2.target.response); - }); - }; - })(this)); + return that.ajaxCleanParse(boardID, response1, this.response); + }); + }); }; DataBoard.prototype.ajaxCleanParse = function(boardID, response1, response2) { - var ID, board, i, j, k, len, len1, len2, page, ref, thread, threads; - if (!(board = this.data.boards[boardID])) { + var ID, board, i, j, k, len, len1, len2, page, ref, siteID, thread, threads; + siteID = g.SITE.ID; + if (!(board = this.data[siteID].boards[boardID])) { return; } - threads = {}; + threads = $.dict(); if (response1) { for (i = 0, len = response1.length; i < len; i++) { page = response1[i]; @@ -5062,17 +6480,19 @@ DataBoard = (function() { } } } - this.data.boards[boardID] = threads; + this.data[siteID].boards[boardID] = threads; this.deleteIfEmpty({ + siteID: siteID, boardID: boardID }); - return this.save(); + return $.set(this.key, this.data); }; DataBoard.prototype.onSync = function(data) { - this.data = data || { - boards: {} - }; + if (!((data.version || 0) > (this.data.version || 0))) { + return; + } + this.initData(data); return typeof this.sync === "function" ? this.sync() : void 0; }; @@ -5090,23 +6510,36 @@ Fetcher = (function() { Fetcher = (function() { function Fetcher(boardID1, threadID, postID1, root, quoter) { - var post; + var board, post, ref, that, thread; this.boardID = boardID1; this.threadID = threadID; this.postID = postID1; this.root = root; this.quoter = quoter; - if (post = g.posts[this.boardID + "." + this.postID]) { + if (post = g.posts.get(this.boardID + "." + this.postID)) { + this.insert(post); + return; + } + if ((post = (ref = Index.replyData) != null ? ref[this.boardID + "." + this.postID] : void 0) && (thread = g.threads.get(this.boardID + "." + this.threadID))) { + board = g.boards[this.boardID]; + post = new Post(g.SITE.Build.postFromObject(post, this.boardID), thread, board, { + isFetchedQuote: true + }); + Main.callbackNodes('Post', [post]); this.insert(post); return; } this.root.textContent = "Loading post No." + this.postID + "..."; if (this.threadID) { - $.cache("//a.4cdn.org/" + this.boardID + "/thread/" + this.threadID + ".json", (function(_this) { - return function(e, isCached) { - return _this.fetchedPost(e.target, isCached); - }; - })(this)); + that = this; + $.cache(g.SITE.urls.threadJSON({ + boardID: this.boardID, + threadID: this.threadID + }), function(arg) { + var isCached; + isCached = arg.isCached; + return that.fetchedPost(this, isCached); + }); } else { this.archivedPost(); } @@ -5117,6 +6550,7 @@ Fetcher = (function() { if (!this.root.parentNode) { return; } + this.quoter || (this.quoter = post); clone = post.addClone(this.quoter.context, $.hasClass(this.root, 'dialog')); Main.callbackNodes('Post', [clone]); nodes = clone.nodes; @@ -5140,26 +6574,26 @@ Fetcher = (function() { } $.rmAll(this.root); $.add(this.root, nodes.root); - return $.event('PostsInserted'); + return $.event('PostsInserted', null, this.root); }; Fetcher.prototype.fetchedPost = function(req, isCached) { - var api, board, k, len, post, posts, status, thread; - if (post = g.posts[this.boardID + "." + this.postID]) { + var api, board, k, len, post, posts, status, that, thread; + if (post = g.posts.get(this.boardID + "." + this.postID)) { this.insert(post); return; } status = req.status; if (status !== 200) { - if (this.archivedPost()) { + if (status && this.archivedPost()) { return; } $.addClass(this.root, 'warning'); - this.root.textContent = status === 404 ? "Thread No." + this.threadID + " 404'd." : "Error " + req.statusText + " (" + req.status + ")."; + this.root.textContent = status === 404 ? "Thread No." + this.threadID + " 404'd." : !status ? 'Connection Error' : "Error " + req.statusText + " (" + req.status + ")."; return; } posts = req.response.posts; - Build.spoilerRange[this.boardID] = posts[0].custom_spoiler; + g.SITE.Build.spoilerRange[this.boardID] = posts[0].custom_spoiler; for (k = 0, len = posts.length; k < len; k++) { post = posts[k]; if (post.no === this.postID) { @@ -5168,15 +6602,17 @@ Fetcher = (function() { } if (post.no !== this.postID) { if (isCached) { - api = "//a.4cdn.org/" + this.boardID + "/thread/" + this.threadID + ".json"; + api = g.SITE.urls.threadJSON({ + boardID: this.boardID, + threadID: this.threadID + }); $.cleanCache(function(url) { return url === api; }); - $.cache(api, (function(_this) { - return function(e) { - return _this.fetchedPost(e.target, false); - }; - })(this)); + that = this; + $.cache(api, function() { + return that.fetchedPost(this, false); + }); return; } if (this.archivedPost()) { @@ -5187,15 +6623,16 @@ Fetcher = (function() { return; } board = g.boards[this.boardID] || new Board(this.boardID); - thread = g.threads[this.boardID + "." + this.threadID] || new Thread(this.threadID, board); - post = new Post(Build.postFromObject(post, this.boardID), thread, board); - post.isFetchedQuote = true; + thread = g.threads.get(this.boardID + "." + this.threadID) || new Thread(this.threadID, board); + post = new Post(g.SITE.Build.postFromObject(post, this.boardID), thread, board, { + isFetchedQuote: true + }); Main.callbackNodes('Post', [post]); return this.insert(post); }; Fetcher.prototype.archivedPost = function() { - var archive, url; + var archive, encryptionOK, that, url; if (!Conf['Resurrect Quotes']) { return false; } @@ -5206,41 +6643,31 @@ Fetcher = (function() { return false; } archive = Redirect.data.post[this.boardID]; - if (/^https:\/\//.test(url) || location.protocol === 'http:') { - $.cache(url, (function(_this) { - return function(e) { - return _this.parseArchivedPost(e.target.response, url, archive); - }; - })(this), { - responseType: 'json', - withCredentials: archive.withCredentials - }); - return true; - } else if (Conf['Exempt Archives from Encryption']) { - CrossOrigin.json(url, (function(_this) { - return function(response) { - var key, media, ref; - media = response.media; - if (media) { - for (key in media) { - if (/_link$/.test(key)) { - if (!((ref = media[key]) != null ? ref.match(/^http:\/\//) : void 0)) { - delete media[key]; - } + encryptionOK = /^https:\/\//.test(url) || location.protocol === 'http:'; + if (encryptionOK || Conf['Exempt Archives from Encryption']) { + that = this; + CrossOrigin.cache(url, function() { + var key, media, ref, ref1; + if (!encryptionOK && ((ref = this.response) != null ? ref.media : void 0)) { + media = this.response.media; + for (key in media) { + if (/_link$/.test(key)) { + if (!((ref1 = $.getOwn(media, key)) != null ? ref1.match(/^http:\/\//) : void 0)) { + delete media[key]; } } } - return _this.parseArchivedPost(response, url, archive); - }; - })(this)); + } + return that.parseArchivedPost(this.response, url, archive); + }); return true; } return false; }; Fetcher.prototype.parseArchivedPost = function(data, url, archive) { - var board, comment, greentext, i, j, key, o, post, ref, ref1, tag, text, text2, thread, val; - if (post = g.posts[this.boardID + "." + this.postID]) { + var board, comment, greentext, i, j, media_link, o, post, ref, tag, text, text2, thread, thumb_link; + if (post = g.posts.get(this.boardID + "." + this.postID)) { this.insert(post); return; } @@ -5276,26 +6703,20 @@ Fetcher = (function() { results1 = []; for (j = l = 0, len1 = ref.length; l < len1; j = ++l) { text2 = ref[j]; - results1.push({ - innerHTML: ((j % 2) ? "" + E(text2) + "" : E(text2)) - }); + results1.push({innerHTML: ((j % 2) ? "" + E(text2) + "" : E(text2))}); } return results1; })(); - text = { - innerHTML: ((greentext) ? "" + E.cat(text) + "" : E.cat(text)) - }; + text = {innerHTML: ((greentext) ? "" + E.cat(text) + "" : E.cat(text))}; results.push(text); } } return results; }).call(this); - comment = { - innerHTML: E.cat(comment) - }; + comment = {innerHTML: E.cat(comment)}; this.threadID = +data.thread_num; o = { - postID: this.postID, + ID: this.postID, threadID: this.threadID, boardID: this.boardID, isReply: this.postID !== this.threadID @@ -5313,11 +6734,18 @@ Fetcher = (function() { return 'Admin'; case 'D': return 'Developer'; + case 'V': + return 'Verified'; + case 'F': + return 'Founder'; + case 'G': + return 'Manager'; } })(), uniqueID: data.poster_hash, flagCode: data.poster_country, - flag: data.poster_country_name, + flagCodeTroll: data.troll_country_code, + flag: data.poster_country_name || data.troll_country_name, dateUTC: data.timestamp, dateText: data.fourchan_date, commentHTML: comment @@ -5325,22 +6753,31 @@ Fetcher = (function() { if (o.info.capcode) { delete o.info.uniqueID; } - if ((ref = data.media) != null ? ref.media_filename : void 0) { - ref1 = data.media; - for (key in ref1) { - val = ref1[key]; - if (/_link$/.test(key) && (val != null ? val[0] : void 0) === '/') { - data.media[key] = url.split('/', 3).join('/') + val; - } + if (data.media && !!+data.media.banned) { + o.fileDeleted = true; + } else if ((ref = data.media) != null ? ref.media_filename : void 0) { + thumb_link = data.media.thumb_link; + if ((thumb_link != null ? thumb_link[0] : void 0) === '/') { + thumb_link = url.split('/', 3).join('/') + thumb_link; + } + if (!Redirect.securityCheck(thumb_link)) { + thumb_link = ''; + } + media_link = Redirect.to('file', { + boardID: this.boardID, + filename: data.media.media_orig + }); + if (!Redirect.securityCheck(media_link)) { + media_link = ''; } o.file = { name: data.media.media_filename, - url: data.media.media_link || data.media.remote_media_link || (location.protocol + "//i.4cdn.org/" + this.boardID + "/" + (encodeURIComponent(data.media[this.boardID === 'f' ? 'media_filename' : 'media_orig']))), + url: media_link || (this.boardID === 'f' ? location.protocol + "//" + (ImageHost.flashHost()) + "/" + this.boardID + "/" + (encodeURIComponent(E(data.media.media_filename))) : location.protocol + "//" + (ImageHost.host()) + "/" + this.boardID + "/" + data.media.media_orig), height: data.media.media_h, width: data.media.media_w, MD5: data.media.media_hash, size: $.bytesToString(data.media.media_size), - thumbURL: data.media.thumb_link || (location.protocol + "//i.4cdn.org/" + this.boardID + "/" + data.media.preview_orig), + thumbURL: thumb_link || (location.protocol + "//" + (ImageHost.thumbHost()) + "/" + this.boardID + "/" + data.media.preview_orig), theight: data.media.preview_h, twidth: data.media.preview_w, isSpoiler: data.media.spoiler === '1' @@ -5352,84 +6789,44 @@ Fetcher = (function() { o.file.tag = JSON.parse(data.media.exif).Tag; } } + o.extra = $.dict(); board = g.boards[this.boardID] || new Board(this.boardID); - thread = g.threads[this.boardID + "." + this.threadID] || new Thread(this.threadID, board); - post = new Post(Build.post(o), thread, board); + thread = g.threads.get(this.boardID + "." + this.threadID) || new Thread(this.threadID, board); + post = new Post(g.SITE.Build.post(o), thread, board, { + isFetchedQuote: true + }); post.kill(); if (post.file) { post.file.thumbURL = o.file.thumbURL; } - post.isFetchedQuote = true; Main.callbackNodes('Post', [post]); return this.insert(post); }; Fetcher.prototype.archiveTags = { - '\n': { - innerHTML: "
      " - }, - '[b]': { - innerHTML: "" - }, - '[/b]': { - innerHTML: "" - }, - '[spoiler]': { - innerHTML: "" - }, - '[/spoiler]': { - innerHTML: "" - }, - '[code]': { - innerHTML: "
      "
      -      },
      -      '[/code]': {
      -        innerHTML: "
      " - }, - '[moot]': { - innerHTML: "
      " - }, - '[/moot]': { - innerHTML: "
      " - }, - '[banned]': { - innerHTML: "" - }, - '[/banned]': { - innerHTML: "" - }, + '\n': {innerHTML: "
      "}, + '[b]': {innerHTML: ""}, + '[/b]': {innerHTML: ""}, + '[spoiler]': {innerHTML: ""}, + '[/spoiler]': {innerHTML: ""}, + '[code]': {innerHTML: "
      "},
      +      '[/code]': {innerHTML: "
      "}, + '[moot]': {innerHTML: "
      "}, + '[/moot]': {innerHTML: "
      "}, + '[banned]': {innerHTML: ""}, + '[/banned]': {innerHTML: ""}, '[fortune]': function(text) { - return { - innerHTML: "" - }; - }, - '[/fortune]': { - innerHTML: "" - }, - '[i]': { - innerHTML: "" - }, - '[/i]': { - innerHTML: "" + return {innerHTML: ""}; }, - '[red]': { - innerHTML: "" - }, - '[/red]': { - innerHTML: "" - }, - '[green]': { - innerHTML: "" - }, - '[/green]': { - innerHTML: "" - }, - '[blue]': { - innerHTML: "" - }, - '[/blue]': { - innerHTML: "" - } + '[/fortune]': {innerHTML: ""}, + '[i]': {innerHTML: ""}, + '[/i]': {innerHTML: ""}, + '[red]': {innerHTML: ""}, + '[/red]': {innerHTML: ""}, + '[green]': {innerHTML: ""}, + '[/green]': {innerHTML: ""}, + '[blue]': {innerHTML: ""}, + '[/blue]': {innerHTML: ""} }; return Fetcher; @@ -5450,9 +6847,7 @@ Notice = (function() { this.onclose = onclose; this.close = bind(this.close, this); this.add = bind(this.add, this); - this.el = $.el('div', { - innerHTML: "
      " - }); + this.el = $.el('div', {innerHTML: "
      "}); this.el.style.opacity = 0; this.setType(type); $.on(this.el.firstElementChild, 'click', this.close); @@ -5508,121 +6903,152 @@ Post = (function() { return this.ID; }; - function Post(root, thread, board) { - var clone, j, len, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, ref8; + function Post(root, thread, board, flags) { + var clone, j, k, key, len, len1, ref, ref1, ref10, ref11, ref12, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, selector; this.thread = thread; this.board = board; - this.ID = +root.id.slice(2); + if (flags == null) { + flags = {}; + } + $.extend(this, flags); + this.ID = +root.id.match(/\d*$/)[0]; + this.postID = this.ID; + this.threadID = this.thread.ID; + this.boardID = this.board.ID; + this.siteID = g.SITE.ID; this.fullID = this.board + "." + this.ID; this.context = this; + this.isReply = this.ID !== this.threadID; root.dataset.fullID = this.fullID; this.nodes = this.parseNodes(root); - if (!(this.isReply = $.hasClass(this.nodes.post, 'reply'))) { + if (!this.isReply) { this.thread.OP = this; - this.thread.isArchived = !!$('.archivedIcon', this.nodes.info); - this.thread.isSticky = !!$('.stickyIcon', this.nodes.info); - this.thread.isClosed = this.thread.isArchived || !!$('.closedIcon', this.nodes.info); + ref = ['isSticky', 'isClosed', 'isArchived']; + for (j = 0, len = ref.length; j < len; j++) { + key = ref[j]; + if ((selector = g.SITE.selectors.icons[key])) { + this.thread[key] = !!$(selector, this.nodes.info); + } + } if (this.thread.isArchived) { + this.thread.isClosed = true; this.thread.kill(); } } this.info = { - nameBlock: Conf['Anonymize'] ? 'Anonymous' : this.nodes.nameBlock.textContent.trim(), - subject: ((ref = this.nodes.subject) != null ? ref.textContent : void 0) || void 0, - name: (ref1 = this.nodes.name) != null ? ref1.textContent : void 0, - tripcode: (ref2 = this.nodes.tripcode) != null ? ref2.textContent : void 0, - uniqueID: (ref3 = this.nodes.uniqueID) != null ? ref3.firstElementChild.textContent : void 0, - capcode: (ref4 = this.nodes.capcode) != null ? ref4.textContent.replace('## ', '') : void 0, - flagCode: (ref5 = this.nodes.flag) != null ? (ref6 = ref5.className.match(/flag-(\w+)/)) != null ? ref6[1].toUpperCase() : void 0 : void 0, - flag: (ref7 = this.nodes.flag) != null ? ref7.title : void 0, - date: this.nodes.date ? new Date(this.nodes.date.dataset.utc * 1000) : void 0 + subject: ((ref1 = this.nodes.subject) != null ? ref1.textContent : void 0) || void 0, + name: (ref2 = this.nodes.name) != null ? ref2.textContent : void 0, + email: this.nodes.email ? decodeURIComponent(this.nodes.email.href.replace(/^mailto:/, '')) : void 0, + tripcode: (ref3 = this.nodes.tripcode) != null ? ref3.textContent : void 0, + uniqueID: (ref4 = this.nodes.uniqueID) != null ? ref4.textContent : void 0, + capcode: (ref5 = this.nodes.capcode) != null ? ref5.textContent.replace('## ', '') : void 0, + pass: (ref6 = this.nodes.pass) != null ? ref6.title.match(/\d*$/)[0] : void 0, + flagCode: (ref7 = this.nodes.flag) != null ? (ref8 = ref7.className.match(/flag-(\w+)/)) != null ? ref8[1].toUpperCase() : void 0 : void 0, + flagCodeTroll: (ref9 = this.nodes.flag) != null ? (ref10 = ref9.className.match(/bfl-(\w+)/)) != null ? ref10[1].toUpperCase() : void 0 : void 0, + flag: (ref11 = this.nodes.flag) != null ? ref11.title : void 0, + date: this.nodes.date ? g.SITE.parseDate(this.nodes.date) : void 0 }; + if (Conf['Anonymize']) { + this.info.nameBlock = 'Anonymous'; + } else { + this.info.nameBlock = ((this.info.name || '') + " " + (this.info.tripcode || '')).trim(); + } + if (this.info.capcode) { + this.info.nameBlock += " ## " + this.info.capcode; + } + if (this.info.uniqueID) { + this.info.nameBlock += " (ID: " + this.info.uniqueID + ")"; + } this.parseComment(); this.parseQuotes(); - this.parseFile(); + this.parseFiles(); this.isDead = false; this.isHidden = false; this.clones = []; - if (g.posts[this.fullID]) { + if (g.posts.get(this.fullID)) { this.isRebuilt = true; - this.clones = g.posts[this.fullID].clones; - ref8 = this.clones; - for (j = 0, len = ref8.length; j < len; j++) { - clone = ref8[j]; + this.clones = g.posts.get(this.fullID).clones; + ref12 = this.clones; + for (k = 0, len1 = ref12.length; k < len1; k++) { + clone = ref12[k]; clone.origin = this; } } + if (!this.isFetchedQuote && this.ID > this.thread.lastPost) { + this.thread.lastPost = this.ID; + } this.board.posts.push(this.ID, this); this.thread.posts.push(this.ID, this); g.posts.push(this.fullID, this); } Post.prototype.parseNodes = function(root) { - var info, nodes, post; - post = $('.post', root); - info = $('.postInfo', post); + var base, info, key, nodes, post, ref, s, selector; + s = g.SITE.selectors; + post = $(s.post, root) || root; + info = $(s.infoRoot, post); nodes = { root: root, + bottom: this.isReply || !g.SITE.isOPContainerThread ? root : $(s.opBottom, root), post: post, info: info, - subject: $('.subject', info), - name: $('.name', info), - email: $('.useremail', info), - tripcode: $('.postertrip', info), - uniqueID: $('.posteruid', info), - capcode: $('.capcode.hand', info), - flag: $('.flag, .countryFlag', info), - date: $('.dateTime', info), - nameBlock: $('.nameBlock', info), - quote: $('.postNum > a:nth-of-type(2)', info), - reply: $('.replylink', info), - comment: $('.postMessage', post), - links: [], + comment: $(s.comment, post), quotelinks: [], - archivelinks: [] + archivelinks: [], + embedlinks: [] }; + ref = s.info; + for (key in ref) { + selector = ref[key]; + nodes[key] = $(selector, info); + } + if (typeof (base = g.SITE).parseNodes === "function") { + base.parseNodes(this, nodes); + } + nodes.uniqueIDRoot || (nodes.uniqueIDRoot = nodes.uniqueID); if ($.engine === 'edge') { Object.defineProperty(nodes, 'backlinks', { configurable: true, enumerable: true, get: function() { - return info.getElementsByClassName('backlink'); + return post.getElementsByClassName('backlink'); } }); } else { - nodes.backlinks = info.getElementsByClassName('backlink'); + nodes.backlinks = post.getElementsByClassName('backlink'); } return nodes; }; Post.prototype.parseComment = function() { - var abbr, bq, commentDisplay, j, k, len, len1, node, ref, spoilers; + var base, bq; this.nodes.comment.normalize(); - bq = this.nodes.comment.cloneNode(true); - ref = $$('.abbr + br, .exif, b, .fortune', bq); - for (j = 0, len = ref.length; j < len; j++) { - node = ref[j]; - $.rm(node); + this.nodes.commentClean = bq = this.nodes.comment.cloneNode(true); + if (typeof (base = g.SITE).cleanComment === "function") { + base.cleanComment(bq); } - if (abbr = $('.abbr', bq)) { - $.rm(abbr); + return this.info.comment = this.nodesToText(bq); + }; + + Post.prototype.commentDisplay = function() { + var base, bq; + bq = this.nodes.commentClean.cloneNode(true); + if (!(Conf['Remove Spoilers'] || Conf['Reveal Spoilers'])) { + this.cleanSpoilers(bq); } - this.info.comment = this.nodesToText(bq); - if (abbr) { - this.info.comment = this.info.comment.replace(/\n\n$/, ''); + if (typeof (base = g.SITE).cleanCommentDisplay === "function") { + base.cleanCommentDisplay(bq); } - commentDisplay = this.info.comment; - if (!(Conf['Remove Spoilers'] || Conf['Reveal Spoilers'])) { - spoilers = $$('s', bq); - if (spoilers.length) { - for (k = 0, len1 = spoilers.length; k < len1; k++) { - node = spoilers[k]; - $.replace(node, $.tn('[spoiler]')); - } - commentDisplay = this.nodesToText(bq); - } + return this.nodesToText(bq).trim().replace(/\s+$/gm, ''); + }; + + Post.prototype.commentOrig = function() { + var base, bq; + bq = this.nodes.commentClean.cloneNode(true); + if (typeof (base = g.SITE).insertTags === "function") { + base.insertTags(bq); } - return this.info.commentDisplay = commentDisplay.trim().replace(/\s+$/gm, ''); + return this.nodesToText(bq); }; Post.prototype.nodesToText = function(bq) { @@ -5636,10 +7062,19 @@ Post = (function() { return text; }; + Post.prototype.cleanSpoilers = function(bq) { + var j, len, node, spoilers; + spoilers = $$(g.SITE.selectors.spoiler, bq); + for (j = 0, len = spoilers.length; j < len; j++) { + node = spoilers[j]; + $.replace(node, $.tn('[spoiler]')); + } + }; + Post.prototype.parseQuotes = function() { var j, len, quotelink, ref; this.quotes = []; - ref = $$(':not(pre) > .quotelink', this.nodes.comment); + ref = $$(g.SITE.selectors.quotelink, this.nodes.comment); for (j = 0, len = ref.length; j < len; j++) { quotelink = ref[j]; this.parseQuote(quotelink); @@ -5648,7 +7083,7 @@ Post = (function() { Post.prototype.parseQuote = function(quotelink) { var fullID, match; - match = quotelink.href.match(/^https?:\/\/boards\.4chan\.org\/+([^\/]+)\/+(?:res|thread)\/+\d+(?:[\/?][^#]*)?#p(\d+)$/); + match = quotelink.href.match(g.SITE.regexp.quotelink); if (!(match || (this.isClone && quotelink.dataset.postID))) { return; } @@ -5656,58 +7091,85 @@ Post = (function() { if (this.isClone) { return; } - fullID = match[1] + "." + match[2]; + fullID = match[1] + "." + match[3]; if (indexOf.call(this.quotes, fullID) < 0) { return this.quotes.push(fullID); } }; - Post.prototype.parseFile = function() { - var fileEl, fileText, info, link, m, ref, ref1, ref2, size, thumb, unit; - if (!(fileEl = $('.file', this.nodes.post))) { - return; + Post.prototype.parseFiles = function() { + var docIndex, file, fileRoot, fileRoots, index, j, len; + this.files = []; + fileRoots = this.fileRoots(); + index = 0; + for (docIndex = j = 0, len = fileRoots.length; j < len; docIndex = ++j) { + fileRoot = fileRoots[docIndex]; + if ((file = this.parseFile(fileRoot))) { + file.index = index++; + file.docIndex = docIndex; + this.files.push(file); + } + } + if (this.files.length) { + return this.file = this.files[0]; + } + }; + + Post.prototype.fileRoots = function() { + var roots; + if (g.SITE.selectors.multifile) { + roots = $$(g.SITE.selectors.multifile, this.nodes.root); + if (roots.length) { + return roots; + } + } + return [this.nodes.root]; + }; + + Post.prototype.parseFile = function(fileRoot) { + var file, key, ref, ref1, selector, size, unit; + file = {}; + ref = g.SITE.selectors.file; + for (key in ref) { + selector = ref[key]; + file[key] = $(selector, fileRoot); } - if (!(link = $('.fileText > a, .fileText-original > a', fileEl))) { + file.thumbLink = (ref1 = file.thumb) != null ? ref1.parentNode : void 0; + if (!(file.text && file.link)) { return; } - if (!(info = (ref = link.nextSibling) != null ? ref.textContent.match(/\(([\d.]+ [KMG]?B).*\)/) : void 0)) { + if (!g.SITE.parseFile(this, file)) { return; } - fileText = fileEl.firstElementChild; - this.file = { - text: fileText, - link: link, - url: link.href, - name: fileText.title || link.title || link.textContent, - size: info[1], - isImage: /(jpg|png|gif)$/i.test(link.href), - isVideo: /webm$/i.test(link.href), - dimensions: (ref1 = info[0].match(/\d+x\d+/)) != null ? ref1[0] : void 0, - tag: (ref2 = info[0].match(/,[^,]*, ([a-z]+)\)/i)) != null ? ref2[1] : void 0 - }; - size = +this.file.size.match(/[\d.]+/)[0]; - unit = ['B', 'KB', 'MB', 'GB'].indexOf(this.file.size.match(/\w+$/)[0]); + $.extend(file, { + url: file.link.href, + isImage: $.isImage(file.link.href), + isVideo: $.isVideo(file.link.href) + }); + size = +file.size.match(/[\d.]+/)[0]; + unit = ['B', 'KB', 'MB', 'GB'].indexOf(file.size.match(/\w+$/)[0]); while (unit-- > 0) { size *= 1024; } - this.file.sizeInBytes = size; - if ((thumb = $('.fileThumb > [data-md5]', fileEl))) { - return $.extend(this.file, { - thumb: thumb, - thumbURL: (m = link.href.match(/\d+(?=\.\w+$)/)) ? location.protocol + "//i.4cdn.org/" + this.board + "/" + m[0] + "s.jpg" : void 0, - MD5: thumb.dataset.md5, - isSpoiler: $.hasClass(thumb.parentNode, 'imgspoiler') - }); - } + file.sizeInBytes = size; + return file; }; - Post.prototype.kill = function(file) { + Post.deadMark = $.el('span', { + textContent: '\u00A0(Dead)', + className: 'qmark-dead' + }); + + Post.prototype.kill = function(file, index) { var clone, j, k, len, len1, quotelink, ref, ref1, strong; + if (index == null) { + index = 0; + } if (file) { - if (this.isDead || this.file.isDead) { + if (this.isDead || this.files[index].isDead) { return; } - this.file.isDead = true; + this.files[index].isDead = true; $.addClass(this.nodes.root, 'deleted-file'); } else { if (this.isDead) { @@ -5730,7 +7192,7 @@ Post = (function() { ref = this.clones; for (j = 0, len = ref.length; j < len; j++) { clone = ref[j]; - clone.kill(file); + clone.kill(file, index); } if (file) { return; @@ -5741,7 +7203,7 @@ Post = (function() { if (!(!$.hasClass(quotelink, 'deadlink'))) { continue; } - quotelink.textContent = quotelink.textContent + '\u00A0(Dead)'; + $.add(quotelink, Post.deadMark.cloneNode(true)); $.addClass(quotelink, 'deadlink'); } }; @@ -5751,7 +7213,10 @@ Post = (function() { this.isDead = false; $.rmClass(this.nodes.root, 'deleted-post'); strong = $('strong.warning', this.nodes.info); - if (this.file && this.file.isDead) { + if (this.files.some(function(file) { + return file.isDead; + })) { + $.addClass(this.nodes.root, 'deleted-file'); strong.textContent = '[File deleted]'; } else { $.rm(strong); @@ -5770,7 +7235,7 @@ Post = (function() { if (!($.hasClass(quotelink, 'deadlink'))) { continue; } - quotelink.textContent = quotelink.textContent.replace('\u00A0(Dead)', ''); + $.rm($('.qmark-dead', quotelink)); $.rmClass(quotelink, 'deadlink'); } }; @@ -5782,6 +7247,7 @@ Post = (function() { }; Post.prototype.addClone = function(context, contractThumb) { + Callbacks.Post.execute(this); return new Post.Clone(this, context, contractThumb); }; @@ -5795,6 +7261,14 @@ Post = (function() { } }; + Post.prototype.setCatalogOP = function(isCatalogOP) { + this.nodes.root.classList.toggle('catalog-container', isCatalogOP); + this.nodes.root.classList.toggle('opContainer', !isCatalogOP); + this.nodes.post.classList.toggle('catalog-post', isCatalogOP); + this.nodes.post.classList.toggle('op', !isCatalogOP); + return this.nodes.post.style.left = this.nodes.post.style.right = null; + }; + return Post; })(); @@ -5813,60 +7287,83 @@ Post = (function() { _Class.prototype.isClone = true; - function _Class(origin, context, contractThumb) { - var base, file, i, inline, inlined, j, k, key, l, len, len1, len2, len3, node, nodes, ref, ref1, ref2, ref3, ref4, ref5, root, val; + function _Class() { + var that; + that = Object.create(Post.Clone.prototype); + that.construct.apply(that, arguments); + return that; + } + + _Class.prototype.construct = function(origin, context, contractThumb) { + var base, file, fileRoot, fileRoots, i, inline, inlined, j, k, key, l, len, len1, len2, len3, len4, m, node, nodes, originFile, ref, ref1, ref2, ref3, ref4, ref5, ref6, root, selector, val; this.origin = origin; this.context = context; - ref = ['ID', 'fullID', 'board', 'thread', 'info', 'quotes', 'isReply']; + ref = ['ID', 'postID', 'threadID', 'boardID', 'siteID', 'fullID', 'board', 'thread', 'info', 'quotes', 'isReply']; for (i = 0, len = ref.length; i < len; i++) { key = ref[i]; this[key] = this.origin[key]; } nodes = this.origin.nodes; root = contractThumb ? this.cloneWithoutVideo(nodes.root) : nodes.root.cloneNode(true); - (base = Post.Clone).prefix || (base.prefix = 0); + (base = Post.Clone).suffix || (base.suffix = 0); ref1 = [root].concat(slice.call($$('[id]', root))); for (j = 0, len1 = ref1.length; j < len1; j++) { node = ref1[j]; - node.id = Post.Clone.prefix + node.id; + node.id += "_" + Post.Clone.suffix; } - Post.Clone.prefix++; - this.nodes = this.parseNodes(root); - ref2 = $$('.inline', this.nodes.post); + Post.Clone.suffix++; + ref2 = $$('.inline', root); for (k = 0, len2 = ref2.length; k < len2; k++) { inline = ref2[k]; $.rm(inline); } - ref3 = $$('.inlined', this.nodes.post); + ref3 = $$('.inlined', root); for (l = 0, len3 = ref3.length; l < len3; l++) { inlined = ref3[l]; $.rmClass(inlined, 'inlined'); } + this.nodes = this.parseNodes(root); root.hidden = false; $.rmClass(root, 'forwarded'); $.rmClass(this.nodes.post, 'highlight'); + if (!this.isReply) { + this.setCatalogOP(false); + $.rm($('.catalog-link', this.nodes.post)); + $.rm($('.catalog-stats', this.nodes.post)); + $.rm($('.catalog-replies', this.nodes.post)); + } this.parseQuotes(); this.quotes = slice.call(this.origin.quotes); - if (this.origin.file) { - this.file = {}; - ref4 = this.origin.file; - for (key in ref4) { - val = ref4[key]; - this.file[key] = val; - } - file = $('.file', this.nodes.post); - this.file.text = file.firstElementChild; - this.file.link = $('.fileText > a, .fileText-original', file); - this.file.thumb = $('.fileThumb > [data-md5]', file); - this.file.fullImage = $('.full-image', file); - this.file.videoControls = $('.video-controls', this.file.text); - if (this.file.videoThumb) { - this.file.thumb.muted = true; - } - if ((ref5 = this.file.thumb) != null ? ref5.dataset.src : void 0) { - this.file.thumb.src = this.file.thumb.dataset.src; - this.file.thumb.removeAttribute('data-src'); - } + this.files = []; + if (this.origin.files.length) { + fileRoots = this.fileRoots(); + } + ref4 = this.origin.files; + for (m = 0, len4 = ref4.length; m < len4; m++) { + originFile = ref4[m]; + file = {}; + for (key in originFile) { + val = originFile[key]; + file[key] = val; + } + fileRoot = fileRoots[file.docIndex]; + ref5 = g.SITE.selectors.file; + for (key in ref5) { + selector = ref5[key]; + file[key] = $(selector, fileRoot); + } + file.thumbLink = (ref6 = file.thumb) != null ? ref6.parentNode : void 0; + if (file.thumbLink) { + file.fullImage = $('.full-image', file.thumbLink); + } + file.videoControls = $('.video-controls', file.text); + if (file.videoThumb) { + file.thumb.muted = true; + } + this.files.push(file); + } + if (this.files.length) { + this.file = this.files[0]; if (this.file.thumb && contractThumb) { ImageExpand.contract(this); } @@ -5874,8 +7371,8 @@ Post = (function() { if (this.origin.isDead) { this.isDead = true; } - root.dataset.clone = this.origin.clones.push(this) - 1; - } + return root.dataset.clone = this.origin.clones.push(this) - 1; + }; _Class.prototype.cloneWithoutVideo = function(node) { var child, clone, i, len, ref; @@ -6034,6 +7531,47 @@ RandomAccessList = (function() { }).call(this); +ShimSet = (function() { + var ShimSet; + + ShimSet = (function() { + function ShimSet() { + this.elements = $.dict(); + this.size = 0; + } + + ShimSet.prototype.has = function(value) { + return value in this.elements; + }; + + ShimSet.prototype.add = function(value) { + if (this.elements[value]) { + return; + } + this.elements[value] = true; + return this.size++; + }; + + ShimSet.prototype["delete"] = function(value) { + if (!this.elements[value]) { + return; + } + delete this.elements[value]; + return this.size--; + }; + + return ShimSet; + + })(); + + if (!('Set' in window)) { + window.Set = ShimSet; + } + + return ShimSet; + +}).call(this); + SimpleDict = (function() { var SimpleDict, slice = [].slice; @@ -6069,6 +7607,14 @@ SimpleDict = (function() { } }; + SimpleDict.prototype.get = function(key) { + if (key === 'keys') { + return void 0; + } else { + return $.getOwn(this, key); + } + }; + return SimpleDict; })(); @@ -6086,21 +7632,28 @@ Thread = (function() { }; function Thread(ID, board) { - this.ID = ID; this.board = board; + this.ID = +ID; + this.threadID = this.ID; + this.boardID = this.board.ID; + this.siteID = g.SITE.ID; this.fullID = this.board + "." + this.ID; this.posts = new SimpleDict(); this.isDead = false; this.isHidden = false; - this.isOnTop = false; this.isSticky = false; this.isClosed = false; this.isArchived = false; this.postLimit = false; this.fileLimit = false; + this.lastPost = 0; this.ipCount = void 0; + this.json = null; this.OP = null; this.catalogView = null; + this.nodes = { + root: null + }; this.board.threads.push(this.ID, this); g.threads.push(this.fullID, this); } @@ -6162,11 +7715,14 @@ Thread = (function() { return; } icon = $.el('img', { - src: "" + Build.staticPath + typeLC + Build.gifIcon, + src: "" + g.SITE.Build.staticPath + typeLC + g.SITE.Build.gifIcon, alt: type, title: type, className: typeLC + "Icon retina" }); + if (g.BOARD.ID === 'f') { + icon.style.cssText = 'height: 18px; width: 18px;'; + } root = type !== 'Sticky' && this.isSticky ? $('.stickyIcon', this.OP.nodes.info) : $('.page-num', this.OP.nodes.info) || this.OP.nodes.quote; $.after(root, [$.tn(' '), icon]); if (!this.catalogView) { @@ -6180,11 +7736,19 @@ Thread = (function() { }; Thread.prototype.collect = function() { + var n; + n = 0; this.posts.forEach(function(post) { - return post.collect(); + if (post.clones.length) { + return n++; + } else { + return post.collect(); + } }); - g.threads.rm(this.fullID); - return this.board.threads.rm(this); + if (!n) { + g.threads.rm(this.fullID); + return this.board.threads.rm(this); + } }; return Thread; @@ -6195,158 +7759,1317 @@ Thread = (function() { }).call(this); -Redirect = (function() { - var Redirect, - indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; +SW = {}; - Redirect = { - archives: [ - { "uid": 3, "name": "4plebs", "domain": "archive.4plebs.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "adv", "f", "hr", "o", "pol", "s4s", "sp", "tg", "trv", "tv", "x" ], "files": [ "adv", "f", "hr", "o", "pol", "s4s", "sp", "tg", "trv", "tv", "x" ] }, - { "uid": 4, "name": "Nyafuu Archive", "domain": "archive.nyafuu.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "c", "e", "news", "w", "wg", "wsr" ], "files": [ "c", "e", "news", "w", "wg", "wsr" ] }, - { "uid": 8, "name": "Rebecca Black Tech", "domain": "archive.rebeccablacktech.com", "http": false, "https": true, "software": "fuuka", "boards": [ "cgl", "g", "mu" ], "files": [ "cgl", "g", "mu" ] }, - { "uid": 10, "name": "warosu", "domain": "warosu.org", "http": false, "https": true, "software": "fuuka", "boards": [ "3", "biz", "cgl", "ck", "diy", "fa", "g", "ic", "jp", "lit", "sci", "tg", "vr" ], "files": [ "3", "biz", "cgl", "ck", "diy", "fa", "g", "ic", "jp", "lit", "sci", "tg", "vr" ] }, - { "uid": 23, "name": "Desustorage", "domain": "desustorage.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "a", "aco", "an", "c", "co", "d", "fit", "gif", "his", "int", "k", "m", "mlp", "qa", "r9k", "tg", "trash", "vr", "wsg" ], "files": [ "a", "aco", "an", "c", "co", "d", "fit", "gif", "his", "int", "k", "m", "mlp", "qa", "r9k", "tg", "trash", "vr", "wsg" ] }, - { "uid": 24, "name": "fireden.net", "domain": "boards.fireden.net", "http": false, "https": true, "software": "foolfuuka", "boards": [ "a", "cm", "ic", "sci", "tg", "v", "vg", "y" ], "files": [ "a", "cm", "ic", "sci", "tg", "v", "vg", "y" ] }, - { "uid": 25, "name": "arch.b4k.co", "domain": "arch.b4k.co", "http": true, "https": true, "software": "foolfuuka", "boards": [ "g", "jp", "mlp", "v" ], "files": [] }, - { "uid": 5, "name": "Love is Over", "domain": "archive.loveisover.me", "http": true, "https": false, "software": "foolfuuka", "boards": [ "c", "d", "e", "i", "lgbt", "t", "u" ], "files": [ "c", "d", "e", "i", "lgbt", "t", "u" ] }, - { "uid": 28, "name": "bstats", "domain": "archive.b-stats.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "f", "cm", "hm", "lgbt", "news", "qst", "trash", "y" ], "files": [] }, - { "uid": 29, "name": "Archived.Moe", "domain": "archived.moe", "http": true, "https": false, "software": "foolfuuka", "boards": [ "3", "a", "aco", "adv", "an", "asp", "b", "biz", "c", "cgl", "ck", "cm", "co", "d", "diy", "e", "f", "fa", "fit", "g", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "int", "jp", "k", "lgbt", "lit", "m", "mlp", "mu", "n", "news", "o", "out", "p", "po", "pol", "qa", "qst", "r", "r9k", "s", "s4s", "sci", "soc", "sp", "t", "tg", "toy", "trash", "trv", "tv", "u", "v", "vg", "vp", "vr", "w", "wg", "wsg", "wsr", "x", "y" ], "files": [ "gd", "po", "qst" ] }, - { "uid": 30, "name": "TheBArchive.com", "domain": "thebarchive.com", "http": true, "https": false, "software": "foolfuuka", "boards": [ "b" ], "files": [ "b" ] } - ], - init: function() { - this.selectArchives(); - if (Conf['archiveAutoUpdate'] && Conf['lastarchivecheck'] < Date.now() - 2 * $.DAY) { - return this.update(); +(function() { + var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + SW.tinyboard = { + isOPContainerThread: true, + mayLackJSON: true, + threadModTimeIgnoresSage: true, + disabledFeatures: ['Resurrect Quotes', 'Quick Reply Personas', 'Quick Reply', 'Cooldown', 'Report Link', 'Delete Link', 'Edit Link', 'Quote Inlining', 'Quote Previewing', 'Quote Backlinks', 'File Info Formatting', 'Image Expansion', 'Image Expansion (Menu)', 'Comment Expansion', 'Thread Expansion', 'Favicon', 'Quote Threading', 'Thread Updater', 'Banner', 'Flash Features', 'Reply Pruning'], + detect: function() { + var j, len, m, properties, ref, root, script; + ref = $$('script:not([src])', d.head); + for (j = 0, len = ref.length; j < len; j++) { + script = ref[j]; + if ((m = script.textContent.match(/\bvar configRoot=(".*?")/))) { + properties = $.dict(); + try { + root = JSON.parse(m[1]); + if (root[0] === '/') { + properties.root = location.origin + root; + } else if (/^https?:/.test(root)) { + properties.root = root; + } + } catch (error) {} + return properties; + } } + return false; }, - selectArchives: function() { - var archive, archives, boardID, boards, data, files, id, j, k, key, l, len, len1, len2, name, o, record, ref, ref1, ref2, software, type, uid; - o = { - thread: {}, - post: {}, - file: {} - }; - archives = {}; - ref = Conf['archives']; - for (j = 0, len = ref.length; j < len; j++) { - data = ref[j]; - ref1 = ['boards', 'files']; - for (k = 0, len1 = ref1.length; k < len1; k++) { - key = ref1[k]; - if (!(data[key] instanceof Array)) { - data[key] = []; - } + awaitBoard: function(cb) { + var reactUI, s; + if ((reactUI = $.id('react-ui'))) { + s = this.selectors = Object.create(this.selectors); + s.boardFor = { + index: '.page-container' + }; + s.thread = 'div[id^="thread_"]'; + return Main.mounted(cb); + } else { + return cb(); + } + }, + urls: { + thread: function(arg, isArchived) { + var boardID, ref, siteID, threadID; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID; + return "" + (((ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0) || ("http://" + siteID + "/")) + boardID + "/" + (isArchived ? 'archive/' : '') + "res/" + threadID + ".html"; + }, + post: function(arg) { + var postID; + postID = arg.postID; + return "#" + postID; + }, + index: function(arg) { + var boardID, ref, siteID; + siteID = arg.siteID, boardID = arg.boardID; + return "" + (((ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0) || ("http://" + siteID + "/")) + boardID + "/"; + }, + catalog: function(arg) { + var boardID, ref, siteID; + siteID = arg.siteID, boardID = arg.boardID; + return "" + (((ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0) || ("http://" + siteID + "/")) + boardID + "/catalog.html"; + }, + threadJSON: function(arg, isArchived) { + var boardID, ref, root, siteID, threadID; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID; + root = (ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0; + if (root) { + return "" + root + boardID + "/" + (isArchived ? 'archive/' : '') + "res/" + threadID + ".json"; + } else { + return ''; } - uid = data.uid, name = data.name, boards = data.boards, files = data.files, software = data.software; - if (software !== 'fuuka' && software !== 'foolfuuka') { - continue; + }, + archivedThreadJSON: function(thread) { + return SW.tinyboard.urls.threadJSON(thread, true); + }, + threadsListJSON: function(arg) { + var boardID, ref, root, siteID; + siteID = arg.siteID, boardID = arg.boardID; + root = (ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0; + if (root) { + return "" + root + boardID + "/threads.json"; + } else { + return ''; } - archives[JSON.stringify(uid != null ? uid : name)] = data; - for (l = 0, len2 = boards.length; l < len2; l++) { - boardID = boards[l]; - if (!(boardID in o.thread)) { - o.thread[boardID] = data; - } - if (!(boardID in o.post || software !== 'foolfuuka')) { - o.post[boardID] = data; - } - if (!(boardID in o.file || indexOf.call(files, boardID) < 0)) { - o.file[boardID] = data; - } + }, + archiveListJSON: function(arg) { + var boardID, ref, root, siteID; + siteID = arg.siteID, boardID = arg.boardID; + root = (ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0; + if (root) { + return "" + root + boardID + "/archive/archive.json"; + } else { + return ''; } - } - ref2 = Conf['selectedArchives']; - for (boardID in ref2) { - record = ref2[boardID]; - for (type in record) { - id = record[type]; - if (!((archive = archives[JSON.stringify(id)]))) { - continue; + }, + catalogJSON: function(arg) { + var boardID, ref, root, siteID; + siteID = arg.siteID, boardID = arg.boardID; + root = (ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0; + if (root) { + return "" + root + boardID + "/catalog.json"; + } else { + return ''; + } + }, + file: function(arg, filename) { + var boardID, ref, siteID; + siteID = arg.siteID, boardID = arg.boardID; + return "" + (((ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0) || ("http://" + siteID + "/")) + boardID + "/" + filename; + }, + thumb: function(board, filename) { + return SW.tinyboard.urls.file(board, filename); + } + }, + selectors: { + board: 'form[name="postcontrols"]', + thread: 'input[name="board"] ~ div[id^="thread_"]', + threadDivider: 'div[id^="thread_"] > hr:last-child', + summary: '.omitted', + postContainer: 'div[id^="reply_"]:not(.hidden)', + opBottom: '.op', + replyOriginal: 'div[id^="reply_"]:not(.hidden)', + infoRoot: '.intro', + info: { + subject: '.subject', + name: '.name', + email: '.email', + tripcode: '.trip', + uniqueID: '.poster_id', + capcode: '.capcode', + flag: '.flag', + date: 'time', + nameBlock: 'label', + quote: 'a[href*="#q"]', + reply: 'a[href*="/res/"]:not([href*="#"])' + }, + icons: { + isSticky: '.fa-thumb-tack', + isClosed: '.fa-lock' + }, + file: { + text: '.fileinfo', + link: '.fileinfo > a', + thumb: 'a > .post-image' + }, + thumbLink: '.file > a', + multifile: '.files > .file', + highlightable: { + op: ' > .op', + reply: '.reply', + catalog: ' > .thread' + }, + comment: '.body', + spoiler: '.spoiler', + quotelink: 'a[onclick*="highlightReply("]', + catalog: { + board: '#Grid', + thread: '.mix', + thumb: '.thread-image' + }, + boardList: '.boardlist', + boardListBottom: '.boardlist.bottom', + styleSheet: '#stylesheet', + psa: '.blotter', + nav: { + prev: '.pages > form > [value=Previous]', + next: '.pages > form > [value=Next]' + } + }, + classes: { + highlight: 'highlighted' + }, + xpath: { + thread: 'div[starts-with(@id,"thread_")]', + postContainer: 'div[starts-with(@id,"reply_") or starts-with(@id,"thread_")]', + replyContainer: 'div[starts-with(@id,"reply_")]' + }, + regexp: { + quotelink: /\/([^\/]+)\/res\/(\d+)(?:\.\w+)?#(\d+)$/, + quotelinkHTML: /]*\bhref="[^"]*\/([^\/]+)\/res\/(\d+)(?:\.\w+)?#(\d+)"/g + }, + Build: { + parseJSON: function(data, board) { + var extra_file, file, i, j, len, o, ref; + o = SW.yotsuba.Build.parseJSON(data, board); + if (data.ext === 'deleted') { + delete o.file; + $.extend(o, { + files: [], + fileDeleted: true, + filesDeleted: [0] + }); + } + if (data.extra_files) { + ref = data.extra_files; + for (i = j = 0, len = ref.length; j < len; i = ++j) { + extra_file = ref[i]; + if (extra_file.ext === 'deleted') { + o.filesDeleted.push(i); + } else { + file = SW.yotsuba.Build.parseJSONFile(data, board); + o.files.push(file); + } } - boards = type === 'file' ? archive.files : archive.boards; - if (indexOf.call(boards, boardID) >= 0) { - o[type][boardID] = archive; + if (o.files.length) { + o.file = o.files[0]; } } + return o; + }, + parseComment: function(html) { + html = html.replace(//gi, '\n').replace(/<[^>]*>/g, ''); + return $.unescape(html); } - return Redirect.data = o; }, - update: function(cb) { - var i, j, k, len, len1, load, nloaded, ref, ref1, responses, url, urls; - urls = []; - responses = []; - nloaded = 0; - ref = Conf['archiveLists'].split('\n'); - for (j = 0, len = ref.length; j < len; j++) { - url = ref[j]; - if (!(url[0] !== '#')) { - continue; - } - url = url.trim(); - if (url) { - urls.push(url); - } + bgColoredEl: function() { + return $.el('div', { + className: 'post reply' + }); + }, + isFileURL: function(url) { + return /\/src\/[^\/]+/.test(url.pathname); + }, + preParsingFixes: function(board) { + var broken; + if ((broken = $('a > input[name="board"]', board))) { + return $.before(broken.parentNode, broken); } - load = function(i) { - return function() { - var err, fail, response; - fail = function(action, msg) { - return new Notice('warning', "Error " + action + " archive data from\n" + urls[i] + "\n" + msg, 20); - }; - if (this.status !== 200) { - return fail('fetching', (this.status ? "Error " + this.statusText + " (" + this.status + ")" : 'Connection Error')); - } - try { - response = JSON.parse(this.response); - } catch (_error) { - err = _error; - return fail('parsing', err.message); - } - if (!(response instanceof Array)) { - response = [response]; - } - responses[i] = response; - nloaded++; - if (nloaded === urls.length) { - return Redirect.parse(responses, cb); - } - }; - }; - if (urls.length) { - for (i = k = 0, len1 = urls.length; k < len1; i = ++k) { - url = urls[i]; - if ((ref1 = url[0]) === '[' || ref1 === '{') { - load(i).call({ - status: 200, - response: url - }); - } else { - $.ajax(url, { - responseType: 'text', - onloadend: load(i) - }); - } - } - } else { - Redirect.parse([], cb); + }, + parseNodes: function(post, nodes) { + var m, nextSibling, node, text, uniqueID; + if (nodes.uniqueID) { + return; + } + text = ''; + node = nodes.nameBlock.nextSibling; + while (node && node.nodeType === 3) { + text += node.textContent; + node = node.nextSibling; + } + if ((m = text.match(/(\s*ID:\s*)(\S+)/))) { + nodes.info.normalize(); + nextSibling = nodes.nameBlock.nextSibling; + nextSibling = nextSibling.splitText(m[1].length); + nextSibling.splitText(m[2].length); + nodes.uniqueID = uniqueID = $.el('span', { + className: 'poster_id' + }); + $.replace(nextSibling, uniqueID); + return $.add(uniqueID, nextSibling); } }, - parse: function(responses, cb) { - var archiveUIDs, archives, data, items, j, k, len, len1, ref, response, uid; - archives = []; - archiveUIDs = {}; - for (j = 0, len = responses.length; j < len; j++) { - response = responses[j]; - for (k = 0, len1 = response.length; k < len1; k++) { - data = response[k]; - uid = JSON.stringify((ref = data.uid) != null ? ref : data.name); - if (uid in archiveUIDs) { - $.extend(archiveUIDs[uid], data); - } else { - archiveUIDs[uid] = data; - archives.push(data); - } - } + parseDate: function(node) { + var date, ref; + date = Date.parse((ref = node.getAttribute('datetime')) != null ? ref.trim() : void 0); + if (!isNaN(date)) { + return new Date(date); + } + date = Date.parse(node.textContent.trim() + ' UTC'); + if (!isNaN(date)) { + return new Date(date); + } + return void 0; + }, + parseFile: function(post, file) { + var info, infoNode, link, nameNode, ref, ref1, text, thumb; + text = file.text, link = file.link, thumb = file.thumb; + if ($.x("ancestor::" + this.xpath.postContainer + "[1]", text) !== post.nodes.root) { + return false; + } + if (!(infoNode = indexOf.call((ref = link.nextSibling) != null ? ref.textContent : void 0, '(') >= 0 ? link.nextSibling : link.nextElementSibling)) { + return false; + } + if (!(info = infoNode.textContent.match(/\((.*,\s*)?([\d.]+ ?[KMG]?B).*\)/))) { + return false; + } + nameNode = $('.postfilename', text); + $.extend(file, { + name: nameNode ? nameNode.title || nameNode.textContent : link.pathname.match(/[^\/]*$/)[0], + size: info[2], + dimensions: (ref1 = info[0].match(/\d+x\d+/)) != null ? ref1[0] : void 0 + }); + if (thumb) { + $.extend(file, { + thumbURL: /\/static\//.test(thumb.src) && $.isImage(link.href) ? link.href : thumb.src, + isSpoiler: /^Spoiler/i.test(info[1] || '') || link.textContent === 'Spoiler Image' + }); + } + return true; + }, + isThumbExpanded: function(file) { + return $.hasClass(file.thumb.parentNode, 'expanded') || file.thumb.parentNode.dataset.expanded === 'true'; + }, + isLinkified: function(link) { + return /\bnofollow\b/.test(link.rel); + }, + catalogPin: function(threadRoot) { + return threadRoot.dataset.sticky = 'true'; + } + }; + +}).call(this); + +(function() { + var slice = [].slice; + + SW.yotsuba = { + isOPContainerThread: false, + hasIPCount: true, + archivedBoardsKnown: true, + urls: { + thread: function(arg) { + var boardID, threadID; + boardID = arg.boardID, threadID = arg.threadID; + return location.protocol + "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/thread/" + threadID; + }, + post: function(arg) { + var postID; + postID = arg.postID; + return "#p" + postID; + }, + index: function(arg) { + var boardID; + boardID = arg.boardID; + return location.protocol + "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/"; + }, + catalog: function(arg) { + var boardID; + boardID = arg.boardID; + if (boardID === 'f') { + return void 0; + } else { + return location.protocol + "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/catalog"; + } + }, + archive: function(arg) { + var boardID; + boardID = arg.boardID; + if (BoardConfig.isArchived(boardID)) { + return location.protocol + "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/archive"; + } else { + return void 0; + } + }, + threadJSON: function(arg) { + var boardID, threadID; + boardID = arg.boardID, threadID = arg.threadID; + return location.protocol + "//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json"; + }, + threadsListJSON: function(arg) { + var boardID; + boardID = arg.boardID; + return location.protocol + "//a.4cdn.org/" + boardID + "/threads.json"; + }, + archiveListJSON: function(arg) { + var boardID; + boardID = arg.boardID; + if (BoardConfig.isArchived(boardID)) { + return location.protocol + "//a.4cdn.org/" + boardID + "/archive.json"; + } else { + return ''; + } + }, + catalogJSON: function(arg) { + var boardID; + boardID = arg.boardID; + return location.protocol + "//a.4cdn.org/" + boardID + "/catalog.json"; + }, + file: function(arg, filename) { + var boardID, hostname; + boardID = arg.boardID; + hostname = boardID === 'f' ? ImageHost.flashHost() : ImageHost.host(); + return location.protocol + "//" + hostname + "/" + boardID + "/" + filename; + }, + thumb: function(arg, filename) { + var boardID; + boardID = arg.boardID; + return location.protocol + "//" + (ImageHost.thumbHost()) + "/" + boardID + "/" + filename; + } + }, + isPrunedByAge: function(arg) { + var boardID; + boardID = arg.boardID; + return boardID === 'f'; + }, + areMD5sDeferred: function(arg) { + var boardID; + boardID = arg.boardID; + return boardID === 'f'; + }, + isOnePage: function(arg) { + var boardID; + boardID = arg.boardID; + return boardID === 'f'; + }, + noAudio: function(arg) { + var boardID; + boardID = arg.boardID; + return BoardConfig.noAudio(boardID); + }, + selectors: { + board: '.board', + thread: '.thread', + threadDivider: '.board > hr', + summary: '.summary', + postContainer: '.postContainer', + replyOriginal: '.replyContainer:not([data-clone])', + sideArrows: 'div.sideArrows', + post: '.post', + infoRoot: '.postInfo', + info: { + subject: '.subject', + name: '.name', + email: '.useremail', + tripcode: '.postertrip', + uniqueIDRoot: '.posteruid', + uniqueID: '.posteruid > .hand', + capcode: '.capcode.hand', + pass: '.n-pu', + flag: '.flag, .bfl', + date: '.dateTime', + nameBlock: '.nameBlock', + quote: '.postNum > a:nth-of-type(2)', + reply: '.replylink' + }, + icons: { + isSticky: '.stickyIcon', + isClosed: '.closedIcon', + isArchived: '.archivedIcon' + }, + file: { + text: '.file > :first-child', + link: '.fileText > a', + thumb: 'a.fileThumb > [data-md5]' + }, + thumbLink: 'a.fileThumb', + highlightable: { + op: '.opContainer', + reply: ' > .reply', + catalog: '' + }, + comment: '.postMessage', + spoiler: 's', + quotelink: ':not(pre) > .quotelink', + catalog: { + board: '#threads', + thread: '.thread', + thumb: '.thumb' + }, + boardList: '#boardNavDesktop > .boardList', + boardListBottom: '#boardNavDesktopFoot > .boardList', + styleSheet: 'link[title=switch]', + psa: '#globalMessage', + psaTop: '#globalToggle', + searchBox: '#search-box', + nav: { + prev: '.prev > form > [type=submit]', + next: '.next > form > [type=submit]' + } + }, + classes: { + highlight: 'highlight' + }, + xpath: { + thread: 'div[contains(concat(" ",@class," ")," thread ")]', + postContainer: 'div[contains(@class,"postContainer")]', + replyContainer: 'div[contains(@class,"replyContainer")]' + }, + regexp: { + quotelink: /^https?:\/\/boards\.4chan(?:nel)?\.org\/+([^\/]+)\/+thread\/+(\d+)(?:[\/?][^#]*)?(?:#p(\d+))?$/, + quotelinkHTML: /]*\bhref="(?:(?:\/\/boards\.4chan(?:nel)?\.org)?\/([^\/]+)\/thread\/)?(\d+)?(?:#p(\d+))?"/g, + pass: /^https?:\/\/www\.4chan(?:nel)?\.org\/+pass(?:$|[?#])/, + captcha: /^https?:\/\/sys\.4chan(?:nel)?\.org\/+captcha(?:$|[?#])/ + }, + bgColoredEl: function() { + return $.el('div', { + className: 'reply' + }); + }, + isThisPageLegit: function() { + var ref, ref1; + return ((ref = location.hostname) === 'boards.4chan.org' || ref === 'boards.4channel.org') && d.doctype && !$('link[href*="favicon-status.ico"]', d.head) && ((ref1 = d.title) !== '4chan - Temporarily Offline' && ref1 !== '4chan - Error' && ref1 !== '504 Gateway Time-out' && ref1 !== 'MathJax Equation Source'); + }, + is404: function() { + var ref; + return ((ref = d.title) === '4chan - Temporarily Offline' || ref === '4chan - 404 Not Found') || (g.VIEW === 'thread' && $('.board') && !$('.opContainer')); + }, + isIncomplete: function() { + var ref; + return ((ref = g.VIEW) === 'index' || ref === 'thread') && !$('.board + *'); + }, + isBoardlessPage: function(url) { + var ref; + return (ref = url.hostname) === 'www.4chan.org' || ref === 'www.4channel.org'; + }, + isAuxiliaryPage: function(url) { + var ref; + return (ref = url.hostname) !== 'boards.4chan.org' && ref !== 'boards.4channel.org'; + }, + isFileURL: function(url) { + return ImageHost.test(url.hostname); + }, + initAuxiliary: function() { + var match, pathname; + switch (location.hostname) { + case 'www.4chan.org': + case 'www.4channel.org': + if (SW.yotsuba.regexp.pass.test(location.href)) { + PassMessage.init(); + } else { + $.onExists(doc, 'body', function() { + return $.addStyle(CSS.www); + }); + Captcha.replace.init(); + } + break; + case 'sys.4chan.org': + case 'sys.4channel.org': + pathname = location.pathname.split(/\/+/); + if (pathname[2] === 'imgboard.php') { + if (/\bmode=report\b/.test(location.search)) { + Report.init(); + } else if ((match = location.search.match(/\bres=(\d+)/))) { + $.ready(function() { + var ref; + if (Conf['404 Redirect'] && ((ref = $.id('errmsg')) != null ? ref.textContent : void 0) === 'Error: Specified thread does not exist.') { + return Redirect.navigate('thread', { + boardID: g.BOARD.ID, + postID: +match[1] + }); + } + }); + } + } else if (pathname[2] === 'post') { + PostSuccessful.init(); + } + } + }, + scriptData: function() { + var j, len, ref, script; + ref = $$('script:not([src])', d.head); + for (j = 0, len = ref.length; j < len; j++) { + script = ref[j]; + if (/\bcooldowns *=/.test(script.textContent)) { + return script.textContent; + } + } + return ''; + }, + parseThreadMetadata: function(thread) { + var file, m, scriptData; + scriptData = this.scriptData(); + thread.postLimit = /\bbumplimit *= *1\b/.test(scriptData); + thread.fileLimit = /\bimagelimit *= *1\b/.test(scriptData); + thread.ipCount = (m = scriptData.match(/\bunique_ips *= *(\d+)\b/)) ? +m[1] : void 0; + if (g.BOARD.ID === 'f' && thread.OP.file) { + file = thread.OP.file; + return $.ajax(this.urls.threadJSON({ + boardID: 'f', + threadID: thread.ID + }), { + timeout: $.MINUTE, + onloadend: function() { + if (this.response) { + return file.text.dataset.md5 = file.MD5 = this.response.posts[0].md5; + } + } + }); + } + }, + parseNodes: function(post, nodes) { + var icon, j, len, ref, results, type; + if (post.boardID === 'f') { + ref = ['Sticky', 'Closed']; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + type = ref[j]; + if ((icon = $("img[alt=" + type + "]", nodes.info))) { + results.push($.addClass(icon, (type.toLowerCase()) + "Icon", 'retina')); + } + } + return results; + } + }, + parseDate: function(node) { + return new Date(node.dataset.utc * 1000); + }, + parseFile: function(post, file) { + var info, link, m, ref, ref1, ref2, text, thumb; + text = file.text, link = file.link, thumb = file.thumb; + if (!(info = (ref = link.nextSibling) != null ? ref.textContent.match(/\(([\d.]+ [KMG]?B).*\)/) : void 0)) { + return false; + } + $.extend(file, { + name: text.title || link.title || link.textContent, + size: info[1], + dimensions: (ref1 = info[0].match(/\d+x\d+/)) != null ? ref1[0] : void 0, + tag: (ref2 = info[0].match(/,[^,]*, ([a-z]+)\)/i)) != null ? ref2[1] : void 0, + MD5: text.dataset.md5 + }); + if (thumb) { + $.extend(file, { + thumbURL: thumb.src, + MD5: thumb.dataset.md5, + isSpoiler: $.hasClass(thumb.parentNode, 'imgspoiler') + }); + if (file.isSpoiler) { + file.thumbURL = (m = link.href.match(/\d+(?=\.\w+$)/)) ? location.protocol + "//" + (ImageHost.thumbHost()) + "/" + post.board + "/" + m[0] + "s.jpg" : void 0; + } + } + return true; + }, + cleanComment: function(bq) { + var abbr, br, i, j, k, len, node, ref; + if ((abbr = $('.abbr', bq))) { + ref = $$('.abbr + br, .exif', bq); + for (j = 0, len = ref.length; j < len; j++) { + node = ref[j]; + $.rm(node); + } + for (i = k = 0; k < 2; i = ++k) { + if ((br = abbr.previousSibling) && br.nodeName === 'BR') { + $.rm(br); + } + } + return $.rm(abbr); + } + }, + cleanCommentDisplay: function(bq) { + var b; + if ((b = $('b', bq)) && /^Rolled /.test(b.textContent)) { + $.rm(b); + } + return $.rm($('.fortune', bq)); + }, + insertTags: function(bq) { + var j, k, len, len1, node, ref, ref1; + ref = $$('s, .removed-spoiler', bq); + for (j = 0, len = ref.length; j < len; j++) { + node = ref[j]; + $.replace(node, [$.tn('[spoiler]')].concat(slice.call(node.childNodes), [$.tn('[/spoiler]')])); + } + ref1 = $$('.prettyprint', bq); + for (k = 0, len1 = ref1.length; k < len1; k++) { + node = ref1[k]; + $.replace(node, [$.tn('[code]')].concat(slice.call(node.childNodes), [$.tn('[/code]')])); + } + }, + hasCORS: function(url) { + return url.split('/').slice(0, 3).join('/') === location.protocol + '//a.4cdn.org'; + }, + sfwBoards: function(sfw) { + return BoardConfig.sfwBoards(sfw); + }, + uidColor: function(uid) { + var i, msg; + msg = 0; + i = 0; + while (i < 8) { + msg = (msg << 5) - msg + uid.charCodeAt(i++); + } + return (msg >> 8) & 0xFFFFFF; + }, + isLinkified: function(link) { + return ImageHost.test(link.hostname); + }, + testNativeExtension: function() { + return $.global(function() { + if (window.Parser.postMenuIcon) { + return this.enabled = 'true'; + } + }); + }, + transformBoardList: function() { + var a, chr, i, items, j, len, node, nodes, ref, spacer, span; + nodes = []; + spacer = function() { + return $.el('span', { + className: 'spacer' + }); + }; + items = $.X('.//a|.//text()[not(ancestor::a)]', $(SW.yotsuba.selectors.boardList)); + i = 0; + while (node = items.snapshotItem(i++)) { + switch (node.nodeName) { + case '#text': + ref = node.nodeValue; + for (j = 0, len = ref.length; j < len; j++) { + chr = ref[j]; + span = $.el('span', { + textContent: chr + }); + if (chr === ' ') { + span.className = 'space'; + } + if (chr === ']') { + nodes.push(spacer()); + } + nodes.push(span); + if (chr === '[') { + nodes.push(spacer()); + } + } + break; + case 'A': + a = node.cloneNode(true); + nodes.push(a); + } + } + return nodes; + } + }; + +}).call(this); + +(function() { + var Build, + slice = [].slice; + + Build = { + staticPath: '//s.4cdn.org/image/', + gifIcon: window.devicePixelRatio >= 2 ? '@2x.gif' : '.gif', + spoilerRange: $.dict(), + shortFilename: function(filename) { + var ext; + ext = filename.match(/\.?[^\.]*$/)[0]; + if (filename.length - ext.length > 30) { + return (filename.match(/(?:[\uD800-\uDBFF][\uDC00-\uDFFF]|[^]){0,25}/)[0]) + "(...)" + ext; + } else { + return filename; + } + }, + spoilerThumb: function(boardID) { + var spoilerRange; + if (spoilerRange = Build.spoilerRange[boardID]) { + return Build.staticPath + "spoiler-" + boardID + (Math.floor(1 + spoilerRange * Math.random())) + ".png"; + } else { + return Build.staticPath + "spoiler.png"; + } + }, + sameThread: function(boardID, threadID) { + return g.VIEW === 'thread' && g.BOARD.ID === boardID && g.THREADID === +threadID; + }, + threadURL: function(boardID, threadID) { + if (boardID !== g.BOARD.ID) { + return "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/thread/" + threadID; + } else if (g.VIEW !== 'thread' || +threadID !== g.THREADID) { + return "/" + boardID + "/thread/" + threadID; + } else { + return ''; + } + }, + postURL: function(boardID, threadID, postID) { + return (Build.threadURL(boardID, threadID)) + "#p" + postID; + }, + parseJSON: function(data, arg) { + var boardID, key, o, siteID; + siteID = arg.siteID, boardID = arg.boardID; + o = { + ID: data.no, + postID: data.no, + threadID: data.resto || data.no, + boardID: boardID, + siteID: siteID, + isReply: !!data.resto, + isSticky: !!data.sticky, + isClosed: !!data.closed, + isArchived: !!data.archived, + fileDeleted: !!data.filedeleted, + filesDeleted: data.filedeleted ? [0] : [] + }; + o.info = { + subject: $.unescape(data.sub), + email: $.unescape(data.email), + name: $.unescape(data.name) || '', + tripcode: data.trip, + pass: data.since4pass != null ? "" + data.since4pass : void 0, + uniqueID: data.id, + flagCode: data.country, + flagCodeTroll: data.board_flag, + flag: $.unescape(data.country_name || data.flag_name), + dateUTC: data.time, + dateText: data.now, + commentHTML: { + innerHTML: data.com || '' + } + }; + if (data.capcode) { + o.info.capcode = data.capcode.replace(/_highlight$/, '').replace(/_/g, ' ').replace(/\b\w/g, function(c) { + return c.toUpperCase(); + }); + o.capcodeHighlight = /_highlight$/.test(data.capcode); + delete o.info.uniqueID; + } + o.files = []; + if (data.ext) { + o.file = SW.yotsuba.Build.parseJSONFile(data, { + siteID: siteID, + boardID: boardID + }); + o.files.push(o.file); + } + o.extra = $.dict(); + for (key in data) { + if (key[0] === 'x') { + o.extra[key] = data[key]; + } + } + return o; + }, + parseJSONFile: function(data, arg) { + var boardID, filename, o, site, siteID; + siteID = arg.siteID, boardID = arg.boardID; + site = g.sites[siteID]; + filename = site.software === 'yotsuba' && boardID === 'f' ? "" + (encodeURIComponent(data.filename)) + data.ext : "" + data.tim + data.ext; + o = { + name: ($.unescape(data.filename)) + data.ext, + url: site.urls.file({ + siteID: siteID, + boardID: boardID + }, filename), + height: data.h, + width: data.w, + MD5: data.md5, + size: $.bytesToString(data.fsize), + thumbURL: site.urls.thumb({ + siteID: siteID, + boardID: boardID + }, data.tim + "s.jpg"), + theight: data.tn_h, + twidth: data.tn_w, + isSpoiler: !!data.spoiler, + tag: data.tag, + hasDownscale: !!data.m_img + }; + if ((data.h != null) && !/\.pdf$/.test(o.url)) { + o.dimensions = o.width + "x" + o.height; + } + return o; + }, + parseComment: function(html) { + html = html.replace(//gi, '\n').replace(/\n\n]*>/g, ''); + return $.unescape(html); + }, + parseCommentDisplay: function(html) { + var html2; + if (!(Conf['Remove Spoilers'] || Conf['Reveal Spoilers'])) { + while ((html2 = html.replace(/(?:(?!<\/?s>).)*<\/s>/g, '[spoiler]')) !== html) { + html = html2; + } + } + html = html.replace(/^Rolled [^<]*<\/b>/i, '').replace(/ " + ((!o.isReply || boardID === "f" || subject) ? "" + E(subject || "") + " " : "") + "" + ((email) ? "" : "") + "" + E(name) + "" + ((tripcode) ? " " + E(tripcode) + "" : "") + ((pass) ? " " : "") + ((capcode) ? " ## " + E(capcode) + "" : "") + ((email) ? "" : "") + ((boardID === "f" && !o.isReply || capcodeDescription) ? "" : " ") + ((capcodeDescription) ? " \""" : "") + ((uniqueID && !capcode) ? " (ID: " + E(uniqueID) + ")" : "") + ((flagCode) ? " " : "") + ((flagCodeTroll) ? " " : "") + "
      " + E(dateText) + " No." + E(ID) + "" + ((o.isSticky) ? " \"Sticky\"" : "") + ((o.isClosed && !o.isArchived) ? " \"Closed\"" : "") + ((o.isArchived) ? " \"Archived\"" : "") + ((!o.isReply && g.VIEW === "index") ? "   [Reply]" : "") + ""}; + + /* File Info */ + if (file) { + protocol = /^https?:(?=\/\/i\.4cdn\.org\/)/; + fileURL = file.url.replace(protocol, ''); + shortFilename = Build.shortFilename(file.name); + fileThumb = file.isSpoiler ? Build.spoilerThumb(boardID) : file.thumbURL.replace(protocol, ''); + } + fileBlock = {innerHTML: ((file) ? "
      " + ((boardID === "f") ? "
      File: " + E(file.name) + "-(" + E(file.size) + ", " + E(file.dimensions) + ((file.tag) ? ", " + E(file.tag) : "") + ")
      " : "
      File: " + ((file.isSpoiler) ? "Spoiler Image" : E(shortFilename)) + " (" + E(file.size) + ", " + E(file.dimensions || "PDF") + ")
      \""") + "
      " : ((o.fileDeleted) ? "
      \"File
      " : ""))}; + + /* Whole Post */ + postClass = o.isReply ? 'reply' : 'op'; + wholePost = {innerHTML: ((o.isReply) ? "
      >>
      " : "") + "
      " + ((o.isReply) ? (postInfo).innerHTML + (fileBlock).innerHTML : (fileBlock).innerHTML + (postInfo).innerHTML) + "
      " + (commentHTML).innerHTML + "
      "}; + container = $.el('div', { + className: "postContainer " + postClass + "Container", + id: "pc" + ID + }); + $.extend(container, wholePost); + ref1 = $$('.quotelink', container); + for (i = 0, len = ref1.length; i < len; i++) { + quote = ref1[i]; + href = quote.getAttribute('href'); + if (href[0] === '#') { + if (!Build.sameThread(boardID, threadID)) { + quote.href = Build.threadURL(boardID, threadID) + href; + } + } else { + if ((match = quote.href.match(SW.yotsuba.regexp.quotelink)) && (Build.sameThread(match[1], match[2]))) { + quote.href = href.match(/(#[^#]*)?$/)[0] || '#'; + } + } + } + return container; + }, + summaryText: function(status, posts, files) { + var text; + text = ''; + if (status) { + text += status + " "; + } + text += posts + " post" + (posts > 1 ? 's' : ''); + if (+files) { + text += " and " + files + " image repl" + (files > 1 ? 'ies' : 'y'); + } + return text += " " + (status === '-' ? 'shown' : 'omitted') + "."; + }, + summary: function(boardID, threadID, posts, files) { + return $.el('a', { + className: 'summary', + textContent: Build.summaryText('', posts, files), + href: "/" + boardID + "/thread/" + threadID + }); + }, + thread: function(thread, data, withReplies) { + var files, posts, ref, root, summary; + if ((root = thread.nodes.root)) { + $.rmAll(root); + } else { + thread.nodes.root = root = $.el('div', { + className: 'thread', + id: "t" + data.no + }); + } + if (Build.hat) { + $.add(root, Build.hat.cloneNode(false)); + } + $.add(root, thread.OP.nodes.root); + if (data.omitted_posts || !withReplies && data.replies) { + ref = withReplies ? [ + data.omitted_posts, data.images - data.last_replies.filter(function(data) { + return !!data.ext; + }).length + ] : [data.replies, data.images], posts = ref[0], files = ref[1]; + summary = Build.summary(thread.board.ID, data.no, posts, files); + $.add(root, summary); + } + return root; + }, + catalogThread: function(thread, data, pageCount) { + var br, container, cssText, fileCount, gifIcon, i, imgClass, len, postCount, ratio, ref, root, spoilerRange, src, staticPath, tn_h, tn_w; + staticPath = Build.staticPath, gifIcon = Build.gifIcon; + tn_w = data.tn_w, tn_h = data.tn_h; + if (data.spoiler && !Conf['Reveal Spoiler Thumbnails']) { + src = staticPath + "spoiler"; + if (spoilerRange = Build.spoilerRange[thread.board]) { + src += ("-" + thread.board) + Math.floor(1 + spoilerRange * Math.random()); + } + src += '.png'; + imgClass = 'spoiler-file'; + cssText = "--tn-w: 100; --tn-h: 100;"; + } else if (data.filedeleted) { + src = staticPath + "filedeleted-res" + gifIcon; + imgClass = 'deleted-file'; + } else if (thread.OP.file) { + src = thread.OP.file.thumbURL; + ratio = 250 / Math.max(tn_w, tn_h); + cssText = "--tn-w: " + (tn_w * ratio) + "; --tn-h: " + (tn_h * ratio) + ";"; + } else { + src = staticPath + "nofile.png"; + imgClass = 'no-file'; + } + postCount = data.replies + 1; + fileCount = data.images + !!data.ext; + container = $.el('div', {innerHTML: "
      " + E(postCount) + " / " + E(fileCount) + " / " + E(pageCount) + "" + ((thread.isSticky) ? "" : "") + ((thread.isClosed) ? "" : "") + "
      "}); + $.before(thread.OP.nodes.info, slice.call(container.childNodes)); + ref = $$('br', thread.OP.nodes.comment); + for (i = 0, len = ref.length; i < len; i++) { + br = ref[i]; + if (br.previousSibling && br.previousSibling.nodeName === 'BR') { + $.addClass(br, 'extra-linebreak'); + } + } + root = $.el('div', { + className: 'thread catalog-thread', + id: "t" + thread + }); + if (thread.OP.highlights) { + $.addClass.apply($, [root].concat(slice.call(thread.OP.highlights))); + } + if (!thread.OP.file) { + $.addClass(root, 'noFile'); + } + root.style.cssText = cssText || ''; + return root; + }, + catalogReply: function(thread, data) { + var excerpt, link; + excerpt = ''; + if (data.com) { + excerpt = Build.parseCommentDisplay(data.com).replace(/>>\d+/g, '').trim().replace(/\n+/g, ' // '); + } + if (data.ext) { + excerpt || (excerpt = "" + ($.unescape(data.filename)) + data.ext); + } + if (data.com) { + excerpt || (excerpt = $.unescape(data.com.replace(//gi, ' // '))); + } + excerpt || (excerpt = '\xA0'); + if (excerpt.length > 73) { + excerpt = excerpt.slice(0, 70) + "..."; + } + link = Build.postURL(thread.board.ID, thread.ID, data.no); + return $.el('div', { + className: 'catalog-reply' + }, {innerHTML: ": " + E(excerpt) + "..."}); + } + }; + + SW.yotsuba.Build = Build; + +}).call(this); + +Site = (function() { + var Site; + + Site = { + defaultProperties: { + '4chan.org': { + software: 'yotsuba' + }, + '4channel.org': { + canonical: '4chan.org' + }, + '4cdn.org': { + canonical: '4chan.org' + }, + 'notso.smuglo.li': { + canonical: 'smuglo.li' + }, + 'smugloli.net': { + canonical: 'smuglo.li' + }, + 'smug.nepu.moe': { + canonical: 'smuglo.li' + } + }, + init: function(cb) { + var hostname; + $.extend(Conf['siteProperties'], Site.defaultProperties); + hostname = Site.resolve(); + if (hostname && $.hasOwn(SW, Conf['siteProperties'][hostname].software)) { + this.set(hostname); + cb(); + } + return $.onExists(doc, 'body', (function(_this) { + return function() { + var base, base1, changed, changes, key, properties, software; + for (software in SW) { + if (!((changes = typeof (base = SW[software]).detect === "function" ? base.detect() : void 0))) { + continue; + } + changes.software = software; + hostname = location.hostname.replace(/^www\./, ''); + properties = ((base1 = Conf['siteProperties'])[hostname] || (base1[hostname] = $.dict())); + changed = 0; + for (key in changes) { + if (!(properties[key] !== changes[key])) { + continue; + } + properties[key] = changes[key]; + changed++; + } + if (changed) { + $.set('siteProperties', Conf['siteProperties']); + } + if (!g.SITE) { + _this.set(hostname); + cb(); + } + return; + } + }; + })(this)); + }, + resolve: function(url) { + var canonical, hostname; + if (url == null) { + url = location; + } + hostname = url.hostname; + while (hostname && !$.hasOwn(Conf['siteProperties'], hostname)) { + hostname = hostname.replace(/^[^.]*\.?/, ''); + } + if (hostname) { + if ((canonical = Conf['siteProperties'][hostname].canonical)) { + hostname = canonical; + } + } + return hostname; + }, + parseURL: function(url) { + var siteID; + siteID = Site.resolve(url); + return Main.parseURL(g.sites[siteID], url); + }, + set: function(hostname) { + var ID, properties, ref, site, software; + ref = Conf['siteProperties']; + for (ID in ref) { + properties = ref[ID]; + if (properties.canonical) { + continue; + } + software = properties.software; + if (!(software && $.hasOwn(SW, software))) { + continue; + } + g.sites[ID] = site = Object.create(SW[software]); + $.extend(site, { + ID: ID, + siteID: ID, + properties: properties, + software: software + }); + } + return g.SITE = g.sites[hostname]; + } + }; + + return Site; + +}).call(this); + +Redirect = (function() { + var Redirect, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + Redirect = { + archives: [ + { "uid": 3, "name": "4plebs", "domain": "archive.4plebs.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "adv", "f", "hr", "mlpol", "mo", "o", "pol", "s4s", "sp", "tg", "trv", "tv", "x" ], "files": [ "adv", "f", "hr", "mlpol", "mo", "o", "pol", "s4s", "sp", "tg", "trv", "tv", "x" ], "reports": true }, + { "uid": 10, "name": "warosu", "domain": "warosu.org", "http": false, "https": true, "software": "fuuka", "boards": [ "3", "biz", "cgl", "ck", "diy", "fa", "ic", "jp", "lit", "sci", "vr", "vt" ], "files": [ "3", "biz", "cgl", "ck", "diy", "fa", "ic", "jp", "lit", "sci", "vr", "vt" ], "search": [ "biz", "cgl", "ck", "diy", "fa", "ic", "jp", "lit", "sci", "vr", "vt" ] }, + { "uid": 23, "name": "Desuarchive", "domain": "desuarchive.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "a", "aco", "an", "c", "cgl", "co", "d", "fit", "g", "his", "int", "k", "m", "mlp", "mu", "q", "qa", "r9k", "tg", "trash", "vr", "wsg" ], "files": [ "a", "aco", "an", "c", "cgl", "co", "d", "fit", "g", "his", "int", "k", "m", "mlp", "mu", "q", "qa", "r9k", "tg", "trash", "vr" ], "reports": true }, + { "uid": 24, "name": "fireden.net", "domain": "boards.fireden.net", "http": false, "https": true, "software": "foolfuuka", "boards": [ "cm", "co", "ic", "sci", "vip", "y" ], "files": [ "cm", "co", "ic", "sci", "vip", "y" ], "search": [ "cm", "co", "ic", "sci", "y" ] }, + { "uid": 25, "name": "arch.b4k.co", "domain": "arch.b4k.co", "http": true, "https": true, "software": "foolfuuka", "boards": [ "g", "mlp", "qb", "v", "vg", "vm", "vmg", "vp", "vrpg", "vst" ], "files": [ "qb", "v", "vg", "vm", "vmg", "vp", "vrpg", "vst" ], "search": [ "qb", "v", "vg", "vm", "vmg", "vp", "vrpg", "vst" ] }, + { "uid": 29, "name": "Archived.Moe", "domain": "archived.moe", "http": true, "https": true, "software": "foolfuuka", "boards": [ "3", "a", "aco", "adv", "an", "asp", "b", "bant", "biz", "c", "can", "cgl", "ck", "cm", "co", "cock", "con", "d", "diy", "e", "f", "fa", "fap", "fit", "fitlit", "g", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "int", "jp", "k", "lgbt", "lit", "m", "mlp", "mlpol", "mo", "mtv", "mu", "n", "news", "o", "out", "outsoc", "p", "po", "pol", "pw", "q", "qa", "qb", "qst", "r", "r9k", "s", "s4s", "sci", "soc", "sp", "spa", "t", "tg", "toy", "trash", "trv", "tv", "u", "v", "vg", "vint", "vip", "vm", "vmg", "vp", "vr", "vrpg", "vst", "vt", "w", "wg", "wsg", "wsr", "x", "xs", "y" ], "files": [ "can", "cock", "con", "fap", "fitlit", "gd", "mlpol", "mo", "mtv", "outsoc", "po", "q", "qb", "qst", "spa", "vint", "vip" ], "search": [ "aco", "adv", "an", "asp", "b", "bant", "biz", "c", "can", "cgl", "ck", "cm", "cock", "con", "d", "diy", "e", "f", "fap", "fitlit", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "lgbt", "lit", "mlpol", "mo", "mtv", "n", "news", "o", "out", "outsoc", "p", "po", "pw", "q", "qa", "qst", "r", "s", "soc", "spa", "trv", "u", "vint", "vip", "vrpg", "w", "wg", "wsg", "wsr", "x", "y" ], "reports": true }, + { "uid": 30, "name": "TheBArchive.com", "domain": "thebarchive.com", "http": true, "https": true, "software": "foolfuuka", "boards": [ "b", "bant" ], "files": [ "b", "bant" ], "reports": true }, + { "uid": 31, "name": "Archive Of Sins", "domain": "archiveofsins.com", "http": true, "https": true, "software": "foolfuuka", "boards": [ "h", "hc", "hm", "i", "lgbt", "r", "s", "soc", "t", "u" ], "files": [ "h", "hc", "hm", "i", "lgbt", "r", "s", "soc", "t", "u" ], "reports": true }, + { "uid": 36, "name": "palanq.win", "domain": "archive.palanq.win", "http": false, "https": true, "software": "foolfuuka", "boards": [ "bant", "c", "con", "e", "i", "n", "news", "out", "p", "pw", "qst", "toy", "vip", "vp", "vt", "w", "wg", "wsr" ], "files": [ "bant", "c", "e", "i", "n", "news", "out", "p", "pw", "qst", "toy", "vip", "vp", "vt", "w", "wg", "wsr" ], "reports": true }, + { "uid": 37, "name": "Eientei", "domain": "eientei.xyz", "http": false, "https": true, "software": "Eientei", "boards": [ "3", "i", "sci", "xs" ], "files": [ "3", "i", "sci", "xs" ], "reports": true } + ], + init: function() { + var now, ref; + this.selectArchives(); + if (Conf['archiveAutoUpdate']) { + now = Date.now(); + if (!((now - 2 * $.DAY < (ref = Conf['lastarchivecheck']) && ref <= now))) { + return this.update(); + } + } + }, + selectArchives: function() { + var archive, archives, boardID, boards, data, files, id, j, k, key, l, len, len1, len2, name, o, record, ref, ref1, ref2, software, type, uid; + o = { + thread: $.dict(), + post: $.dict(), + file: $.dict() + }; + archives = $.dict(); + ref = Conf['archives']; + for (j = 0, len = ref.length; j < len; j++) { + data = ref[j]; + ref1 = ['boards', 'files']; + for (k = 0, len1 = ref1.length; k < len1; k++) { + key = ref1[k]; + if (!(data[key] instanceof Array)) { + data[key] = []; + } + } + uid = data.uid, name = data.name, boards = data.boards, files = data.files, software = data.software; + if (software !== 'fuuka' && software !== 'foolfuuka') { + continue; + } + archives[JSON.stringify(uid != null ? uid : name)] = data; + for (l = 0, len2 = boards.length; l < len2; l++) { + boardID = boards[l]; + if (!(boardID in o.thread)) { + o.thread[boardID] = data; + } + if (!(boardID in o.post || software !== 'foolfuuka')) { + o.post[boardID] = data; + } + if (!(boardID in o.file || indexOf.call(files, boardID) < 0)) { + o.file[boardID] = data; + } + } + } + ref2 = Conf['selectedArchives']; + for (boardID in ref2) { + record = ref2[boardID]; + for (type in record) { + id = record[type]; + if (!((archive = archives[JSON.stringify(id)]) && $.hasOwn(o, type))) { + continue; + } + boards = type === 'file' ? archive.files : archive.boards; + if (indexOf.call(boards, boardID) >= 0) { + o[type][boardID] = archive; + } + } + } + return Redirect.data = o; + }, + update: function(cb) { + var err, fail, i, j, k, len, len1, load, nloaded, ref, ref1, response, responses, url, urls; + urls = []; + responses = []; + nloaded = 0; + ref = Conf['archiveLists'].split('\n'); + for (j = 0, len = ref.length; j < len; j++) { + url = ref[j]; + if (!(url[0] !== '#')) { + continue; + } + url = url.trim(); + if (url) { + urls.push(url); + } + } + fail = function(url, action, msg) { + return new Notice('warning', "Error " + action + " archive data from\n" + url + "\n" + msg, 20); + }; + load = function(i) { + return function() { + var response; + if (this.status !== 200) { + return fail(urls[i], 'fetching', (this.status ? "Error " + this.statusText + " (" + this.status + ")" : 'Connection Error')); + } + response = this.response; + if (!(response instanceof Array)) { + response = [response]; + } + responses[i] = response; + nloaded++; + if (nloaded === urls.length) { + return Redirect.parse(responses, cb); + } + }; + }; + if (urls.length) { + for (i = k = 0, len1 = urls.length; k < len1; i = ++k) { + url = urls[i]; + if ((ref1 = url[0]) === '[' || ref1 === '{') { + try { + response = JSON.parse(url); + } catch (error) { + err = error; + fail(url, 'parsing', err.message); + continue; + } + load(i).call({ + status: 200, + response: response + }); + } else { + CrossOrigin.ajax(url, { + onloadend: load(i) + }); + } + } + } else { + Redirect.parse([], cb); + } + }, + parse: function(responses, cb) { + var archiveUIDs, archives, data, items, j, k, len, len1, ref, response, uid; + archives = []; + archiveUIDs = $.dict(); + for (j = 0, len = responses.length; j < len; j++) { + response = responses[j]; + for (k = 0, len1 = response.length; k < len1; k++) { + data = response[k]; + uid = JSON.stringify((ref = data.uid) != null ? ref : data.name); + if (uid in archiveUIDs) { + $.extend(archiveUIDs[uid], data); + } else { + archiveUIDs[uid] = $.dict.clone(data); + archives.push(data); + } + } } items = { archives: archives, @@ -6368,7 +9091,7 @@ Redirect = (function() { protocol: function(archive) { var protocol; protocol = location.protocol; - if (!archive[protocol.slice(0, -1)]) { + if (!$.getOwn(archive, protocol.slice(0, -1))) { protocol = protocol === 'https:' ? 'http:' : 'https:'; } return protocol + "//"; @@ -6398,6 +9121,16 @@ Redirect = (function() { file: function(archive, arg) { var boardID, filename; boardID = arg.boardID, filename = arg.filename; + if (!filename) { + return ''; + } + if (boardID === 'f') { + filename = encodeURIComponent($.unescape(decodeURIComponent(filename))); + } else { + if (/[sm]\.jpg$/.test(filename)) { + return ''; + } + } return "" + (Redirect.protocol(archive)) + archive.domain + "/" + boardID + "/full_image/" + filename; }, board: function(archive, arg) { @@ -6410,9 +9143,10 @@ Redirect = (function() { boardID = arg.boardID, type = arg.type, value = arg.value; type = type === 'name' ? 'username' : type === 'MD5' ? 'image' : type; if (type === 'capcode') { - value = { - 'Developer': 'dev' - }[value] || value.toLowerCase(); + value = $.getOwn({ + 'Developer': 'dev', + 'Verified': 'ver' + }, value) || value.toLowerCase(); } else if (type === 'image') { value = value.replace(/[+\/=]/g, function(c) { return { @@ -6426,6 +9160,19 @@ Redirect = (function() { path = archive.software === 'foolfuuka' ? boardID + "/search/" + type + "/" + value + "/" : type === 'image' ? boardID + "/image/" + value : boardID + "/?task=search2&search_" + type + "=" + value; return "" + (Redirect.protocol(archive)) + archive.domain + "/" + path; }, + report: function(boardID) { + var archive, boards, domain, https, j, len, name, ref, reports, software, urls; + urls = []; + ref = Conf['archives']; + for (j = 0, len = ref.length; j < len; j++) { + archive = ref[j]; + software = archive.software, https = archive.https, reports = archive.reports, boards = archive.boards, name = archive.name, domain = archive.domain; + if (software === 'foolfuuka' && https && reports && boards instanceof Array && indexOf.call(boards, boardID) >= 0) { + urls.push([name, "https://" + domain + "/_/api/chan/offsite_report/"]); + } + } + return urls; + }, securityCheck: function(url) { return /^https:\/\//.test(url) || location.protocol === 'http:' || Conf['Exempt Archives from Encryption']; }, @@ -6452,50 +9199,10 @@ Anonymize = (function() { Anonymize = { init: function() { - var ref; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Anonymize'])) { - return; - } - if (g.VIEW === 'archive') { - return this.archive(); - } - return Callbacks.Post.push({ - name: 'Anonymize', - cb: this.node - }); - }, - node: function() { - var email, name, ref, tripcode; - if (this.info.capcode || this.isClone) { + if (!Conf['Anonymize']) { return; } - ref = this.nodes, name = ref.name, tripcode = ref.tripcode, email = ref.email; - if (this.info.name !== 'Anonymous') { - name.textContent = 'Anonymous'; - } - if (tripcode) { - $.rm(tripcode); - delete this.nodes.tripcode; - } - if (email) { - $.replace(email, name); - return delete this.nodes.email; - } - }, - archive: function() { - return $.ready(function() { - var i, j, len, len1, name, ref, ref1, trip; - ref = $$('.name'); - for (i = 0, len = ref.length; i < len; i++) { - name = ref[i]; - name.textContent = 'Anonymous'; - } - ref1 = $$('.postertrip'); - for (j = 0, len1 = ref1.length; j < len1; j++) { - trip = ref1[j]; - $.rm(trip); - } - }); + return $.addClass(doc, 'anonymize'); } }; @@ -6505,48 +9212,59 @@ Anonymize = (function() { Filter = (function() { var Filter, - indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, + slice = [].slice; Filter = { - filters: {}, + filters: $.dict(), init: function() { - var boards, err, excludes, filter, hl, i, key, len, line, op, ref, ref1, ref2, ref3, ref4, ref5, ref6, regexp, stub, top; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Filter'])) { + var base, base1, boards, err, excludes, file, filter, hide, hl, i, isstring, j, key, len, len1, line, mask, noti, op, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, regexp, stub, top, type, types; + if (!(((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'catalog') && Conf['Filter'])) { + return; + } + if (g.VIEW === 'catalog' && !Conf['Filter in Native Catalog']) { return; } if (!Conf['Filtered Backlinks']) { $.addClass(doc, 'hide-backlinks'); } for (key in Config.filter) { - this.filters[key] = []; ref1 = Conf[key].split('\n'); for (i = 0, len = ref1.length; i < len; i++) { line = ref1[i]; if (line[0] === '#') { continue; } - if (!(regexp = line.match(/\/(.+)\/(\w*)/))) { + if (!(regexp = line.match(/\/(.*)\/(\w*)/))) { continue; } filter = line.replace(regexp[0], ''); - boards = ((ref2 = filter.match(/boards:([^;]+)/)) != null ? ref2[1].toLowerCase() : void 0) || 'global'; - boards = boards === 'global' ? null : boards.split(','); - excludes = boards === null ? ((ref3 = filter.match(/exclude:([^;]+)/)) != null ? ref3[1].toLowerCase().split(',') : void 0) || null : null; - if (key === 'uniqueID' || key === 'MD5') { + boards = this.parseBoards((ref2 = filter.match(/(?:^|;)\s*boards:([^;]+)/)) != null ? ref2[1] : void 0); + excludes = this.parseBoards((ref3 = filter.match(/(?:^|;)\s*exclude:([^;]+)/)) != null ? ref3[1] : void 0); + if ((isstring = (key === 'uniqueID' || key === 'MD5'))) { regexp = regexp[1]; } else { try { regexp = RegExp(regexp[1], regexp[2]); - } catch (_error) { - err = _error; + } catch (error) { + err = error; new Notice('warning', [$.tn("Invalid " + key + " filter:"), $.el('br'), $.tn(line), $.el('br'), $.tn(err.message)], 60); continue; } } - op = ((ref4 = filter.match(/[^t]op:(yes|no|only)/)) != null ? ref4[1] : void 0) || 'yes'; + op = ((ref4 = filter.match(/(?:^|;)\s*op:(no|only)/)) != null ? ref4[1] : void 0) || ''; + mask = $.getOwn({ + 'no': 1, + 'only': 2 + }, op) || 0; + file = ((ref5 = filter.match(/(?:^|;)\s*file:(no|only)/)) != null ? ref5[1] : void 0) || ''; + mask = mask | ($.getOwn({ + 'no': 4, + 'only': 8 + }, file) || 0); stub = (function() { - var ref5; - switch ((ref5 = filter.match(/stub:(yes|no)/)) != null ? ref5[1] : void 0) { + var ref6; + switch ((ref6 = filter.match(/(?:^|;)\s*stub:(yes|no)/)) != null ? ref6[1] : void 0) { case 'yes': return true; case 'no': @@ -6555,146 +9273,416 @@ Filter = (function() { return Conf['Stubs']; } })(); - if (hl = /highlight/.test(filter)) { - hl = ((ref5 = filter.match(/highlight:([\w-]+)/)) != null ? ref5[1] : void 0) || 'filter-highlight'; - top = ((ref6 = filter.match(/top:(yes|no)/)) != null ? ref6[1] : void 0) || 'yes'; + noti = /(?:^|;)\s*notify/.test(filter); + if ((hl = /(?:^|;)\s*highlight/.test(filter))) { + hl = ((ref6 = filter.match(/(?:^|;)\s*highlight:([\w-]+)/)) != null ? ref6[1] : void 0) || 'filter-highlight'; + top = ((ref7 = filter.match(/(?:^|;)\s*top:(yes|no)/)) != null ? ref7[1] : void 0) || 'yes'; top = top === 'yes'; } - this.filters[key].push(this.createFilter(regexp, boards, excludes, op, stub, hl, top)); - } - if (!this.filters[key].length) { - delete this.filters[key]; + if (key === 'general') { + if ((types = filter.match(/(?:^|;)\s*type:([^;]*)/))) { + types = types[1].split(','); + } else { + types = ['subject', 'name', 'filename', 'comment']; + } + } + hide = !(hl || noti); + filter = { + isstring: isstring, + regexp: regexp, + boards: boards, + excludes: excludes, + mask: mask, + hide: hide, + stub: stub, + hl: hl, + top: top, + noti: noti + }; + if (key === 'general') { + for (j = 0, len1 = types.length; j < len1; j++) { + type = types[j]; + ((base = this.filters)[type] || (base[type] = [])).push(filter); + } + } else { + ((base1 = this.filters)[key] || (base1[key] = [])).push(filter); + } } } if (!Object.keys(this.filters).length) { return; } - return Callbacks.Post.push({ - name: 'Filter', - cb: this.node - }); + if (g.VIEW === 'catalog') { + return Filter.catalog(); + } else { + return Callbacks.Post.push({ + name: 'Filter', + cb: this.node + }); + } }, - createFilter: function(regexp, boards, excludes, op, stub, hl, top) { - var settings, test; - test = typeof regexp === 'string' ? function(value) { - return regexp === value; - } : function(value) { - return regexp.test(value); - }; - settings = { - hide: !hl, - stub: stub, - "class": hl, - top: top - }; - return function(value, boardID, isReply) { - if (boards && indexOf.call(boards, boardID) < 0) { - return false; - } - if (excludes && indexOf.call(excludes, boardID) >= 0) { - return false; - } - if (isReply && op === 'only' || !isReply && op === 'no') { - return false; - } - if (!test(value)) { - return false; + parseBoards: function(boardsRaw) { + var boardID, boardID2, boards, i, j, len, len1, ref, ref1, ref2, ref3, site, siteFilter, siteID; + if (!boardsRaw) { + return false; + } + if ((boards = Filter.parseBoardsMemo[boardsRaw])) { + return boards; + } + boards = $.dict(); + siteFilter = ''; + ref = boardsRaw.split(','); + for (i = 0, len = ref.length; i < len; i++) { + boardID = ref[i]; + if (indexOf.call(boardID, ':') >= 0) { + ref1 = boardID.split(':').slice(-2), siteFilter = ref1[0], boardID = ref1[1]; + } + ref2 = g.sites; + for (siteID in ref2) { + site = ref2[siteID]; + if (siteID.slice(0, siteFilter.length) === siteFilter) { + if (boardID === 'nsfw' || boardID === 'sfw') { + ref3 = (typeof site.sfwBoards === "function" ? site.sfwBoards(boardID === 'sfw') : void 0) || []; + for (j = 0, len1 = ref3.length; j < len1; j++) { + boardID2 = ref3[j]; + boards[siteID + "/" + boardID2] = true; + } + } else { + boards[siteID + "/" + (encodeURIComponent(boardID))] = true; + } + } } - return settings; - }; + } + Filter.parseBoardsMemo[boardsRaw] = boards; + return boards; }, - node: function() { - var filter, i, key, len, ref, ref1, result, value; - if (this.isClone) { - return; + parseBoardsMemo: $.dict(), + test: function(post, hideable) { + var board, filter, hide, hl, i, j, key, len, len1, mask, noti, ref, ref1, ref2, site, stub, top, value; + if (hideable == null) { + hideable = true; } + if (post.filterResults) { + return post.filterResults; + } + hide = false; + stub = true; + hl = void 0; + top = false; + noti = false; + if (QuoteYou.isYou(post)) { + hideable = false; + } + mask = (post.isReply ? 2 : 1); + mask = mask | (post.file ? 4 : 8); + board = post.siteID + "/" + post.boardID; + site = post.siteID + "/*"; for (key in Filter.filters) { - if ((value = Filter[key](this)) != null) { - ref = Filter.filters[key]; - for (i = 0, len = ref.length; i < len; i++) { - filter = ref[i]; - if (!(result = filter(value, this.board.ID, this.isReply))) { + ref = Filter.values(key, post); + for (i = 0, len = ref.length; i < len; i++) { + value = ref[i]; + ref1 = Filter.filters[key]; + for (j = 0, len1 = ref1.length; j < len1; j++) { + filter = ref1[j]; + if ((filter.boards && !(filter.boards[board] || filter.boards[site])) || (filter.excludes && (filter.excludes[board] || filter.excludes[site])) || (filter.mask & mask) || (filter.isstring ? filter.regexp !== value : !filter.regexp.test(value))) { continue; } - if (result.hide && !this.isFetchedQuote) { - if (this.isReply) { - PostHiding.hide(this, result.stub); - } else if (g.VIEW === 'index') { - ThreadHiding.hide(this.thread, result.stub); - } else { - continue; + if (filter.hide) { + if (hideable) { + hide = true; + stub && (stub = filter.stub); + } + } else { + if (!(hl && (ref2 = filter.hl, indexOf.call(hl, ref2) >= 0))) { + (hl || (hl = [])).push(filter.hl); + } + top || (top = filter.top); + if (filter.noti) { + noti = true; } - return; - } - $.addClass(this.nodes.root, result["class"]); - if (!(this.highlights && (ref1 = result["class"], indexOf.call(this.highlights, ref1) >= 0))) { - (this.highlights || (this.highlights = [])).push(result["class"]); - } - if (!this.isReply && result.top) { - this.thread.isOnTop = true; } } } } + if (hide) { + return { + hide: hide, + stub: stub + }; + } else { + return { + hl: hl, + top: top, + noti: noti + }; + } }, - isHidden: function(post) { - var filter, i, key, len, ref, result, value; - for (key in Filter.filters) { - if ((value = Filter[key](post)) != null) { - ref = Filter.filters[key]; - for (i = 0, len = ref.length; i < len; i++) { - filter = ref[i]; - if (result = filter(value, post.boardID, post.isReply)) { - if (result.hide) { - return true; - } - } - } + node: function() { + var hide, hl, noti, ref, stub, top; + if (this.isClone) { + return; + } + ref = Filter.test(this, !this.isFetchedQuote && (this.isReply || g.VIEW === 'index')), hide = ref.hide, stub = ref.stub, hl = ref.hl, top = ref.top, noti = ref.noti; + if (hide) { + if (this.isReply) { + PostHiding.hide(this, stub); + } else { + ThreadHiding.hide(this.thread, stub); + } + } else { + if (hl) { + this.highlights = hl; + $.addClass.apply($, [this.nodes.root].concat(slice.call(hl))); } } - return false; + if (noti && Unread.posts && (this.ID > Unread.lastReadPost) && !QuoteYou.isYou(this)) { + return Unread.openNotification(this, ' triggered a notification filter'); + } }, - postID: function(post) { - var ref; - return "" + ((ref = post.ID) != null ? ref : post.postID); + catalog: function() { + var base, url; + if (!(url = typeof (base = g.SITE.urls).catalogJSON === "function" ? base.catalogJSON(g.BOARD) : void 0)) { + return; + } + Filter.catalogData = $.dict(); + $.ajax(url, { + onloadend: Filter.catalogParse + }); + return Callbacks.CatalogThreadNative.push({ + name: 'Filter', + cb: this.catalogNode + }); }, - name: function(post) { - return post.info.name; + catalogParse: function() { + var i, item, j, len, len1, page, ref, ref1, ref2; + if ((ref = this.status) !== 200 && ref !== 404) { + new Notice('warning', "Failed to fetch catalog JSON data. " + (this.status ? "Error " + this.statusText + " (" + this.status + ")" : 'Connection Error'), 1); + return; + } + ref1 = this.response; + for (i = 0, len = ref1.length; i < len; i++) { + page = ref1[i]; + ref2 = page.threads; + for (j = 0, len1 = ref2.length; j < len1; j++) { + item = ref2[j]; + Filter.catalogData[item.no] = item; + } + } + g.BOARD.threads.forEach(function(thread) { + if (thread.catalogViewNative) { + return Filter.catalogNode.call(thread.catalogViewNative); + } + }); }, - uniqueID: function(post) { - return post.info.uniqueID; + catalogNode: function() { + var base, hide, hl, ref, ref1, top; + if (!(this.boardID === g.BOARD.ID && Filter.catalogData[this.ID])) { + return; + } + if ((ref = QuoteYou.db) != null ? ref.get({ + siteID: g.SITE.ID, + boardID: this.boardID, + threadID: this.ID, + postID: this.ID + }) : void 0) { + return; + } + ref1 = Filter.test(g.SITE.Build.parseJSON(Filter.catalogData[this.ID], this)), hide = ref1.hide, hl = ref1.hl, top = ref1.top; + if (hide) { + return this.nodes.root.hidden = true; + } else { + if (hl) { + this.highlights = hl; + $.addClass.apply($, [this.nodes.root].concat(slice.call(hl))); + } + if (top) { + $.prepend(this.nodes.root.parentNode, this.nodes.root); + return typeof (base = g.SITE).catalogPin === "function" ? base.catalogPin(this.nodes.root) : void 0; + } + } }, - tripcode: function(post) { - return post.info.tripcode; + isHidden: function(post) { + return !!Filter.test(post).hide; }, - capcode: function(post) { - return post.info.capcode; + valueF: { + postID: function(post) { + return ["" + post.ID]; + }, + name: function(post) { + return [post.info.name]; + }, + uniqueID: function(post) { + return [post.info.uniqueID || '']; + }, + tripcode: function(post) { + return [post.info.tripcode]; + }, + capcode: function(post) { + return [post.info.capcode]; + }, + pass: function(post) { + return [post.info.pass]; + }, + email: function(post) { + return [post.info.email]; + }, + subject: function(post) { + return [post.info.subject || (post.isReply ? void 0 : '')]; + }, + comment: function(post) { + var base, ref, ref1; + return [((base = post.info).comment != null ? base.comment : base.comment = (ref = g.sites[post.siteID]) != null ? (ref1 = ref.Build) != null ? typeof ref1.parseComment === "function" ? ref1.parseComment(post.info.commentHTML.innerHTML) : void 0 : void 0 : void 0)]; + }, + flag: function(post) { + return [post.info.flag]; + }, + filename: function(post) { + return post.files.map(function(f) { + return f.name; + }); + }, + dimensions: function(post) { + return post.files.map(function(f) { + return f.dimensions; + }); + }, + filesize: function(post) { + return post.files.map(function(f) { + return f.size; + }); + }, + MD5: function(post) { + return post.files.map(function(f) { + return f.MD5; + }); + } }, - subject: function(post) { - return post.info.subject; + values: function(key, post) { + if ($.hasOwn(Filter.valueF, key)) { + return Filter.valueF[key](post).filter(function(v) { + return v != null; + }); + } else { + return [ + key.split('+').map(function(k) { + var f; + if ((f = $.getOwn(Filter.valueF, k))) { + return f(post).map(function(v) { + return v || ''; + }).join('\n'); + } else { + return ''; + } + }).join('\n') + ]; + } }, - comment: function(post) { - var base; - return (base = post.info).comment != null ? base.comment : base.comment = Build.parseComment(post.info.commentHTML.innerHTML); + addFilter: function(type, re, cb) { + if (!$.hasOwn(Config.filter, type)) { + return; + } + return $.get(type, Conf[type], function(item) { + var save; + save = item[type]; + save = save ? save + "\n" + re : re; + return $.set(type, save, cb); + }); }, - flag: function(post) { - return post.info.flag; + removeFilters: function(type, res, cb) { + return $.get(type, Conf[type], function(item) { + var save; + save = item[type]; + res = res.map(Filter.escape).join('|'); + save = save.replace(RegExp("(?:$\n|^)(?:" + res + ")$", 'mg'), ''); + return $.set(type, save, cb); + }); }, - filename: function(post) { - var ref; - return (ref = post.file) != null ? ref.name : void 0; + showFilters: function(type) { + var section, select; + Settings.open('Filter'); + section = $('.section-container'); + select = $('select[name=filter]', section); + select.value = type; + Settings.selectFilter.call(select); + return $.onExists(section, 'textarea', function(ta) { + var tl; + tl = ta.textLength; + ta.setSelectionRange(tl, tl); + return ta.focus(); + }); }, - dimensions: function(post) { - var ref; - return (ref = post.file) != null ? ref.dimensions : void 0; + quickFilterMD5: function() { + var files, filter, links, msg, notice, origin, post; + post = Get.postFromNode(this); + files = post.files.filter(function(f) { + return f.MD5; + }); + if (!files.length) { + return; + } + filter = files.map(function(f) { + return "/" + f.MD5 + "/"; + }).join('\n'); + Filter.addFilter('MD5', filter); + origin = post.origin || post; + if (origin.isReply) { + PostHiding.hide(origin); + } else if (g.VIEW === 'index') { + ThreadHiding.hide(origin.thread); + } + if (!Conf['MD5 Quick Filter Notifications']) { + if (post.nodes.post.getBoundingClientRect().height) { + new Notice('info', 'MD5 filtered.', 2); + } + return; + } + notice = Filter.quickFilterMD5.notice; + if (notice) { + notice.filters.push(filter); + notice.posts.push(origin); + return $('span', notice.el).textContent = notice.filters.length + " MD5s filtered."; + } else { + msg = $.el('div', {innerHTML: "MD5 filtered. [show] [undo]"}); + notice = Filter.quickFilterMD5.notice = new Notice('info', msg, void 0, function() { + return delete Filter.quickFilterMD5.notice; + }); + notice.filters = [filter]; + notice.posts = [origin]; + links = $$('a', msg); + $.on(links[0], 'click', Filter.quickFilterCB.show.bind(notice)); + return $.on(links[1], 'click', Filter.quickFilterCB.undo.bind(notice)); + } }, - filesize: function(post) { - var ref; - return (ref = post.file) != null ? ref.size : void 0; + quickFilterCB: { + show: function() { + Filter.showFilters('MD5'); + return this.close(); + }, + undo: function() { + var i, len, post, ref; + Filter.removeFilters('MD5', this.filters); + ref = this.posts; + for (i = 0, len = ref.length; i < len; i++) { + post = ref[i]; + if (post.isReply) { + PostHiding.show(post); + } else if (g.VIEW === 'index') { + ThreadHiding.show(post.thread); + } + } + return this.close(); + } }, - MD5: function(post) { - var ref; - return (ref = post.file) != null ? ref.MD5 : void 0; + escape: function(value) { + return value.replace(/\/|\\|\^|\$|\n|\.|\(|\)|\{|\}|\[|\]|\?|\*|\+|\|/g, function(c) { + if (c === '\n') { + return '\\n'; + } else if (c === '\\') { + return '\\\\'; + } else { + return "\\" + c; + } + }); }, menu: { init: function() { @@ -6714,7 +9702,7 @@ Filter = (function() { }, subEntries: [] }; - ref1 = [['Name', 'name'], ['Unique ID', 'uniqueID'], ['Tripcode', 'tripcode'], ['Capcode', 'capcode'], ['Subject', 'subject'], ['Comment', 'comment'], ['Flag', 'flag'], ['Filename', 'filename'], ['Image dimensions', 'dimensions'], ['Filesize', 'filesize'], ['Image MD5', 'MD5']]; + ref1 = [['Name', 'name'], ['Unique ID', 'uniqueID'], ['Tripcode', 'tripcode'], ['Capcode', 'capcode'], ['Pass Date', 'pass'], ['Email', 'email'], ['Subject', 'subject'], ['Comment', 'comment'], ['Flag', 'flag'], ['Filename', 'filename'], ['Image dimensions', 'dimensions'], ['Filesize', 'filesize'], ['Image MD5', 'MD5']]; for (i = 0, len = ref1.length; i < len; i++) { type = ref1[i]; entry.subEntries.push(Filter.menu.createSubEntry(type[0], type[1])); @@ -6732,40 +9720,25 @@ Filter = (function() { return { el: el, open: function(post) { - var value; - value = Filter[type](post); - return (value != null) && !(g.BOARD.ID === 'f' && type === 'MD5'); + return Filter.values(type, post).length; } }; }, makeFilter: function() { - var re, type, value; + var res, type, values; type = this.dataset.type; - value = Filter[type](Filter.menu.post); - re = type === 'uniqueID' || type === 'MD5' ? value : value.replace(/\/|\\|\^|\$|\n|\.|\(|\)|\{|\}|\[|\]|\?|\*|\+|\|/g, function(c) { - if (c === '\n') { - return '\\n'; - } else if (c === '\\') { - return '\\\\'; + values = Filter.values(type, Filter.menu.post); + res = values.map(function(value) { + var re; + re = type === 'uniqueID' || type === 'MD5' ? value : Filter.escape(value); + if (type === 'uniqueID' || type === 'MD5') { + return "/" + re + "/"; } else { - return "\\" + c; + return "/^" + re + "$/"; } - }); - re = type === 'uniqueID' || type === 'MD5' ? "/" + re + "/" : "/^" + re + "$/"; - return $.get(type, Conf[type], function(item) { - var save, section, select, ta, tl; - save = item[type]; - save = save ? save + "\n" + re : re; - $.set(type, save); - Settings.open('Filter'); - section = $('.section-container'); - select = $('select[name=filter]', section); - select.value = type; - Settings.selectFilter.call(select); - ta = $('textarea', section); - tl = ta.textLength; - ta.setSelectionRange(tl, tl); - return ta.focus(); + }).join('\n'); + return Filter.addFilter(type, res, function() { + return Filter.showFilters(type); }); } } @@ -6793,8 +9766,15 @@ PostHiding = (function() { cb: this.node }); }, + isHidden: function(boardID, threadID, postID) { + return !!(PostHiding.db && PostHiding.db.get({ + boardID: boardID, + threadID: threadID, + postID: postID + })); + }, node: function() { - var data, sideArrows; + var button, data, sa, sideArrows; if (!this.isReply || this.isClone || this.isFetchedQuote) { return; } @@ -6813,9 +9793,14 @@ PostHiding = (function() { if (!Conf['Reply Hiding Buttons']) { return; } - sideArrows = $('.sideArrows', this.nodes.root); - $.replace(sideArrows.firstChild, PostHiding.makeButton(this, 'hide')); - return sideArrows.removeAttribute('class'); + button = PostHiding.makeButton(this, 'hide'); + if ((sa = g.SITE.selectors.sideArrows)) { + sideArrows = $(sa, this.nodes.root); + $.replace(sideArrows.firstChild, button); + return sideArrows.className = 'replacedSideArrows'; + } else { + return $.prepend(this.nodes.info, button); + } }, menu: { init: function() { @@ -7086,7 +10071,7 @@ Recursive = (function() { indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; Recursive = { - recursives: {}, + recursives: $.dict(), init: function() { var ref; if ((ref = g.VIEW) !== 'index' && ref !== 'thread') { @@ -7169,6 +10154,10 @@ ThreadHiding = (function() { return this.catalogWatch(); } this.catalogSet(g.BOARD); + $.on(d, 'IndexRefreshInternal', this.onIndexRefresh); + if (Conf['Thread Hiding Buttons']) { + $.addClass(doc, 'thread-hide'); + } return Callbacks.Post.push({ name: 'Thread Hiding', cb: this.node @@ -7176,12 +10165,12 @@ ThreadHiding = (function() { }, catalogSet: function(board) { var hiddenThreads, threadID; - if (!$.hasStorage) { + if (!($.hasStorage && g.SITE.software === 'yotsuba')) { return; } hiddenThreads = ThreadHiding.db.get({ boardID: board.ID, - defaultValue: {} + defaultValue: $.dict() }); for (threadID in hiddenThreads) { hiddenThreads[threadID] = true; @@ -7189,7 +10178,7 @@ ThreadHiding = (function() { return localStorage.setItem("4chan-hide-t-" + board, JSON.stringify(hiddenThreads)); }, catalogWatch: function() { - if (!$.hasStorage) { + if (!($.hasStorage && g.SITE.software === 'yotsuba')) { return; } this.hiddenThreads = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; @@ -7205,7 +10194,7 @@ ThreadHiding = (function() { var hiddenThreads2, threadID; hiddenThreads2 = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; for (threadID in hiddenThreads2) { - if (!(threadID in ThreadHiding.hiddenThreads)) { + if (!$.hasOwn(ThreadHiding.hiddenThreads, threadID)) { ThreadHiding.db.set({ boardID: g.BOARD.ID, threadID: threadID, @@ -7216,7 +10205,7 @@ ThreadHiding = (function() { } } for (threadID in ThreadHiding.hiddenThreads) { - if (!(threadID in hiddenThreads2)) { + if (!$.hasOwn(hiddenThreads2, threadID)) { ThreadHiding.db["delete"]({ boardID: g.BOARD.ID, threadID: threadID @@ -7225,31 +10214,35 @@ ThreadHiding = (function() { } return ThreadHiding.hiddenThreads = hiddenThreads2; }, + isHidden: function(boardID, threadID) { + return !!(ThreadHiding.db && ThreadHiding.db.get({ + boardID: boardID, + threadID: threadID + })); + }, node: function() { var data; if (this.isReply || this.isClone || this.isFetchedQuote) { return; } + if (Conf['Thread Hiding Buttons']) { + $.prepend(this.nodes.root, ThreadHiding.makeButton(this.thread, 'hide')); + } if (data = ThreadHiding.db.get({ boardID: this.board.ID, threadID: this.ID })) { - ThreadHiding.hide(this.thread, data.makeStub); + return ThreadHiding.hide(this.thread, data.makeStub); } - if (!Conf['Thread Hiding Buttons']) { - return; - } - return $.prepend(this.nodes.root, ThreadHiding.makeButton(this.thread, 'hide')); }, - onIndexBuild: function(nodes) { - var i, len, root, thread; - for (i = 0, len = nodes.length; i < len; i++) { - root = nodes[i]; - thread = Get.threadFromRoot(root); + onIndexRefresh: function() { + return g.BOARD.threads.forEach(function(thread) { + var root; + root = thread.nodes.root; if (thread.isHidden && thread.stub && !root.contains(thread.stub)) { - ThreadHiding.makeStub(thread, root); + return ThreadHiding.makeStub(thread, root); } - } + }); }, menu: { init: function() { @@ -7354,17 +10347,15 @@ ThreadHiding = (function() { className: type + "-thread-button", href: 'javascript:;' }); - $.extend(a, { - innerHTML: "" - }); + $.extend(a, {innerHTML: ""}); a.dataset.fullID = thread.fullID; $.on(a, 'click', ThreadHiding.toggle); return a; }, makeStub: function(thread, root) { - var a, numReplies, summary; - numReplies = $$('.thread > .replyContainer', root).length; - if (summary = $('.summary', root)) { + var a, numReplies, summary, threadDivider; + numReplies = $$(g.SITE.selectors.replyOriginal, root).length; + if (summary = $(g.SITE.selectors.summary, root)) { numReplies += +summary.textContent.match(/\d+/); } a = ThreadHiding.makeButton(thread, 'show'); @@ -7377,7 +10368,10 @@ ThreadHiding = (function() { } else { $.add(thread.stub, a); } - return $.prepend(root, thread.stub); + $.prepend(root, thread.stub); + if ((threadDivider = $(g.SITE.selectors.threadDivider, root))) { + return $.addClass(threadDivider, 'threadDivider'); + } }, saveHiddenState: function(thread, makeStub) { if (thread.isHidden) { @@ -7398,7 +10392,7 @@ ThreadHiding = (function() { }, toggle: function(thread) { if (!(thread instanceof Thread)) { - thread = g.threads[this.dataset.fullID]; + thread = g.threads.get(this.dataset.fullID); } if (thread.isHidden) { ThreadHiding.show(thread); @@ -7415,10 +10409,12 @@ ThreadHiding = (function() { if (thread.isHidden) { return; } - threadRoot = thread.OP.nodes.root.parentNode; + threadRoot = thread.nodes.root; thread.isHidden = true; - if (Conf['JSON Index']) { - Index.updateHideLabel(); + Index.updateHideLabel(); + if (thread.catalogView && !Index.showHiddenThreads) { + $.rm(thread.catalogView.nodes.root); + $.event('PostsRemoved', null, Index.root); } if (!makeStub) { return threadRoot.hidden = true; @@ -7431,10 +10427,12 @@ ThreadHiding = (function() { $.rm(thread.stub); delete thread.stub; } - threadRoot = thread.OP.nodes.root.parentNode; + threadRoot = thread.nodes.root; threadRoot.hidden = thread.isHidden = false; - if (Conf['JSON Index']) { - return Index.updateHideLabel(); + Index.updateHideLabel(); + if (thread.catalogView && Index.showHiddenThreads) { + $.rm(thread.catalogView.nodes.root); + return $.event('PostsRemoved', null, Index.root); } } }; @@ -7443,375 +10441,178 @@ ThreadHiding = (function() { }).call(this); -Build = (function() { - var Build, - slice = [].slice; +BoardConfig = (function() { + var BoardConfig; - Build = { - staticPath: '//s.4cdn.org/image/', - gifIcon: window.devicePixelRatio >= 2 ? '@2x.gif' : '.gif', - spoilerRange: {}, - unescape: function(text) { - if (text == null) { - return text; - } - return text.replace(/<[^>]*>/g, '').replace(/&(amp|#039|quot|lt|gt|#44);/g, function(c) { - return { - '&': '&', - ''': "'", - '"': '"', - '<': '<', - '>': '>', - ',': ',' - }[c]; - }); - }, - shortFilename: function(filename) { - var ext; - ext = filename.match(/\.?[^\.]*$/)[0]; - if (filename.length - ext.length > 30) { - return (filename.match(/(?:[\uD800-\uDBFF][\uDC00-\uDFFF]|[^]){0,25}/)[0]) + "(...)" + ext; - } else { - return filename; - } - }, - spoilerThumb: function(boardID) { - var spoilerRange; - if (spoilerRange = Build.spoilerRange[boardID]) { - return Build.staticPath + "spoiler-" + boardID + (Math.floor(1 + spoilerRange * Math.random())) + ".png"; - } else { - return Build.staticPath + "spoiler.png"; + BoardConfig = { + cbs: [], + init: function() { + var boards, now, ref; + if (g.SITE.software !== 'yotsuba') { + return; } - }, - sameThread: function(boardID, threadID) { - return g.VIEW === 'thread' && g.BOARD.ID === boardID && g.THREADID === +threadID; - }, - postURL: function(boardID, threadID, postID) { - if (Build.sameThread(boardID, threadID)) { - return "#p" + postID; + now = Date.now(); + if (!((now - 2 * $.HOUR < (ref = Conf['boardConfig'].lastChecked || 0) && ref <= now))) { + return $.ajax(location.protocol + "//a.4cdn.org/boards.json", { + onloadend: this.load + }); } else { - return "/" + boardID + "/thread/" + threadID + "#p" + postID; + boards = Conf['boardConfig'].boards; + return this.set(boards); } }, - parseJSON: function(data, boardID) { - var o; - o = { - postID: data.no, - threadID: data.resto || data.no, - boardID: boardID, - isReply: !!data.resto, - isSticky: !!data.sticky, - isClosed: !!data.closed, - isArchived: !!data.archived, - fileDeleted: !!data.filedeleted - }; - o.info = { - subject: Build.unescape(data.sub), - email: Build.unescape(data.email), - name: Build.unescape(data.name) || '', - tripcode: data.trip, - uniqueID: data.id, - flagCode: data.country, - flag: Build.unescape(data.country_name), - dateUTC: data.time, - dateText: data.now, - commentHTML: { - innerHTML: data.com || '' + load: function() { + var board, boards, err, i, len, ref; + if (this.status === 200 && this.response && this.response.boards) { + boards = $.dict(); + ref = this.response.boards; + for (i = 0, len = ref.length; i < len; i++) { + board = ref[i]; + boards[board.board] = board; } - }; - if (data.capcode) { - o.info.capcode = data.capcode.replace(/_highlight$/, '').replace(/_/g, ' ').replace(/\b\w/g, function(c) { - return c.toUpperCase(); + $.set('boardConfig', { + boards: boards, + lastChecked: Date.now() }); - o.capcodeHighlight = /_highlight$/.test(data.capcode); - delete o.info.uniqueID; - } - if (data.ext) { - o.file = { - name: (Build.unescape(data.filename)) + data.ext, - url: boardID === 'f' ? location.protocol + "//i.4cdn.org/" + boardID + "/" + (encodeURIComponent(data.filename)) + data.ext : location.protocol + "//i.4cdn.org/" + boardID + "/" + data.tim + data.ext, - height: data.h, - width: data.w, - MD5: data.md5, - size: $.bytesToString(data.fsize), - thumbURL: location.protocol + "//i.4cdn.org/" + boardID + "/" + data.tim + "s.jpg", - theight: data.tn_h, - twidth: data.tn_w, - isSpoiler: !!data.spoiler, - tag: data.tag - }; - if (!/\.pdf$/.test(o.file.url)) { - o.file.dimensions = o.file.width + "x" + o.file.height; - } + } else { + boards = Conf['boardConfig'].boards; + err = (function() { + switch (this.status) { + case 0: + return 'Connection Error'; + case 200: + return 'Invalid Data'; + default: + return "Error " + this.statusText + " (" + this.status + ")"; + } + }).call(this); + new Notice('warning', "Failed to load board configuration. " + err, 20); } - return o; + return BoardConfig.set(boards); }, - parseComment: function(html) { - html = html.replace(//gi, '\n').replace(/\n\nRolled [^<]*<\/b>/i, '').replace(/]*>/g, ''); - return Build.unescape(html); - }, - postFromObject: function(data, boardID, suppressThumb) { - var o; - o = Build.parseJSON(data, boardID); - return Build.post(o, suppressThumb); - }, - post: function(o, suppressThumb) { - var boardID, capcode, capcodeDescription, capcodeLC, capcodeLong, capcodePlural, commentHTML, container, dateText, dateUTC, email, file, fileBlock, fileThumb, fileURL, flag, flagCode, gifIcon, href, i, len, match, name, postClass, postID, postInfo, postLink, protocol, quote, quoteLink, ref, ref1, shortFilename, staticPath, subject, threadID, tripcode, uniqueID, wholePost; - postID = o.postID, threadID = o.threadID, boardID = o.boardID, file = o.file; - ref = o.info, subject = ref.subject, email = ref.email, name = ref.name, tripcode = ref.tripcode, capcode = ref.capcode, uniqueID = ref.uniqueID, flagCode = ref.flagCode, flag = ref.flag, dateUTC = ref.dateUTC, dateText = ref.dateText, commentHTML = ref.commentHTML; - staticPath = Build.staticPath, gifIcon = Build.gifIcon; - - /* Post Info */ - if (capcode) { - capcodeLC = capcode.toLowerCase(); - if (capcode === 'Founder') { - capcodePlural = 'the Founder'; - capcodeDescription = "4chan's Founder"; - } else { - capcodeLong = { - 'Admin': 'Administrator', - 'Mod': 'Moderator' - }[capcode] || capcode; - capcodePlural = capcodeLong + "s"; - capcodeDescription = "a 4chan " + capcodeLong; - } - } - postLink = Build.postURL(boardID, threadID, postID); - quoteLink = Build.sameThread(boardID, threadID) ? "javascript:quote('" + (+postID) + "');" : "/" + boardID + "/thread/" + threadID + "#q" + postID; - postInfo = { - innerHTML: "
      " + ((!o.isReply || boardID === "f" || subject) ? "" + E(subject || "") + " " : "") + "" + ((email) ? "" : "") + "" + E(name) + "" + ((tripcode) ? " " + E(tripcode) + "" : "") + ((capcode) ? " ## " + E(capcode) + "" : "") + ((email) ? "" : "") + ((boardID === "f" && !o.isReply || capcode) ? "" : " ") + ((capcode) ? " \""" : "") + ((uniqueID && !capcode) ? " (ID: " + E(uniqueID) + ")" : "") + ((flagCode) ? " " : "") + " " + E(dateText) + " No." + E(postID) + "" + ((o.isSticky) ? " \"Sticky\"" : "") + ((o.isClosed && !o.isArchived) ? " \"Closed\"" : "") + ((o.isArchived) ? " \"Archived\"" : "") + ((!o.isReply && g.VIEW === "index") ? "   [Reply]" : "") + "
      " - }; - - /* File Info */ - if (file) { - protocol = /^https?:(?=\/\/i\.4cdn\.org\/)/; - fileURL = file.url.replace(protocol, ''); - shortFilename = Build.shortFilename(file.name); - fileThumb = file.isSpoiler ? Build.spoilerThumb(boardID) : file.thumbURL.replace(protocol, ''); + set: function(boards1) { + var ID, board, cb, i, len, ref, ref1; + this.boards = boards1; + ref = g.boards; + for (ID in ref) { + board = ref[ID]; + board.config = this.boards[ID] || {}; } - fileBlock = { - innerHTML: ((file) ? "
      " + ((boardID === "f") ? "
      File: " + E(file.name) + "-(" + E(file.size) + ", " + E(file.dimensions) + ((file.tag) ? ", " + E(file.tag) : "") + ")
      " : "
      File: " + ((file.isSpoiler) ? "Spoiler Image" : E(shortFilename)) + " (" + E(file.size) + ", " + E(file.dimensions || "PDF") + ")
      ") + "
      " : ((o.fileDeleted) ? "
      \"File
      " : "")) - }; - - /* Whole Post */ - postClass = o.isReply ? 'reply' : 'op'; - wholePost = { - innerHTML: ((o.isReply) ? "
      >>
      " : "") + "
      " + ((o.isReply) ? (postInfo).innerHTML + (fileBlock).innerHTML : (fileBlock).innerHTML + (postInfo).innerHTML) + "
      " + (commentHTML).innerHTML + "
      " - }; - container = $.el('div', { - className: "postContainer " + postClass + "Container", - id: "pc" + postID - }); - $.extend(container, wholePost); - ref1 = $$('.quotelink', container); + ref1 = this.cbs; for (i = 0, len = ref1.length; i < len; i++) { - quote = ref1[i]; - href = quote.getAttribute('href'); - if ((href[0] === '#') && !(Build.sameThread(boardID, threadID))) { - quote.href = ("/" + boardID + "/thread/" + threadID) + href; - } else if ((match = href.match(/^\/([^\/]+)\/thread\/(\d+)/)) && (Build.sameThread(match[1], match[2]))) { - quote.href = href.match(/(#[^#]*)?$/)[0] || '#'; - } else if (/^\d+(#|$)/.test(href) && !(g.VIEW === 'thread' && g.BOARD.ID === boardID)) { - quote.href = "/" + boardID + "/thread/" + href; - } + cb = ref1[i]; + $.queueTask(cb); } - return container; }, - summaryText: function(status, posts, files) { - var text; - text = ''; - if (status) { - text += status + " "; - } - text += posts + " post" + (posts > 1 ? 's' : ''); - if (+files) { - text += " and " + files + " image repl" + (files > 1 ? 'ies' : 'y'); + ready: function(cb) { + if (this.boards) { + return cb(); + } else { + return this.cbs.push(cb); } - return text += " " + (status === '-' ? 'shown' : 'omitted') + "."; - }, - summary: function(boardID, threadID, posts, files) { - return $.el('a', { - className: 'summary', - textContent: Build.summaryText('', posts, files), - href: "/" + boardID + "/thread/" + threadID - }); }, - thread: function(board, data) { - var OP, root; - Build.spoilerRange[board] = data.custom_spoiler; - if (OP = board.posts[data.no]) { - if (OP.isFetchedQuote) { - OP = null; + sfwBoards: function(sfw) { + var board, data, ref, results; + ref = this.boards || Conf['boardConfig'].boards; + results = []; + for (board in ref) { + data = ref[board]; + if (!!data.ws_board === sfw) { + results.push(board); } } - if (OP && (root = OP.nodes.root.parentNode)) { - $.rmAll(root); - } else { - root = $.el('div', { - className: 'thread', - id: "t" + data.no - }); - } - $.add(root, Build.excerptThread(board, data, OP)); - return root; + return results; }, - excerptThread: function(board, data, OP) { - var files, nodes, posts, ref; - nodes = [OP ? OP.nodes.root : Build.postFromObject(data, board.ID, true)]; - if (data.omitted_posts || !Conf['Show Replies'] && data.replies) { - ref = Conf['Show Replies'] ? [ - data.omitted_posts, data.images - data.last_replies.filter(function(data) { - return !!data.ext; - }).length - ] : [data.replies, data.images], posts = ref[0], files = ref[1]; - nodes.push(Build.summary(board.ID, data.no, posts, files)); - } - return nodes; + isSFW: function(board) { + var ref; + return !!((ref = (this.boards || Conf['boardConfig'].boards)[board]) != null ? ref.ws_board : void 0); }, - catalogThread: function(thread) { - var br, cc, comment, data, exif, fileCount, gifIcon, href, i, imgClass, j, k, l, len, len1, len2, len3, pageCount, postCount, pp, quote, ref, ref1, ref2, ref3, ref4, root, spoilerRange, src, staticPath; - staticPath = Build.staticPath, gifIcon = Build.gifIcon; - data = Index.liveThreadData[Index.liveThreadIDs.indexOf(thread.ID)]; - if (data.spoiler && !Conf['Reveal Spoiler Thumbnails']) { - src = staticPath + "spoiler"; - if (spoilerRange = Build.spoilerRange[thread.board]) { - src += ("-" + thread.board) + Math.floor(1 + spoilerRange * Math.random()); - } - src += '.png'; - imgClass = 'spoiler-file'; - } else if (data.filedeleted) { - src = staticPath + "filedeleted-res" + gifIcon; - imgClass = 'deleted-file'; - } else if (thread.OP.file) { - src = thread.OP.file.thumbURL; - } else { - src = staticPath + "nofile.png"; - imgClass = 'no-file'; - } - postCount = data.replies + 1; - fileCount = data.images + !!data.ext; - pageCount = Math.floor(Index.liveThreadIDs.indexOf(thread.ID) / Index.threadsNumPerPage) + 1; - comment = { - innerHTML: data.com || '' - }; - root = $.el('div', { - className: 'catalog-thread' - }); - $.extend(root, { - innerHTML: "
      " + E(postCount) + " / " + E(fileCount) + " / " + E(pageCount) + "
      " + ((thread.OP.info.subject) ? "
      " + E(thread.OP.info.subject) + "
      " : "") + "
      " + (comment).innerHTML + "
      " - }); - root.dataset.fullID = thread.fullID; - if (thread.OP.highlights) { - $.addClass.apply($, [root].concat(slice.call(thread.OP.highlights))); - } - ref = $$('.quotelink', root.lastElementChild); - for (i = 0, len = ref.length; i < len; i++) { - quote = ref[i]; - href = quote.getAttribute('href'); - if (href[0] === '#') { - quote.href = ("/" + thread.board + "/thread/" + thread.ID) + href; - } - } - ref1 = $$('.abbr, .exif', root.lastElementChild); - for (j = 0, len1 = ref1.length; j < len1; j++) { - exif = ref1[j]; - $.rm(exif); - } - ref2 = $$('.prettyprint', root.lastElementChild); - for (k = 0, len2 = ref2.length; k < len2; k++) { - pp = ref2[k]; - cc = $.el('span', { - className: 'catalog-code' - }); - $.add(cc, slice.call(pp.childNodes)); - $.replace(pp, cc); - } - ref3 = $$('br', root.lastElementChild); - for (l = 0, len3 = ref3.length; l < len3; l++) { - br = ref3[l]; - if (((ref4 = br.previousSibling) != null ? ref4.nodeName : void 0) === 'BR') { - $.rm(br); - } - } - if (thread.isSticky) { - $.add($('.catalog-icons', root), $.el('img', { - src: staticPath + "sticky" + gifIcon, - className: 'stickyIcon', - title: 'Sticky' - })); - } - if (thread.isClosed) { - $.add($('.catalog-icons', root), $.el('img', { - src: staticPath + "closed" + gifIcon, - className: 'closedIcon', - title: 'Closed' - })); - } - if (data.bumplimit) { - $.addClass($('.post-count', root), 'warning'); - } - if (data.imagelimit) { - $.addClass($('.file-count', root), 'warning'); + domain: function() { + return 'boards.4chan.org'; + }, + isArchived: function(board) { + var data; + data = (this.boards || Conf['boardConfig'].boards)[board]; + return !data || data.is_archived; + }, + noAudio: function(boardID) { + var boards; + if (g.SITE.software !== 'yotsuba') { + return false; } - return root; + boards = this.boards || Conf['boardConfig'].boards; + return boards && boards[boardID] && !boards[boardID].webm_audio; + }, + title: function(boardID) { + var ref, ref1; + return ((ref = this.boards || Conf['boardConfig'].boards) != null ? (ref1 = ref[boardID]) != null ? ref1.title : void 0 : void 0) || ''; } }; - return Build; - -}).call(this); - -(function() { - + return BoardConfig; }).call(this); Get = (function() { var Get, + slice = [].slice, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; Get = { + url: function() { + var IDs, args, f, site, type; + type = arguments[0], IDs = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; + if ((site = g.sites[IDs.siteID]) && (f = $.getOwn(site.urls, type))) { + return f.apply(null, [IDs].concat(slice.call(args))); + } else { + return void 0; + } + }, threadExcerpt: function(thread) { var OP, excerpt, ref, ref1; OP = thread.OP; - excerpt = ("/" + thread.board + "/ - ") + (((ref = OP.info.subject) != null ? ref.trim() : void 0) || OP.info.commentDisplay.replace(/\n+/g, ' // ') || ((ref1 = OP.file) != null ? ref1.name : void 0) || OP.info.nameBlock); + excerpt = ("/" + (decodeURIComponent(thread.board.ID)) + "/ - ") + (((ref = OP.info.subject) != null ? ref.trim() : void 0) || OP.commentDisplay().replace(/\n+/g, ' // ') || ((ref1 = OP.file) != null ? ref1.name : void 0) || ("No." + OP)); if (excerpt.length > 73) { return excerpt.slice(0, 70) + "..."; } return excerpt; }, threadFromRoot: function(root) { - return g.threads[g.BOARD + "." + root.id.slice(1)]; + var board; + if (root == null) { + return null; + } + board = root.dataset.board; + return g.threads.get((board ? encodeURIComponent(board) : g.BOARD.ID) + "." + (root.id.match(/\d*$/)[0])); }, threadFromNode: function(node) { - return Get.threadFromRoot($.x('ancestor::div[@class="thread"]', node)); + return Get.threadFromRoot($.x("ancestor-or-self::" + g.SITE.xpath.thread, node)); }, postFromRoot: function(root) { var index, post; if (root == null) { return null; } - post = g.posts[root.dataset.fullID]; + post = g.posts.get(root.dataset.fullID); index = root.dataset.clone; if (index) { - return post.clones[index]; + return post.clones[+index]; } else { return post; } }, postFromNode: function(root) { - return Get.postFromRoot($.x('(ancestor::div[contains(@class,"postContainer")][1]|following::div[contains(@class,"postContainer")][1])', root)); + return Get.postFromRoot($.x("ancestor-or-self::" + g.SITE.xpath.postContainer + "[1]", root)); }, postDataFromLink: function(link) { - var boardID, path, postID, ref, threadID; - if (link.hostname === 'boards.4chan.org') { - path = link.pathname.split(/\/+/); - boardID = path[1]; - threadID = path[3]; - postID = link.hash.slice(2); - } else { + var boardID, match, postID, ref, ref1, threadID; + if (link.dataset.postID) { ref = link.dataset, boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID; threadID || (threadID = 0); + } else { + match = link.href.match(g.SITE.regexp.quotelink); + ref1 = match.slice(1), boardID = ref1[0], threadID = ref1[1], postID = ref1[2]; + postID || (postID = threadID); } return { boardID: boardID, @@ -7842,7 +10643,7 @@ Get = (function() { ref = post.quotes; for (i = 0, len = ref.length; i < len; i++) { quote = ref[i]; - if (qPost = posts[quote]) { + if (qPost = posts.get(quote)) { handleQuotes(qPost, 'backlinks'); } } @@ -7852,17 +10653,6 @@ Get = (function() { ref1 = Get.postDataFromLink(quotelink), boardID = ref1.boardID, postID = ref1.postID; return boardID === post.board.ID && postID === post.ID; }); - }, - scriptData: function() { - var i, len, ref, script; - ref = $$('script:not([src])', d.head); - for (i = 0, len = ref.length; i < len; i++) { - script = ref[i]; - if (/\bcooldowns *=/.test(script.textContent)) { - return script.textContent; - } - } - return ''; } }; @@ -7871,18 +10661,28 @@ Get = (function() { }).call(this); Header = (function() { - var Header; + var Header, + slice = [].slice; Header = { init: function() { - var barFixedToggler, barPositionToggler, box, customNavToggler, editCustomNav, footerToggler, headerToggler, linkJustifyToggler, menuButton, scrollHeaderToggler, shortcutToggler; + var barFixedToggler, barPositionToggler, box, cs, customNavToggler, editCustomNav, footerToggler, headerToggler, linkJustifyToggler, menuButton, scrollHeaderToggler, shortcutToggler; + $.onExists(doc, 'body', (function(_this) { + return function() { + if (!Main.isThisPageLegit()) { + return; + } + $.add(_this.bar, [_this.noticesRoot, _this.toggle]); + $.prepend(d.body, _this.bar); + $.add(d.body, Header.hover); + return _this.setBarPosition(Conf['Bottom Header']); + }; + })(this)); this.menu = new UI.Menu('header'); menuButton = $.el('span', { className: 'menu-button' }); - $.extend(menuButton, { - innerHTML: "" - }); + $.extend(menuButton, {innerHTML: ""}); box = UI.checkbox; barFixedToggler = box('Fixed Header', 'Fixed Header'); headerToggler = box('Header auto-hide', 'Auto-hide header'); @@ -7955,65 +10755,52 @@ Header = (function() { } ] }); - $.on(window, 'load popstate', Header.hashScroll); - $.on(d, 'CreateNotification', this.createNotification); - $.asap((function() { - return d.body; - }), (function(_this) { - return function() { - if (!Main.isThisPageLegit()) { + $.on(window, 'load popstate', Header.hashScroll); + $.on(d, 'CreateNotification', this.createNotification); + this.setBoardList(); + $.onExists(doc, g.SITE.selectors.boardList + " + *", Header.generateFullBoardList); + Main.ready(function() { + var a, absbot, footer, i, len, ref; + if (g.SITE.software === 'yotsuba' && !(footer = $.id('boardNavDesktopFoot'))) { + if (!(absbot = $.id('absbot'))) { return; } - $.asap((function() { - return $.id('boardNavMobile') || d.readyState !== 'loading'; - }), function() { - var a, footer; - footer = $.id('boardNavDesktop').cloneNode(true); - footer.id = 'boardNavDesktopFoot'; - $('#navtopright', footer).id = 'navbotright'; - $('#settingsWindowLink', footer).id = 'settingsWindowLinkBot'; - Header.bottomBoardList = $('.boardList', footer); - if (a = $("a[href*='/" + g.BOARD + "/']", footer)) { - a.className = 'current'; - } - Main.ready(function() { - var absbot, oldFooter; - if ((oldFooter = $.id('boardNavDesktopFoot'))) { - return $.replace($('.boardList', oldFooter), Header.bottomBoardList); - } else if ((absbot = $.id('absbot'))) { - $.before(absbot, footer); - return $.globalEval('window.cloneTopNav = function() {};'); - } - }); - return Header.setBoardList(); + footer = $.id('boardNavDesktop').cloneNode(true); + footer.id = 'boardNavDesktopFoot'; + $('#navtopright', footer).id = 'navbotright'; + $('#settingsWindowLink', footer).id = 'settingsWindowLinkBot'; + $.before(absbot, footer); + $.global(function() { + return window.cloneTopNav = function() {}; }); - $.prepend(d.body, _this.bar); - $.add(d.body, Header.hover); - _this.setBarPosition(Conf['Bottom Header']); - return _this; - }; - })(this)); - Main.ready((function(_this) { - return function() { - var cs; - if (g.VIEW === 'catalog' || !Conf['Disable Native Extension']) { - cs = $.el('a', { - href: 'javascript:;' - }); - if (g.VIEW === 'catalog') { - cs.title = cs.textContent = 'Catalog Settings'; - cs.className = 'fa fa-book'; - } else { - cs.title = cs.textContent = '4chan Settings'; - cs.className = 'native-settings'; + } + if ((Header.bottomBoardList = $(g.SITE.selectors.boardListBottom))) { + ref = $$('a', Header.bottomBoardList); + for (i = 0, len = ref.length; i < len; i++) { + a = ref[i]; + if (a.hostname === location.hostname && a.pathname.split('/')[1] === g.BOARD.ID) { + a.className = 'current'; } - $.on(cs, 'click', function() { - return $.id('settingsWindowLink').click(); - }); - return _this.addShortcut('native', cs, 810); } - }; - })(this)); + return CatalogLinks.setLinks(Header.bottomBoardList); + } + }); + if (g.SITE.software === 'yotsuba' && (g.VIEW === 'catalog' || !Conf['Disable Native Extension'])) { + cs = $.el('a', { + href: 'javascript:;' + }); + if (g.VIEW === 'catalog') { + cs.title = cs.textContent = 'Catalog Settings'; + cs.className = 'fa fa-book'; + } else { + cs.title = cs.textContent = '4chan Settings'; + cs.className = 'native-settings'; + } + $.on(cs, 'click', function() { + return $.id('settingsWindowLink').click(); + }); + this.addShortcut('native', cs, 810); + } return this.enableDesktopNotifications(); }, bar: $.el('div', { @@ -8032,84 +10819,61 @@ Header = (function() { id: 'scroll-marker' }), setBoardList: function() { - var a, boardList, btn, chr, i, j, len, len1, node, nodes, ref, ref1, spacer, span; + var boardList, btn; Header.boardList = boardList = $.el('span', { id: 'board-list' }); - $.extend(boardList, { - innerHTML: "" - }); + $.extend(boardList, {innerHTML: ""}); btn = $('.hide-board-list-button', boardList); $.on(btn, 'click', Header.toggleBoardList); - nodes = []; - spacer = function() { - return $.el('span', { - className: 'spacer' - }); - }; - ref = $('#boardNavDesktop > .boardList').childNodes; - for (i = 0, len = ref.length; i < len; i++) { - node = ref[i]; - switch (node.nodeName) { - case '#text': - ref1 = node.nodeValue; - for (j = 0, len1 = ref1.length; j < len1; j++) { - chr = ref1[j]; - span = $.el('span', { - textContent: chr - }); - if (chr === ' ') { - span.className = 'space'; - } - if (chr === ']') { - nodes.push(spacer()); - } - nodes.push(span); - if (chr === '[') { - nodes.push(spacer()); - } - } - break; - case 'A': - a = node.cloneNode(true); - if (a.pathname.split('/')[1] === g.BOARD.ID) { - a.className = 'current'; - } - nodes.push(a); - } - } - $.add($('.boardList', boardList), nodes); - $.add(Header.bar, [Header.boardList, Header.shortcuts, Header.noticesRoot, Header.toggle]); + $.prepend(Header.bar, [Header.boardList, Header.shortcuts]); Header.setCustomNav(Conf['Custom Board Navigation']); Header.generateBoardList(Conf['boardnav']); $.sync('Custom Board Navigation', Header.setCustomNav); return $.sync('boardnav', Header.generateBoardList); }, + generateFullBoardList: function() { + var a, fullBoardList, i, len, nodes, ref; + if (g.SITE.transformBoardList) { + nodes = g.SITE.transformBoardList(); + } else { + nodes = slice.call($(g.SITE.selectors.boardList).cloneNode(true).childNodes); + } + fullBoardList = $('.boardList', Header.boardList); + $.add(fullBoardList, nodes); + ref = $$('a', fullBoardList); + for (i = 0, len = ref.length; i < len; i++) { + a = ref[i]; + if (a.hostname === location.hostname && a.pathname.split('/')[1] === g.BOARD.ID) { + a.className = 'current'; + } + } + return CatalogLinks.setLinks(fullBoardList); + }, generateBoardList: function(boardnav) { - var as, list, nodes, re, t; + var list, nodes, re, t; list = $('#custom-board-list', Header.boardList); $.rmAll(list); if (!boardnav) { return; } boardnav = boardnav.replace(/(\r\n|\n|\r)/g, ' '); - as = $$('#full-board-list a[title]', Header.boardList); - re = /[\w@]+(-(all|title|replace|full|index|catalog|archive|expired|(mode|sort|text):"[^"]+"(,"[^"]+")?))*|[^\w@]+/g; + re = /[\w@]+(-(all|title|replace|full|index|catalog|archive|expired|nt|(mode|sort|text):"[^"]+"(,"[^"]+")?))*|[^\w@]+/g; nodes = (function() { var i, len, ref, results; ref = boardnav.match(re); results = []; for (i = 0, len = ref.length; i < len; i++) { t = ref[i]; - results.push(Header.mapCustomNavigation(t, as)); + results.push(Header.mapCustomNavigation(t)); } return results; })(); $.add(list, nodes); - return $.ready(CatalogLinks.initBoardList); + return CatalogLinks.setLinks(list); }, - mapCustomNavigation: function(t, as) { - var a, boardID, href, indexOptions, m, text, url; + mapCustomNavigation: function(t) { + var a, boardID, href, indexOptions, m, ref, ref1, text, url, urlIC; if (/^[^\w@]/.test(t)) { return $.tn(t); } @@ -8140,14 +10904,39 @@ Header = (function() { textContent: text || '+', className: 'external' }); + if (/-nt/.test(t)) { + a.target = '_blank'; + a.rel = 'noopener'; + } return a; } boardID = t.split('-')[0]; if (boardID === 'current') { - boardID = g.BOARD.ID; + if ((ref = location.hostname) === 'boards.4chan.org' || ref === 'boards.4channel.org') { + boardID = g.BOARD.ID; + } else { + a = $.el('a', { + href: "/" + g.BOARD.ID + "/", + textContent: text || decodeURIComponent(g.BOARD.ID), + className: 'current' + }); + if (/-nt/.test(t)) { + a.target = '_blank'; + a.rel = 'noopener'; + } + if (/-index/.test(t)) { + a.dataset.only = 'index'; + } else if (/-catalog/.test(t)) { + a.dataset.only = 'catalog'; + a.href += 'catalog.html'; + } else if (/-(archive|expired)/.test(t)) { + a = a.firstChild; + } + return a; + } } a = (function() { - var i, len, ref; + var ref1, urlV; if (boardID === '@') { return $.el('a', { href: 'https://twitter.com/4chan', @@ -8155,29 +10944,31 @@ Header = (function() { textContent: '@' }); } - for (i = 0, len = as.length; i < len; i++) { - a = as[i]; - if (a.textContent === boardID) { - return a.cloneNode(true); - } - } a = $.el('a', { - href: "/" + boardID + "/", - textContent: boardID + href: "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/", + textContent: boardID, + title: BoardConfig.title(boardID) }); - if ((ref = g.VIEW) === 'catalog' || ref === 'archive') { - a.href += g.VIEW; + if (((ref1 = g.VIEW) === 'catalog' || ref1 === 'archive') && (urlV = Get.url(g.VIEW, { + siteID: '4chan.org', + boardID: boardID + }))) { + a.href = urlV; } - if (boardID === g.BOARD.ID) { + if (a.hostname === location.hostname && boardID === g.BOARD.ID) { a.className = 'current'; } return a; })(); - a.textContent = /-title/.test(t) || /-replace/.test(t) && boardID === g.BOARD.ID ? a.title || a.textContent : /-full/.test(t) ? ("/" + boardID + "/") + (a.title ? " - " + a.title : '') : text || boardID; + a.textContent = /-title/.test(t) || /-replace/.test(t) && a.hostname === location.hostname && boardID === g.BOARD.ID ? a.title || a.textContent : /-full/.test(t) ? ("/" + boardID + "/") + (a.title ? " - " + a.title : '') : text || boardID; if (m = t.match(/-(index|catalog)/)) { - if (!(boardID === 'f' && m[1] === 'catalog')) { + urlIC = CatalogLinks[m[1]]({ + siteID: '4chan.org', + boardID: boardID + }); + if (urlIC) { a.dataset.only = m[1]; - a.href = CatalogLinks[m[1]](boardID); + a.href = urlIC; if (m[1] === 'catalog') { $.addClass(a, 'catalog'); } @@ -8187,7 +10978,7 @@ Header = (function() { } if (Conf['JSON Index'] && indexOptions) { a.dataset.indexOptions = indexOptions; - if (a.hostname === 'boards.4chan.org' && a.pathname.split('/')[2] === '') { + if (((ref1 = a.hostname) === 'boards.4chan.org' || ref1 === 'boards.4channel.org') && a.pathname.split('/')[2] === '') { a.href += (a.hash ? '/' : '#') + indexOptions; } } @@ -8201,12 +10992,16 @@ Header = (function() { } } if (/-expired/.test(t)) { - if (boardID !== 'b' && boardID !== 'f' && boardID !== 'trash') { - a.href = "/" + boardID + "/archive"; + if (BoardConfig.isArchived(boardID)) { + a.href = "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/archive"; } else { return a.firstChild; } } + if (/-nt/.test(t)) { + a.target = '_blank'; + a.rel = 'noopener'; + } if (boardID === '@') { $.addClass(a, 'navSmall'); } @@ -8289,9 +11084,7 @@ Header = (function() { } $.off(window, 'scroll', Header.hideBarOnScroll); $.rmClass(Header.bar, 'scroll'); - if (!Conf['Header auto-hide']) { - return $.rmClass(Header.bar, 'autohide'); - } + return Header.bar.classList.toggle('autohide', Conf['Header auto-hide']); }, toggleHideBarOnScroll: function() { var hide; @@ -8310,8 +11103,10 @@ Header = (function() { return Header.previousOffset = offsetY; }, setBarPosition: function(bottom) { - var args; - Header.barPositionToggler.checked = bottom; + var args, ref; + if ((ref = Header.barPositionToggler) != null) { + ref.checked = bottom; + } $.event('CloseMenu'); args = bottom ? ['bottom-header', 'top-header', 'after'] : ['top-header', 'bottom-header', 'add']; $.addClass(doc, args[0]); @@ -8495,9 +11290,7 @@ Header = (function() { case 'denied': return; } - el = $.el('span', { - innerHTML: "4chan X needs your permission to show desktop notifications. [FAQ]
      or " - }); + el = $.el('span', {innerHTML: "4chan X needs your permission to show desktop notifications. [FAQ]
      or "}); ref = $$('button', el), authorize = ref[0], disable = ref[1]; $.on(authorize, 'click', function() { return Notification.requestPermission(function(status) { @@ -8528,11 +11321,26 @@ Index = (function() { Index = { showHiddenThreads: false, changed: {}, + enabledOn: function(arg) { + var boardID, siteID; + siteID = arg.siteID, boardID = arg.boardID; + return Conf['JSON Index'] && g.sites[siteID].software === 'yotsuba' && boardID !== 'f'; + }, init: function() { - var anchorEntry, input, j, k, label, len, len1, name, pinEntry, ref, ref1, ref2, ref3, ref4, ref5, ref6, refNavEntry, repliesEntry, select, sortEntry; - if (g.BOARD.ID === 'f' || !Conf['JSON Index'] || g.VIEW !== 'index') { + var arr, entries, i, input, inputs, k, l, label, len1, len2, name, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, select, sortEntry, tRaw, watchSettings; + if (g.VIEW !== 'index') { return; } + $.one(d, '4chanXInitFinished', this.cb.initFinished); + $.on(d, 'PostsInserted', this.cb.postsInserted); + if (!this.enabledOn(g.BOARD)) { + return; + } + this.enabled = true; + Callbacks.Post.push({ + name: 'Index Page Numbers', + cb: this.node + }); Callbacks.CatalogThread.push({ name: 'Catalog Features', cb: this.catalogNode @@ -8547,7 +11355,8 @@ Index = (function() { this.processHash(); $.addClass(doc, 'index-loading', (Conf['Index Mode'].replace(/\ /g, '-')) + "-mode"); $.on(window, 'popstate', this.cb.popstate); - $.on(d, 'scroll', Index.scroll); + $.on(d, 'scroll', this.scroll); + $.on(d, 'SortIndex', this.cb.resort); this.button = $.el('a', { className: 'fa fa-refresh', title: 'Refresh', @@ -8558,56 +11367,55 @@ Index = (function() { return Index.update(); }); Header.addShortcut('index-refresh', this.button, 590); - repliesEntry = { - el: UI.checkbox('Show Replies', 'Show replies') - }; - sortEntry = { - el: UI.checkbox('Per-Board Sort Type', 'Per-board sort type', typeof Conf['Index Sort'] === 'object') - }; - pinEntry = { - el: UI.checkbox('Pin Watched Threads', 'Pin watched threads') - }; - anchorEntry = { - el: UI.checkbox('Anchor Hidden Threads', 'Anchor hidden threads') - }; - refNavEntry = { - el: UI.checkbox('Refreshed Navigation', 'Refreshed navigation') - }; - sortEntry.el.title = 'Set the sorting order of each board independently.'; - pinEntry.el.title = 'Move watched threads to the start of the index.'; - anchorEntry.el.title = 'Move hidden threads to the end of the index.'; - refNavEntry.el.title = 'Refresh index when navigating through pages.'; - ref4 = [repliesEntry, pinEntry, anchorEntry, refNavEntry]; - for (j = 0, len = ref4.length; j < len; j++) { - label = ref4[j]; - input = label.el.firstChild; - name = input.name; - $.on(input, 'change', $.cb.checked); - switch (name) { - case 'Show Replies': - $.on(input, 'change', this.cb.replies); - break; - case 'Pin Watched Threads': - case 'Anchor Hidden Threads': - $.on(input, 'change', this.cb.resort); + entries = []; + this.inputs = inputs = $.dict(); + ref4 = Config.Index; + for (name in ref4) { + arr = ref4[name]; + if (!(arr instanceof Array)) { + continue; } + label = UI.checkbox(name, "" + name[0] + (name.slice(1).toLowerCase())); + label.title = arr[1]; + entries.push({ + el: label + }); + input = label.firstChild; + $.on(input, 'change', $.cb.checked); + inputs[name] = input; } - $.on(sortEntry.el.firstChild, 'change', this.cb.perBoardSort); + $.on(inputs['Show Replies'], 'change', this.cb.replies); + $.on(inputs['Catalog Hover Expand'], 'change', this.cb.hover); + $.on(inputs['Pin Watched Threads'], 'change', this.cb.resort); + $.on(inputs['Anchor Hidden Threads'], 'change', this.cb.resort); + watchSettings = function(e) { + if ((input = $.getOwn(inputs, e.target.name))) { + input.checked = e.target.checked; + return $.event('change', null, input); + } + }; + $.on(d, 'OpenSettings', function() { + return $.on($.id('fourchanx-settings'), 'change', watchSettings); + }); + sortEntry = UI.checkbox('Per-Board Sort Type', 'Per-board sort type', typeof Conf['Index Sort'] === 'object'); + sortEntry.title = 'Set the sorting order of each board independently.'; + $.on(sortEntry.firstChild, 'change', this.cb.perBoardSort); + entries.splice(3, 0, { + el: sortEntry + }); Header.menu.addEntry({ el: $.el('span', { textContent: 'Index Navigation' }), order: 100, - subEntries: [repliesEntry, sortEntry, pinEntry, anchorEntry, refNavEntry] + subEntries: entries }); this.navLinks = $.el('div', { className: 'navLinks json-index' }); - $.extend(this.navLinks, { - innerHTML: "Index Catalog Archive Bottom ×" - }); + $.extend(this.navLinks, {innerHTML: "Index Catalog Archive Bottom ×"}); $('.cataloglink a', this.navLinks).href = CatalogLinks.catalog(); - if ((ref5 = g.BOARD.ID) === 'b' || ref5 === 'trash') { + if (!BoardConfig.isArchived(g.BOARD.ID)) { $('.archlistlink', this.navLinks).hidden = true; } $.on($('#index-last-refresh a', this.navLinks), 'click', this.cb.refreshFront); @@ -8617,29 +11425,43 @@ Index = (function() { $.on($('#index-search-clear', this.navLinks), 'click', this.clearSearch); this.hideLabel = $('#hidden-label', this.navLinks); $.on($('#hidden-toggle a', this.navLinks), 'click', this.cb.toggleHiddenThreads); + this.selectRev = $('#index-rev', this.navLinks); this.selectMode = $('#index-mode', this.navLinks); this.selectSort = $('#index-sort', this.navLinks); this.selectSize = $('#index-size', this.navLinks); + $.on(this.selectRev, 'change', this.cb.sort); $.on(this.selectMode, 'change', this.cb.mode); $.on(this.selectSort, 'change', this.cb.sort); $.on(this.selectSize, 'change', $.cb.value); $.on(this.selectSize, 'change', this.cb.size); - ref6 = [this.selectMode, this.selectSize]; - for (k = 0, len1 = ref6.length; k < len1; k++) { - select = ref6[k]; + ref5 = [this.selectMode, this.selectSize]; + for (k = 0, len1 = ref5.length; k < len1; k++) { + select = ref5[k]; select.value = Conf[select.name]; } - this.selectSort.value = Index.currentSort; + this.selectRev.checked = /-rev$/.test(Index.currentSort); + this.selectSort.value = Index.currentSort.replace(/-rev$/, ''); + this.lastLongOptions = $('#lastlong-options', this.navLinks); + this.lastLongInputs = $$('input', this.lastLongOptions); + this.lastLongThresholds = [0, 0]; + this.lastLongOptions.hidden = this.selectSort.value !== 'lastlong'; + ref6 = this.lastLongInputs; + for (i = l = 0, len2 = ref6.length; l < len2; i = ++l) { + input = ref6[i]; + $.on(input, 'change', this.cb.lastLongThresholds); + tRaw = Conf["Last Long Reply Thresholds " + i]; + input.value = this.lastLongThresholds[i] = typeof tRaw === 'object' ? (ref7 = tRaw[g.BOARD.ID]) != null ? ref7 : 100 : tRaw; + } this.root = $.el('div', { className: 'board json-index' }); + $.on(this.root, 'click', this.cb.hoverToggle); this.cb.size(); + this.cb.hover(); this.pagelist = $.el('div', { className: 'pagelist json-index' }); - $.extend(this.pagelist, { - innerHTML: "
      " - }); + $.extend(this.pagelist, {innerHTML: "
      "}); $('.cataloglink a', this.pagelist).href = CatalogLinks.catalog(); $.on(this.pagelist, 'click', this.cb.pageNav); this.update(true); @@ -8647,25 +11469,25 @@ Index = (function() { return d.title = d.title.replace(/\ -\ Page\ \d+/, ''); }); $.onExists(doc, '.board > .thread > .postContainer, .board + *', function() { - var board, el, l, len2, len3, m, ref7, ref8, threadRoot, topNavPos; - Index.hat = $('.board > .thread > img:first-child'); - if (Index.hat) { - if (Index.nodes) { - ref7 = Index.nodes; - for (l = 0, len2 = ref7.length; l < len2; l++) { - threadRoot = ref7[l]; - $.prepend(threadRoot, Index.hat.cloneNode(false)); + var board, el, len3, m, ref8, timeEl, topNavPos; + g.SITE.Build.hat = $('.board > .thread > img:first-child'); + if (g.SITE.Build.hat) { + g.BOARD.threads.forEach(function(thread) { + if (thread.nodes.root) { + return $.prepend(thread.nodes.root, g.SITE.Build.hat.cloneNode(false)); } - } + }); $.addClass(doc, 'hats-enabled'); - $.addStyle(".catalog-thread::after {background-image: url(" + Index.hat.src + ");}"); + $.addStyle(".catalog-thread::after {background-image: url(" + g.SITE.Build.hat.src + ");}"); } board = $('.board'); $.replace(board, Index.root); - $.event('PostsInserted'); + if (Index.loaded) { + $.event('PostsInserted', null, Index.root); + } try { d.implementation.createDocument(null, null, null).appendChild(board); - } catch (_error) {} + } catch (error) {} ref8 = $$('.navLinks'); for (m = 0, len3 = ref8.length; m < len3; m++) { el = ref8[m]; @@ -8674,7 +11496,11 @@ Index = (function() { $.rm($.id('ctrl-top')); topNavPos = $.id('delform').previousElementSibling; $.before(topNavPos, $.el('hr')); - return $.before(topNavPos, Index.navLinks); + $.before(topNavPos, Index.navLinks); + timeEl = $('#index-last-refresh time', Index.navLinks); + if (timeEl.dataset.utc) { + return RelativeDates.update(timeEl); + } }); return Main.ready(function() { var pagelist; @@ -8685,7 +11511,7 @@ Index = (function() { }); }, scroll: function() { - var nodes, pageNum; + var pageNum, threadIDs; if (Index.req || !Index.liveThreadData || Conf['Index Mode'] !== 'infinite' || (window.scrollY <= doc.scrollHeight - (300 + window.innerHeight))) { return; } @@ -8696,11 +11522,8 @@ Index = (function() { if (pageNum > Index.pagesNum) { return Index.endNotice(); } - nodes = Index.buildSinglePage(pageNum); - if (Conf['Show Replies']) { - Index.buildReplies(nodes); - } - return Index.buildStructure(nodes); + threadIDs = Index.threadsOnPage(pageNum); + return Index.buildStructure(threadIDs); }, endNotice: (function() { var notify, reset; @@ -8719,16 +11542,14 @@ Index = (function() { })(), menu: { init: function() { - if (g.VIEW !== 'index' || !Conf['JSON Index'] || !Conf['Menu'] || !Conf['Thread Hiding Link'] || g.BOARD.ID === 'f') { + if (!(g.VIEW === 'index' && Conf['Menu'] && Conf['Thread Hiding Link'] && Index.enabledOn(g.BOARD))) { return; } return Menu.menu.addEntry({ el: $.el('a', { href: 'javascript:;', className: 'has-shortcut-text' - }, { - innerHTML: "Shift+click" - }), + }, {innerHTML: "Shift+click"}), order: 20, open: function(arg) { var thread; @@ -8750,24 +11571,26 @@ Index = (function() { }); } }, - catalogNode: function() { - return $.on(this.nodes.thumb.parentNode, 'click', Index.onClick); - }, - onClick: function(e) { - var thread; - if (e.button !== 0) { - return; - } - thread = g.threads[this.parentNode.dataset.fullID]; - if (e.shiftKey) { - Index.toggleHide(thread); - } else { + node: function() { + if (this.isReply || this.isClone || !(Index.threadPosition[this.ID] != null)) { return; } - return e.preventDefault(); + return this.thread.setPage(Math.floor(Index.threadPosition[this.ID] / Index.threadsNumPerPage) + 1); + }, + catalogNode: function() { + return $.on(this.nodes.root, 'mousedown click', (function(_this) { + return function(e) { + if (!(e.button === 0 && e.shiftKey)) { + return; + } + if (e.type === 'click') { + Index.toggleHide(_this.thread); + } + return e.preventDefault(); + }; + })(this)); }, toggleHide: function(thread) { - $.rm(thread.catalogView.nodes.root); if (Index.showHiddenThreads) { ThreadHiding.show(thread); if (!ThreadHiding.db.get({ @@ -8782,11 +11605,11 @@ Index = (function() { return ThreadHiding.saveHiddenState(thread); }, cycleSortType: function() { - var i, j, len, type, types; + var i, k, len1, type, types; types = slice.call(Index.selectSort.options).filter(function(option) { return !option.disabled; }); - for (i = j = 0, len = types.length; j < len; i = ++j) { + for (i = k = 0, len1 = types.length; k < len1; i = ++k) { type = types[i]; if (type.selected) { break; @@ -8796,6 +11619,28 @@ Index = (function() { return $.event('change', null, Index.selectSort); }, cb: { + initFinished: function() { + Index.initFinishedFired = true; + return $.queueTask(function() { + return Index.cb.postsInserted(); + }); + }, + postsInserted: function() { + var n; + if (!Index.initFinishedFired) { + return; + } + n = 0; + g.posts.forEach(function(post) { + if (!post.isFetchedQuote && !post.indexRefreshSeen && doc.contains(post.nodes.root)) { + post.indexRefreshSeen = true; + return n++; + } + }); + if (n) { + return $.event('IndexRefresh'); + } + }, toggleHiddenThreads: function() { $('#hidden-toggle a', Index.navLinks).textContent = (Index.showHiddenThreads = !Index.showHiddenThreads) ? 'Hide' : 'Show'; Index.sort(); @@ -8808,18 +11653,41 @@ Index = (function() { return Index.pageLoad(false); }, sort: function() { + var value; + value = Index.selectRev.checked ? Index.selectSort.value + "-rev" : Index.selectSort.value; Index.pushState({ - sort: this.value + sort: value }); return Index.pageLoad(false); }, - resort: function() { - Index.sort(); - return Index.buildIndex(); + resort: function(e) { + var ref; + Index.changed.order = true; + if (!(e != null ? (ref = e.detail) != null ? ref.deferred : void 0 : void 0)) { + return Index.pageLoad(false); + } }, perBoardSort: function() { - Conf['Index Sort'] = this.checked ? {} : ''; - return Index.saveSort(); + var i, k; + Conf['Index Sort'] = this.checked ? $.dict() : ''; + Index.saveSort(); + for (i = k = 0; k < 2; i = ++k) { + Conf["Last Long Reply Thresholds " + i] = this.checked ? $.dict() : ''; + Index.saveLastLongThresholds(i); + } + }, + lastLongThresholds: function() { + var i, value; + i = slice.call(this.parentNode.children).indexOf(this); + value = +this.value; + if (!Number.isFinite(value)) { + this.value = Index.lastLongThresholds[i]; + return; + } + Index.lastLongThresholds[i] = value; + Index.saveLastLongThresholds(i); + Index.changed.order = true; + return Index.pageLoad(false); }, size: function(e) { if (Conf['Index Mode'] !== 'catalog') { @@ -8837,10 +11705,23 @@ Index = (function() { } }, replies: function() { - Index.buildThreads(); - Index.sort(); return Index.buildIndex(); }, + hover: function() { + return doc.classList.toggle('catalog-hover-expand', Conf['Catalog Hover Expand']); + }, + hoverToggle: function(e) { + var input, thread; + if (Conf['Catalog Hover Toggle'] && $.hasClass(doc, 'catalog-mode') && !$.modifiedClick(e) && !$.x('ancestor-or-self::a', e.target)) { + input = Index.inputs['Catalog Hover Expand']; + input.checked = !input.checked; + $.event('change', null, input); + if ((thread = Get.threadFromNode(e.target))) { + Index.cb.catalogReplies.call(thread); + return Index.cb.hoverAdjust.call(thread.OP.nodes); + } + } + }, popstate: function(e) { var mode, nCommands, page, ref, searched, sort; if (e != null ? e.state : void 0) { @@ -8864,7 +11745,7 @@ Index = (function() { }, pageNav: function(e) { var a; - if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { + if ($.modifiedClick(e)) { return; } switch (e.target.nodeName) { @@ -8889,6 +11770,26 @@ Index = (function() { page: 1 }); return Index.update(); + }, + catalogReplies: function() { + if (Conf['Show Replies'] && $.hasClass(doc, 'catalog-hover-expand') && !this.catalogView.nodes.replies) { + return Index.buildCatalogReplies(this); + } + }, + hoverAdjust: function() { + var rect, style, x; + if (!$.hasClass(doc, 'catalog-hover-expand')) { + return; + } + rect = this.post.getBoundingClientRect(); + if ((x = $.minmax(0, -rect.left, doc.clientWidth - rect.right))) { + style = this.post.style; + style.left = x + "px"; + style.right = (-x) + "px"; + return $.one(this.root, 'mouseleave', function() { + return style.left = style.right = null; + }); + } } }, scrollToIndex: function() { @@ -8922,26 +11823,30 @@ Index = (function() { 'last-long-reply': 'lastlong', 'creation-date': 'birth', 'reply-count': 'replycount', - 'file-count': 'filecount' + 'file-count': 'filecount', + 'posts-per-minute': 'activity' } }, processHash: function() { - var command, commands, hash, j, leftover, len, mode, ref, sort, state; + var command, commands, hash, k, leftover, len1, mode, ref, sort, state; hash = ((ref = location.href.match(/#.*/)) != null ? ref[0] : void 0) || ''; state = { replace: true }; commands = hash.slice(1).split('/'); leftover = []; - for (j = 0, len = commands.length; j < len; j++) { - command = commands[j]; - if ((mode = Index.hashCommands.mode[command])) { + for (k = 0, len1 = commands.length; k < len1; k++) { + command = commands[k]; + if ((mode = $.getOwn(Index.hashCommands.mode, command))) { state.mode = mode; } else if (command === 'index') { state.mode = Conf['Previous Index Mode']; state.page = 1; - } else if ((sort = Index.hashCommands.sort[command])) { + } else if ((sort = $.getOwn(Index.hashCommands.sort, command.replace(/-rev$/, '')))) { state.sort = sort; + if (/-rev$/.test(command)) { + state.sort += '-rev'; + } } else if (/^s=/.test(command)) { state.search = decodeURIComponent(command.slice(2)).replace(/\+/g, ' ').trim(); } else { @@ -9009,27 +11914,35 @@ Index = (function() { return Index.changed.hash = true; } }, - saveSort: function() { - if (typeof Conf['Index Sort'] === 'object') { - Conf['Index Sort'][g.BOARD.ID] = Index.currentSort; + savePerBoard: function(key, value) { + if (typeof Conf[key] === 'object') { + Conf[key][g.BOARD.ID] = value; } else { - Conf['Index Sort'] = Index.currentSort; + Conf[key] = value; } - return $.set('Index Sort', Conf['Index Sort']); + return $.set(key, Conf[key]); + }, + saveSort: function() { + return Index.savePerBoard('Index Sort', Index.currentSort); + }, + saveLastLongThresholds: function(i) { + return Index.savePerBoard("Last Long Reply Thresholds " + i, Index.lastLongThresholds[i]); }, pageLoad: function(scroll) { - var hash, mode, page, ref, search, sort, threads; + var hash, mode, order, page, ref, search, sort, threads; if (scroll == null) { scroll = true; } if (!Index.liveThreadData) { return; } - ref = Index.changed, threads = ref.threads, search = ref.search, mode = ref.mode, sort = ref.sort, page = ref.page, hash = ref.hash; - if (threads || search || sort) { + ref = Index.changed, threads = ref.threads, order = ref.order, search = ref.search, mode = ref.mode, sort = ref.sort, page = ref.page, hash = ref.hash; + threads || (threads = search); + order || (order = sort); + if (threads || order) { Index.sort(); } - if (threads || search) { + if (threads) { Index.buildPagelist(); } if (search) { @@ -9041,10 +11954,10 @@ Index = (function() { if (sort) { Index.setupSort(); } - if (threads || search || mode || page || sort) { + if (threads || mode || page || order) { Index.buildIndex(); } - if (threads || search || mode || page) { + if (threads || page) { Index.setPage(); } if (scroll && !hash) { @@ -9056,10 +11969,10 @@ Index = (function() { return Index.changed = {}; }, setupMode: function() { - var j, len, mode, ref; + var k, len1, mode, ref; ref = ['paged', 'infinite', 'all pages', 'catalog']; - for (j = 0, len = ref.length; j < len; j++) { - mode = ref[j]; + for (k = 0, len1 = ref.length; k < len1; k++) { + mode = ref[k]; $[mode === Conf['Index Mode'] ? 'addClass' : 'rmClass'](doc, (mode.replace(/\ /g, '-')) + "-mode"); } Index.selectMode.value = Conf['Index Mode']; @@ -9068,11 +11981,13 @@ Index = (function() { return $('#hidden-toggle a', Index.navLinks).textContent = 'Show'; }, setupSort: function() { - return Index.selectSort.value = Index.currentSort; + Index.selectRev.checked = /-rev$/.test(Index.currentSort); + Index.selectSort.value = Index.currentSort.replace(/-rev$/, ''); + return Index.lastLongOptions.hidden = Index.selectSort.value !== 'lastlong'; }, getPagesNum: function() { if (Index.search) { - return Math.ceil(Index.sortedNodes.length / Index.threadsNumPerPage); + return Math.ceil(Index.sortedThreadIDs.length / Index.threadsNumPerPage); } else { return Index.pagesNum; } @@ -9081,12 +11996,12 @@ Index = (function() { return Math.max(1, Index.getPagesNum()); }, buildPagelist: function() { - var a, i, j, maxPageNum, nodes, pagesRoot, ref; + var a, i, k, maxPageNum, nodes, pagesRoot, ref; pagesRoot = $('.pages', Index.pagelist); maxPageNum = Index.getMaxPageNum(); if (pagesRoot.childElementCount !== maxPageNum) { nodes = []; - for (i = j = 1, ref = maxPageNum; j <= ref; i = j += 1) { + for (i = k = 1, ref = maxPageNum; k <= ref; i = k += 1) { a = $.el('a', { textContent: i, href: i === 1 ? './' : i @@ -9118,20 +12033,22 @@ Index = (function() { } else { strong = $.el('strong'); } - a = pagesRoot.children[pageNum - 1]; - $.before(a, strong); - return $.add(strong, a); + if ((a = pagesRoot.children[pageNum - 1])) { + $.before(a, strong); + return $.add(strong, a); + } }, updateHideLabel: function() { - var hiddenCount, ref, ref1, thread, threadID; + var hiddenCount, k, len1, ref, threadID; + if (!Index.hideLabel) { + return; + } hiddenCount = 0; - ref = g.BOARD.threads; - for (threadID in ref) { - thread = ref[threadID]; - if (thread.isHidden) { - if (ref1 = thread.ID, indexOf.call(Index.liveThreadIDs, ref1) >= 0) { - hiddenCount++; - } + ref = Index.liveThreadIDs; + for (k = 0, len1 = ref.length; k < len1; k++) { + threadID = ref[k]; + if (Index.isHidden(threadID)) { + hiddenCount++; } } if (!hiddenCount) { @@ -9145,56 +12062,46 @@ Index = (function() { return $('#hidden-count', Index.navLinks).textContent = hiddenCount === 1 ? '1 hidden thread' : hiddenCount + " hidden threads"; }, update: function(firstTime) { - var now, ref, ref1; - if ((ref = Index.req) != null) { - ref.abort(); - } - if ((ref1 = Index.notice) != null) { - ref1.close(); - } - if (Conf['Index Refresh Notifications'] && d.readyState !== 'loading') { - Index.notice = new Notice('info', 'Refreshing index...'); + var oldReq; + if ((oldReq = Index.req)) { + delete Index.req; + oldReq.abort(); + } + if (Conf['Index Refresh Notifications']) { + Index.notice || (Index.notice = new Notice('info', 'Refreshing index...')); + Index.nTimeout || (Index.nTimeout = setTimeout(function() { + var ref; + return (ref = Index.notice) != null ? ref.el.lastElementChild.textContent += ' (disable JSON Index if this takes too long)' : void 0; + }, 3 * $.SECOND)); } else { - now = Date.now(); - $.ready(function() { - return Index.nTimeout = setTimeout((function() { - if (Index.req && !Index.notice) { - return Index.notice = new Notice('info', 'Refreshing index...'); - } - }), 3 * $.SECOND - (Date.now() - now)); - }); + Index.nTimeout || (Index.nTimeout = setTimeout(function() { + return Index.notice || (Index.notice = new Notice('info', 'Refreshing index... (disable JSON Index if this takes too long)')); + }, 3 * $.SECOND)); } if (!firstTime && d.readyState !== 'loading' && !$('.board + *')) { location.reload(); return; } - Index.req = $.ajax("//a.4cdn.org/" + g.BOARD + "/catalog.json", { - onabort: Index.load, - onloadend: Index.load - }, { - whenModified: 'Index' - }); + Index.req = $.whenModified(g.SITE.urls.catalogJSON({ + boardID: g.BOARD.ID + }), 'Index', Index.load); return $.addClass(Index.button, 'fa-spin'); }, - load: function(e) { - var err, nTimeout, notice, ref, req, timeEl; + load: function() { + var err, nTimeout, notice, ref, timeEl; + if (this !== Index.req) { + return; + } $.rmClass(Index.button, 'fa-spin'); - req = Index.req, notice = Index.notice, nTimeout = Index.nTimeout; + notice = Index.notice, nTimeout = Index.nTimeout; if (nTimeout) { clearTimeout(nTimeout); } delete Index.nTimeout; delete Index.req; delete Index.notice; - if (e.type === 'abort') { - req.onloadend = null; - if (notice != null) { - notice.close(); - } - return; - } - if ((ref = req.status) !== 200 && ref !== 304) { - err = "Index refresh failed. " + (req.status ? "Error " + req.statusText + " (" + req.status + ")" : 'Connection Error'); + if ((ref = this.status) !== 200 && ref !== 304) { + err = "Index refresh failed. " + (this.status ? "Error " + this.statusText + " (" + this.status + ")" : 'Connection Error'); if (notice) { notice.setType('warning'); notice.el.lastElementChild.textContent = err; @@ -9205,13 +12112,13 @@ Index = (function() { return; } try { - if (req.status === 200) { - Index.parse(req.response); - } else if (req.status === 304) { + if (this.status === 200) { + Index.parse(this.response); + } else if (this.status === 304) { Index.pageLoad(); } - } catch (_error) { - err = _error; + } catch (error) { + err = error; c.error("Index failure: " + err.message, err.stack); if (notice) { notice.setType('error'); @@ -9232,20 +12139,19 @@ Index = (function() { } } timeEl = $('#index-last-refresh time', Index.navLinks); - timeEl.dataset.utc = Date.parse(req.getResponseHeader('Last-Modified')); + timeEl.dataset.utc = Date.parse(this.getResponseHeader('Last-Modified')); return RelativeDates.update(timeEl); }, parse: function(pages) { $.cleanCache(function(url) { - return /^\/\/a\.4cdn\.org\//.test(url); + return /^https?:\/\/a\.4cdn\.org\//.test(url); }); Index.parseThreadList(pages); - Index.buildThreads(); Index.changed.threads = true; return Index.pageLoad(); }, parseThreadList: function(pages) { - var ref; + var ID, data, i, k, l, len1, len2, obj, ref, ref1, ref2, reply, results; Index.pagesNum = pages.length; Index.threadsNumPerPage = ((ref = pages[0]) != null ? ref.threads.length : void 0) || 1; Index.liveThreadData = pages.reduce((function(arr, next) { @@ -9254,126 +12160,197 @@ Index = (function() { Index.liveThreadIDs = Index.liveThreadData.map(function(data) { return data.no; }); + Index.liveThreadDict = $.dict(); + Index.threadPosition = $.dict(); + Index.parsedThreads = $.dict(); + Index.replyData = $.dict(); + ref1 = Index.liveThreadData; + for (i = k = 0, len1 = ref1.length; k < len1; i = ++k) { + data = ref1[i]; + Index.liveThreadDict[data.no] = data; + Index.threadPosition[data.no] = i; + Index.parsedThreads[data.no] = obj = g.SITE.Build.parseJSON(data, g.BOARD); + obj.filterResults = results = Filter.test(obj); + obj.isOnTop = results.top; + obj.isHidden = results.hide || ThreadHiding.isHidden(obj.boardID, obj.threadID); + if (data.last_replies) { + ref2 = data.last_replies; + for (l = 0, len2 = ref2.length; l < len2; l++) { + reply = ref2[l]; + Index.replyData[g.BOARD + "." + reply.no] = reply; + } + } + } + if (Index.liveThreadData[0]) { + g.SITE.Build.spoilerRange[g.BOARD.ID] = Index.liveThreadData[0].custom_spoiler; + } g.BOARD.threads.forEach(function(thread) { - var ref1; - if (ref1 = thread.ID, indexOf.call(Index.liveThreadIDs, ref1) < 0) { + var ref3; + if (ref3 = thread.ID, indexOf.call(Index.liveThreadIDs, ref3) < 0) { return thread.collect(); } }); + $.event('IndexUpdate', { + threads: (function() { + var len3, m, ref3, results1; + ref3 = Index.liveThreadIDs; + results1 = []; + for (m = 0, len3 = ref3.length; m < len3; m++) { + ID = ref3[m]; + results1.push(g.BOARD + "." + ID); + } + return results1; + })() + }); }, - buildThreads: function() { - var err, errors, i, j, len, posts, ref, thread, threadData, threadRoot, threads; - if (!Index.liveThreadData) { - return; + isHidden: function(threadID) { + var thread; + if ((thread = g.BOARD.threads.get(threadID)) && thread.OP && !thread.OP.isFetchedQuote) { + return thread.isHidden; + } else { + return Index.parsedThreads[threadID].isHidden; } - Index.nodes = []; + }, + isHiddenReply: function(threadID, replyData) { + return PostHiding.isHidden(g.BOARD.ID, threadID, replyData.no) || Filter.isHidden(g.SITE.Build.parseJSON(replyData, g.BOARD)); + }, + buildThreads: function(threadIDs, isCatalog, withReplies) { + var ID, OP, err, errors, isStale, k, lastPost, len1, newPosts, newThreads, obj, opRoot, t, thread, threadData, threads; threads = []; - posts = []; - ref = Index.liveThreadData; - for (i = j = 0, len = ref.length; j < len; i = ++j) { - threadData = ref[i]; + newThreads = []; + newPosts = []; + for (k = 0, len1 = threadIDs.length; k < len1; k++) { + ID = threadIDs[k]; try { - threadRoot = Build.thread(g.BOARD, threadData); - if (Index.hat) { - $.prepend(threadRoot, Index.hat.cloneNode(false)); - } - if (thread = g.BOARD.threads[threadData.no]) { - thread.setCount('post', threadData.replies + 1, threadData.bumplimit); - thread.setCount('file', threadData.images + !!threadData.ext, threadData.imagelimit); - thread.setStatus('Sticky', !!threadData.sticky); - thread.setStatus('Closed', !!threadData.closed); + threadData = Index.liveThreadDict[ID]; + if ((thread = g.BOARD.threads.get(ID))) { + isStale = (thread.json !== threadData) && (JSON.stringify(thread.json) !== JSON.stringify(threadData)); + if (isStale) { + thread.setCount('post', threadData.replies + 1, threadData.bumplimit); + thread.setCount('file', threadData.images + !!threadData.ext, threadData.imagelimit); + thread.setStatus('Sticky', !!threadData.sticky); + thread.setStatus('Closed', !!threadData.closed); + } + if (thread.catalogView) { + $.rm(thread.catalogView.nodes.replies); + thread.catalogView.nodes.replies = null; + } } else { - thread = new Thread(threadData.no, g.BOARD); - threads.push(thread); + thread = new Thread(ID, g.BOARD); + newThreads.push(thread); + } + lastPost = threadData.last_replies && threadData.last_replies.length ? threadData.last_replies[threadData.last_replies.length - 1].no : ID; + if (lastPost > thread.lastPost) { + thread.lastPost = lastPost; + } + thread.json = threadData; + threads.push(thread); + if ((OP = thread.OP) && !OP.isFetchedQuote) { + OP.setCatalogOP(isCatalog); + thread.setPage(Math.floor(Index.threadPosition[ID] / Index.threadsNumPerPage) + 1); + } else { + obj = Index.parsedThreads[ID]; + opRoot = g.SITE.Build.post(obj); + OP = new Post(opRoot, thread, g.BOARD); + OP.filterResults = obj.filterResults; + newPosts.push(OP); } - Index.nodes.push(threadRoot); - if (!(thread.OP && !thread.OP.isFetchedQuote)) { - posts.push(new Post($('.opContainer', threadRoot), thread, g.BOARD)); + if (!(isCatalog && thread.nodes.root)) { + g.SITE.Build.thread(thread, threadData, withReplies); } - thread.setPage(Math.floor(i / Index.threadsNumPerPage) + 1); - } catch (_error) { - err = _error; + } catch (error) { + err = error; if (!errors) { errors = []; } errors.push({ message: "Parsing of Thread No." + thread + " failed. Thread will be skipped.", - error: err + error: err, + html: opRoot != null ? opRoot.outerHTML : void 0 }); } } if (errors) { Main.handleErrors(errors); } - $.nodes(Index.nodes); - Main.callbackNodes('Thread', threads); - Main.callbackNodes('Post', posts); + if (withReplies) { + newPosts = newPosts.concat(Index.buildReplies(threads)); + } + Main.callbackNodes('Thread', newThreads); + Main.callbackNodes('Post', newPosts); Index.updateHideLabel(); - return $.event('IndexRefresh'); + $.event('IndexRefreshInternal', { + threadIDs: (function() { + var l, len2, results1; + results1 = []; + for (l = 0, len2 = threads.length; l < len2; l++) { + t = threads[l]; + results1.push(t.fullID); + } + return results1; + })(), + isCatalog: isCatalog + }); + return threads; }, - buildReplies: function(threadRoots) { - var data, err, errors, i, j, k, lastReplies, len, len1, node, nodes, post, posts, thread, threadRoot; + buildReplies: function(threads) { + var data, err, errors, k, l, lastReplies, len1, len2, node, nodes, post, posts, thread; posts = []; - for (j = 0, len = threadRoots.length; j < len; j++) { - threadRoot = threadRoots[j]; - thread = Get.threadFromRoot(threadRoot); - i = Index.liveThreadIDs.indexOf(thread.ID); - if (!(lastReplies = Index.liveThreadData[i].last_replies)) { + for (k = 0, len1 = threads.length; k < len1; k++) { + thread = threads[k]; + if (!(lastReplies = Index.liveThreadDict[thread.ID].last_replies)) { continue; } nodes = []; - for (k = 0, len1 = lastReplies.length; k < len1; k++) { - data = lastReplies[k]; - if ((post = thread.posts[data.no]) && !post.isFetchedQuote) { + for (l = 0, len2 = lastReplies.length; l < len2; l++) { + data = lastReplies[l]; + if ((post = thread.posts.get(data.no)) && !post.isFetchedQuote) { nodes.push(post.nodes.root); continue; } - nodes.push(node = Build.postFromObject(data, thread.board.ID)); + nodes.push(node = g.SITE.Build.postFromObject(data, thread.board.ID)); try { posts.push(new Post(node, thread, thread.board)); - } catch (_error) { - err = _error; + } catch (error) { + err = error; if (!errors) { errors = []; } errors.push({ message: "Parsing of Post No." + data.no + " failed. Post will be skipped.", - error: err + error: err, + html: node != null ? node.outerHTML : void 0 }); } } - $.add(threadRoot, nodes); + $.add(thread.nodes.root, nodes); } if (errors) { Main.handleErrors(errors); } - return Main.callbackNodes('Post', posts); + return posts; }, - buildCatalogViews: function() { - var catalogThreads, j, len, thread, threads; - threads = Index.sortedNodes.map(function(threadRoot) { - return Get.threadFromRoot(threadRoot); - }).filter(function(thread) { - return !thread.isHidden !== Index.showHiddenThreads; - }); + buildCatalogViews: function(threads) { + var ID, catalogThreads, k, len1, page, root, thread; catalogThreads = []; - for (j = 0, len = threads.length; j < len; j++) { - thread = threads[j]; - if (!thread.catalogView) { - catalogThreads.push(new CatalogThread(Build.catalogThread(thread), thread)); + for (k = 0, len1 = threads.length; k < len1; k++) { + thread = threads[k]; + if (!(!thread.catalogView)) { + continue; } + ID = thread.ID; + page = Math.floor(Index.threadPosition[ID] / Index.threadsNumPerPage) + 1; + root = g.SITE.Build.catalogThread(thread, Index.liveThreadDict[ID], page); + catalogThreads.push(new CatalogThread(root, thread)); } Main.callbackNodes('CatalogThread', catalogThreads); - return threads.map(function(thread) { - return thread.catalogView.nodes.root; - }); }, - sizeCatalogViews: function(nodes) { - var height, j, len, node, ratio, ref, size, thumb, width; + sizeCatalogViews: function(threads) { + var height, k, len1, ratio, ref, size, thread, thumb, width; size = Conf['Index Size'] === 'small' ? 150 : 250; - for (j = 0, len = nodes.length; j < len; j++) { - node = nodes[j]; - thumb = $('.catalog-thumb', node); + for (k = 0, len1 = threads.length; k < len1; k++) { + thread = threads[k]; + thumb = thread.catalogView.nodes.thumb; ref = thumb.dataset, width = ref.width, height = ref.height; if (!width) { continue; @@ -9383,41 +12360,78 @@ Index = (function() { thumb.style.height = height * ratio + 'px'; } }, + buildCatalogReplies: function(thread) { + var data, k, lastReplies, len1, nodes, replies, reply; + nodes = thread.catalogView.nodes; + if (!(lastReplies = Index.liveThreadDict[thread.ID].last_replies)) { + return; + } + replies = []; + for (k = 0, len1 = lastReplies.length; k < len1; k++) { + data = lastReplies[k]; + if (Index.isHiddenReply(thread.ID, data)) { + continue; + } + reply = g.SITE.Build.catalogReply(thread, data); + RelativeDates.update($('time', reply)); + $.on($('.catalog-reply-preview', reply), 'mouseover', QuotePreview.mouseover); + replies.push(reply); + } + nodes.replies = $.el('div', { + className: 'catalog-replies' + }); + $.add(nodes.replies, replies); + $.add(thread.OP.nodes.post, nodes.replies); + }, sort: function() { - var j, lastlong, len, liveThreadData, liveThreadIDs, nodes, sortedNodes, sortedThreadIDs, threadID; + var lastlong, lastlongD, liveThreadData, liveThreadIDs, repliesAvailable, sortType, thread, threadIDs, tmp_time; liveThreadIDs = Index.liveThreadIDs, liveThreadData = Index.liveThreadData; if (!liveThreadData) { return; } - sortedThreadIDs = (function() { - switch (Index.currentSort) { + tmp_time = new Date().getTime() / 1000; + sortType = Index.currentSort.replace(/-rev$/, ''); + Index.sortedThreadIDs = (function() { + var k, len1; + switch (sortType) { case 'lastreply': - return slice.call(liveThreadData).sort(function(a, b) { - var num; - if ((num = a.last_replies)) { - a = num[num.length - 1]; - } - if ((num = b.last_replies)) { - b = num[num.length - 1]; - } - return b.no - a.no; - }).map(function(post) { - return post.no; - }); case 'lastlong': + repliesAvailable = liveThreadData.some(function(thread) { + var ref; + return (ref = thread.last_replies) != null ? ref.length : void 0; + }); lastlong = function(thread) { - var i, j, r, ref; + var i, k, len, r, ref, ref1; + if (!repliesAvailable) { + return thread.last_modified; + } ref = thread.last_replies || []; - for (i = j = ref.length - 1; j >= 0; i = j += -1) { + for (i = k = ref.length - 1; k >= 0; i = k += -1) { r = ref[i]; - if (r.com && Build.parseComment(r.com).replace(/[^a-z]/ig, '').length >= 100) { + if (Index.isHiddenReply(thread.no, r)) { + continue; + } + if (sortType === 'lastreply') { return r; } + len = r.com ? g.SITE.Build.parseComment(r.com).replace(/[^a-z]/ig, '').length : 0; + if (len >= Index.lastLongThresholds[+(!!r.ext)]) { + return r; + } + } + if (thread.omitted_posts && ((ref1 = thread.last_replies) != null ? ref1.length : void 0)) { + return thread.last_replies[0]; + } else { + return thread; } - return thread; }; + lastlongD = $.dict(); + for (k = 0, len1 = liveThreadData.length; k < len1; k++) { + thread = liveThreadData[k]; + lastlongD[thread.no] = lastlong(thread).no; + } return slice.call(liveThreadData).sort(function(a, b) { - return lastlong(b).no - lastlong(a).no; + return lastlongD[b.no] - lastlongD[a.no]; }).map(function(post) { return post.no; }); @@ -9439,105 +12453,134 @@ Index = (function() { }).map(function(post) { return post.no; }); + case 'activity': + return slice.call(liveThreadData).sort(function(a, b) { + return (tmp_time - a.time) / (a.replies + 1) - (tmp_time - b.time) / (b.replies + 1); + }).map(function(post) { + return post.no; + }); + default: + return liveThreadIDs; } })(); - Index.sortedNodes = sortedNodes = []; - nodes = Index.nodes; - for (j = 0, len = sortedThreadIDs.length; j < len; j++) { - threadID = sortedThreadIDs[j]; - sortedNodes.push(nodes[Index.liveThreadIDs.indexOf(threadID)]); + if (/-rev$/.test(Index.currentSort)) { + Index.sortedThreadIDs = slice.call(Index.sortedThreadIDs).reverse(); } - if (Index.search && (nodes = Index.querySearch(Index.search))) { - Index.sortedNodes = nodes; + if (Index.search && (threadIDs = Index.querySearch(Index.search))) { + Index.sortedThreadIDs = threadIDs; } - Index.sortOnTop(function(thread) { - return thread.isSticky; + Index.sortOnTop(function(obj) { + return obj.isSticky; }); - Index.sortOnTop(function(thread) { - return thread.isOnTop || Conf['Pin Watched Threads'] && ThreadWatcher.isWatched(thread); + Index.sortOnTop(function(obj) { + return obj.isOnTop || Conf['Pin Watched Threads'] && ThreadWatcher.isWatchedRaw(obj.boardID, obj.threadID); }); if (Conf['Anchor Hidden Threads']) { - return Index.sortOnTop(function(thread) { - return !thread.isHidden; + return Index.sortOnTop(function(obj) { + return !Index.isHidden(obj.threadID); }); } }, sortOnTop: function(match) { - var bottomNodes, j, len, ref, threadRoot, topNodes; - topNodes = []; - bottomNodes = []; - ref = Index.sortedNodes; - for (j = 0, len = ref.length; j < len; j++) { - threadRoot = ref[j]; - (match(Get.threadFromRoot(threadRoot)) ? topNodes : bottomNodes).push(threadRoot); + var ID, bottomThreads, k, len1, ref, topThreads; + topThreads = []; + bottomThreads = []; + ref = Index.sortedThreadIDs; + for (k = 0, len1 = ref.length; k < len1; k++) { + ID = ref[k]; + (match(Index.parsedThreads[ID]) ? topThreads : bottomThreads).push(ID); } - return Index.sortedNodes = topNodes.concat(bottomNodes); + return Index.sortedThreadIDs = topThreads.concat(bottomThreads); }, buildIndex: function() { - var i, nodes, page, post; + var threadIDs; if (!Index.liveThreadData) { return; } switch (Conf['Index Mode']) { case 'all pages': - nodes = Index.sortedNodes; + threadIDs = Index.sortedThreadIDs; break; case 'catalog': - nodes = Index.buildCatalogViews(); - Index.sizeCatalogViews(nodes); + threadIDs = Index.sortedThreadIDs.filter(function(ID) { + return !Index.isHidden(ID) !== Index.showHiddenThreads; + }); break; default: - if (Index.followedThreadID != null) { - i = 0; - while (Index.followedThreadID !== Get.threadFromRoot(Index.sortedNodes[i]).ID) { - i++; - } - page = Math.floor(i / Index.threadsNumPerPage) + 1; - if (page !== Index.currentPage) { - Index.currentPage = page; - Index.pushState({ - page: page - }); - Index.setPage(); - } - } - nodes = Index.buildSinglePage(Index.currentPage); + threadIDs = Index.threadsOnPage(Index.currentPage); } delete Index.pageNum; $.rmAll(Index.root); $.rmAll(Header.hover); + if (Index.loaded && Index.root.parentNode) { + $.event('PostsRemoved', null, Index.root); + } if (Conf['Index Mode'] === 'catalog') { - return $.add(Index.root, nodes); + Index.buildCatalog(threadIDs); } else { - if (Conf['Show Replies']) { - Index.buildReplies(nodes); - } - Index.buildStructure(nodes); - if ((Index.followedThreadID != null) && (post = g.posts[g.BOARD + "." + Index.followedThreadID])) { - return Header.scrollTo(post.nodes.root); - } + Index.buildStructure(threadIDs); } }, - buildSinglePage: function(pageNum) { + threadsOnPage: function(pageNum) { var nodesPerPage, offset; nodesPerPage = Index.threadsNumPerPage; offset = nodesPerPage * (pageNum - 1); - return Index.sortedNodes.slice(offset, offset + nodesPerPage); + return Index.sortedThreadIDs.slice(offset, offset + nodesPerPage); }, - buildStructure: function(nodes) { - var j, len, node, thumb; - for (j = 0, len = nodes.length; j < len; j++) { - node = nodes[j]; - if (thumb = $('img[data-src]', node)) { - thumb.src = thumb.dataset.src; - thumb.removeAttribute('data-src'); - } - $.add(Index.root, [node, $.el('hr')]); + buildStructure: function(threadIDs) { + var k, len1, nodes, thread, threads; + threads = Index.buildThreads(threadIDs, false, Conf['Show Replies']); + nodes = []; + for (k = 0, len1 = threads.length; k < len1; k++) { + thread = threads[k]; + nodes.push(thread.nodes.root, $.el('hr')); } - if (doc.contains(Index.root)) { - $.event('PostsInserted'); + $.add(Index.root, nodes); + if (Index.root.parentNode) { + $.event('PostsInserted', null, Index.root); } - return ThreadHiding.onIndexBuild(nodes); + Index.loaded = true; + }, + buildCatalog: function(threadIDs) { + var fn, i, n, node0; + i = 0; + n = threadIDs.length; + node0 = null; + fn = function() { + var j; + if (node0 && !node0.parentNode) { + return; + } + j = i > 0 && Index.root.parentNode ? n : i + 30; + node0 = Index.buildCatalogPart(threadIDs.slice(i, j))[0]; + i = j; + if (i < n) { + return $.queueTask(fn); + } else { + if (Index.root.parentNode) { + $.event('PostsInserted', null, Index.root); + } + return Index.loaded = true; + } + }; + fn(); + }, + buildCatalogPart: function(threadIDs) { + var k, len1, nodes, thread, threads; + threads = Index.buildThreads(threadIDs, true); + Index.buildCatalogViews(threads); + Index.sizeCatalogViews(threads); + nodes = []; + for (k = 0, len1 = threads.length; k < len1; k++) { + thread = threads[k]; + thread.OP.setCatalogOP(true); + $.add(thread.catalogView.nodes.root, thread.OP.nodes.root); + nodes.push(thread.catalogView.nodes.root); + $.on(thread.catalogView.nodes.root, 'mouseenter', Index.cb.catalogReplies.bind(thread)); + $.on(thread.OP.nodes.root, 'mouseenter', Index.cb.hoverAdjust.bind(thread.OP.nodes)); + } + $.add(Index.root, nodes); + return nodes; }, clearSearch: function() { Index.searchInput.value = ''; @@ -9565,21 +12608,34 @@ Index = (function() { return Index.pageLoad(false); }, querySearch: function(query) { - var keywords; + var keywords, match, regexp; + if ((match = query.match(/^([\w+]+):\/(.*)\/(\w*)$/))) { + try { + regexp = RegExp(match[2], match[3]); + } catch (error) { + return []; + } + return Index.sortedThreadIDs.filter(function(ID) { + return regexp.test(Filter.values(match[1], Index.parsedThreads[ID]).join('\n')); + }); + } if (!(keywords = query.toLowerCase().match(/\S+/g))) { return; } - return Index.sortedNodes.filter(function(threadRoot) { - return Index.searchMatch(Get.threadFromRoot(threadRoot), keywords); + return Index.sortedThreadIDs.filter(function(ID) { + return Index.searchMatch(Index.parsedThreads[ID], keywords); }); }, - searchMatch: function(thread, keywords) { - var file, info, j, k, key, keyword, len, len1, ref, ref1, text; - ref = thread.OP, info = ref.info, file = ref.file; + searchMatch: function(obj, keywords) { + var file, info, k, key, keyword, l, len1, len2, ref, text; + info = obj.info, file = obj.file; + if (info.comment == null) { + info.comment = g.SITE.Build.parseComment(info.commentHTML.innerHTML); + } text = []; - ref1 = ['comment', 'subject', 'name', 'tripcode', 'email']; - for (j = 0, len = ref1.length; j < len; j++) { - key = ref1[j]; + ref = ['comment', 'subject', 'name', 'tripcode']; + for (k = 0, len1 = ref.length; k < len1; k++) { + key = ref[k]; if (key in info) { text.push(info[key]); } @@ -9588,8 +12644,8 @@ Index = (function() { text.push(file.name); } text = text.join(' ').toLowerCase(); - for (k = 0, len1 = keywords.length; k < len1; k++) { - keyword = keywords[k]; + for (l = 0, len2 = keywords.length; l < len2; l++) { + keyword = keywords[l]; if (-1 === text.indexOf(keyword)) { return false; } @@ -9607,7 +12663,10 @@ Polyfill = (function() { Polyfill = { init: function() { - return this.toBlob(); + var base; + this.toBlob(); + $.global(this.toBlob); + (base = Element.prototype).matches || (base.matches = Element.prototype.mozMatchesSelector || Element.prototype.webkitMatchesSelector); }, toBlob: function() { if (HTMLCanvasElement.prototype.toBlob) { @@ -9615,9 +12674,6 @@ Polyfill = (function() { } HTMLCanvasElement.prototype.toBlob = function(cb, type, encoderOptions) { var data, i, j, l, ref, ui8a, url; - if (type == null) { - type = 'image/png'; - } url = this.toDataURL(type, encoderOptions); data = atob(url.slice(url.indexOf(',') + 1)); l = data.length; @@ -9626,10 +12682,9 @@ Polyfill = (function() { ui8a[i] = data.charCodeAt(i); } return cb(new Blob([ui8a], { - type: type + type: type || 'image/png' })); }; - return $.globalEval("HTMLCanvasElement.prototype.toBlob = (" + HTMLCanvasElement.prototype.toBlob + ");"); } }; @@ -9644,7 +12699,7 @@ Settings = (function() { Settings = { init: function() { - var add, link, settings; + var add, link; link = $.el('a', { className: 'settings-link fa fa-wrench', textContent: 'Settings', @@ -9663,14 +12718,25 @@ Settings = (function() { $.on(d, 'OpenSettings', function(e) { return Settings.open(e.detail); }); - if (Conf['Disable Native Extension']) { + if (g.SITE.software === 'yotsuba' && Conf['Disable Native Extension']) { if ($.hasStorage) { - settings = JSON.parse(localStorage.getItem('4chan-settings')) || {}; - if (settings.disableAll) { - return; - } - settings.disableAll = true; - return localStorage.setItem('4chan-settings', JSON.stringify(settings)); + return $.global(function() { + var settings; + try { + settings = JSON.parse(localStorage.getItem('4chan-settings')) || {}; + if (settings.disableAll) { + return; + } + settings.disableAll = true; + return localStorage.setItem('4chan-settings', JSON.stringify(settings)); + } catch (error) { + return Object.defineProperty(window, 'Config', { + value: { + disableAll: true + } + }); + } + }); } else { return $.global(function() { return Object.defineProperty(window, 'Config', { @@ -9683,21 +12749,14 @@ Settings = (function() { } }, open: function(openSection) { - var dialog, j, len, link, links, overlay, ref, section, sectionToOpen; - if (Settings.overlay) { + var dialog, j, len, link, links, ref, section, sectionToOpen; + if (Settings.dialog) { return; } $.event('CloseMenu'); Settings.dialog = dialog = $.el('div', { - id: 'fourchanx-settings', - className: 'dialog' - }); - $.extend(dialog, { - innerHTML: "
      " - }); - Settings.overlay = overlay = $.el('div', { id: 'overlay' - }); + }, {innerHTML: ""}); $.on($('.export', dialog), 'click', Settings["export"]); $.on($('.import', dialog), 'click', Settings["import"]); $.on($('.reset', dialog), 'click', Settings.reset); @@ -9723,9 +12782,12 @@ Settings = (function() { (sectionToOpen ? sectionToOpen : links[0]).click(); } $.on($('.close', dialog), 'click', Settings.close); - $.on(overlay, 'click', Settings.close); $.on(window, 'beforeunload', Settings.close); - $.add(d.body, [overlay, dialog]); + $.on(dialog, 'click', Settings.close); + $.on(dialog.firstElementChild, 'click', function(e) { + return e.stopPropagation(); + }); + $.add(d.body, dialog); return $.event('OpenSettings', null, dialog); }, close: function() { @@ -9736,9 +12798,7 @@ Settings = (function() { if ((ref = d.activeElement) != null) { ref.blur(); } - $.rm(Settings.overlay); $.rm(Settings.dialog); - delete Settings.overlay; return delete Settings.dialog; }, sections: [], @@ -9773,32 +12833,28 @@ Settings = (function() { if ($.cantSync) { why = $.cantSet ? 'save your settings' : 'synchronize settings between tabs'; return cb($.el('li', { - textContent: "4chan X needs local storage to " + why + ".\nEnable it on boards.4chan.org in your browser's privacy settings (may be listed as part of \"local data\" or \"cookies\")." + textContent: "4chan X needs local storage to " + why + ".\nEnable it on boards." + (location.hostname.split('.')[1]) + ".org in your browser's privacy settings (may be listed as part of \"local data\" or \"cookies\")." })); } }, ads: function(cb) { - return $.onExists(doc, '.ad-cnt', function(ad) { - return $.onExists(ad, 'img', function() { + return $.onExists(doc, '.adg-rects > .desktop', function(ad) { + return $.onExists(ad, 'iframe', function() { var url; url = Redirect.to('thread', { boardID: 'qa', threadID: 362590 }); - return cb($.el('li', { - innerHTML: "To protect yourself from malicious ads, you should block ads on 4chan." - })); + return cb($.el('li', {innerHTML: "To protect yourself from malicious ads, you should block ads on 4chan."})); }); }); } }, main: function(section) { - var addWarning, arr, button, container, containers, description, div, fs, input, inputs, items, key, level, obj, ref, ref1, warning, warnings; + var addCheckboxes, addWarning, button, div, fs, inputs, items, key, keyFS, obj, ref, ref1, warning, warnings; warnings = $.el('fieldset', { hidden: true - }, { - innerHTML: "Warnings
        " - }); + }, {innerHTML: "Warnings
          "}); addWarning = function(item) { $.add($('ul', warnings), item); return warnings.hidden = false; @@ -9809,28 +12865,24 @@ Settings = (function() { warning(addWarning); } $.add(section, warnings); - items = {}; - inputs = {}; - ref1 = Config.main; - for (key in ref1) { - obj = ref1[key]; - fs = $.el('fieldset', { - innerHTML: "" + E(key) + "" - }); - containers = [fs]; + items = $.dict(); + inputs = $.dict(); + addCheckboxes = function(root, obj) { + var arr, container, containers, description, div, input, level, results; + containers = [root]; + results = []; for (key in obj) { arr = obj[key]; - description = arr[1]; - div = $.el('div', { - innerHTML: ": " + E(description) + "" - }); - if ($.engine !== 'gecko' && key === 'Remember QR Size') { - div.hidden = true; + if (!(arr instanceof Array)) { + continue; } + description = arr[1]; + div = $.el('div', {innerHTML: ": " + E(description) + ""}); + div.dataset.name = key; input = $('input', div); + $.on(input, 'change', $.cb.checked); $.on(input, 'change', function() { - this.parentNode.parentNode.dataset.checked = this.checked; - return $.cb.checked.call(this); + return this.parentNode.parentNode.dataset.checked = this.checked; }); items[key] = Conf[key]; inputs[key] = input; @@ -9844,10 +12896,30 @@ Settings = (function() { } else if (containers.length > level + 1) { containers.splice(level + 1, containers.length - (level + 1)); } - $.add(containers[level], div); + results.push($.add(containers[level], div)); + } + return results; + }; + ref1 = Config.main; + for (keyFS in ref1) { + obj = ref1[keyFS]; + fs = $.el('fieldset', {innerHTML: "" + E(keyFS) + ""}); + addCheckboxes(fs, obj); + if (keyFS === 'Posting and Captchas') { + $.add(fs, $.el('p', {innerHTML: "For more info on captcha options and issues, see the captcha FAQ."})); } $.add(section, fs); } + addCheckboxes($('div[data-name="JSON Index"] > .suboption-list', section), Config.Index); + if ($.engine !== 'gecko') { + $('div[data-name="Remember QR Size"]', section).hidden = true; + } + if ($.perProtocolSettings || location.protocol !== 'https:') { + $('div[data-name="Redirect to HTTPS"]', section).hidden = true; + } + if ($.platform !== 'crx') { + $('div[data-name="Work around CORB Bug"]', section).hidden = true; + } $.get(items, function(items) { var val; for (key in items) { @@ -9856,25 +12928,46 @@ Settings = (function() { inputs[key].parentNode.parentNode.dataset.checked = val; } }); - div = $.el('div', { - innerHTML: ": Clear manually-hidden threads and posts on all boards. Reload the page to apply." - }); + div = $.el('div', {innerHTML: ": Clear manually-hidden threads and posts on all boards. Reload the page to apply."}); button = $('button', div); $.get({ - hiddenThreads: {}, - hiddenPosts: {} + hiddenThreads: $.dict(), + hiddenPosts: $.dict() }, function(arg) { - var ID, board, hiddenNum, hiddenPosts, hiddenThreads, ref2, ref3, thread; + var ID, board, hiddenNum, hiddenPosts, hiddenThreads, ref2, ref3, ref4, ref5, site, thread; hiddenThreads = arg.hiddenThreads, hiddenPosts = arg.hiddenPosts; hiddenNum = 0; - ref2 = hiddenThreads.boards; - for (ID in ref2) { - board = ref2[ID]; - hiddenNum += Object.keys(board).length; + for (ID in hiddenThreads) { + site = hiddenThreads[ID]; + if (ID !== 'boards') { + ref2 = site.boards; + for (ID in ref2) { + board = ref2[ID]; + hiddenNum += Object.keys(board).length; + } + } } - ref3 = hiddenPosts.boards; + ref3 = hiddenThreads.boards; for (ID in ref3) { board = ref3[ID]; + hiddenNum += Object.keys(board).length; + } + for (ID in hiddenPosts) { + site = hiddenPosts[ID]; + if (ID !== 'boards') { + ref4 = site.boards; + for (ID in ref4) { + board = ref4[ID]; + for (ID in board) { + thread = board[ID]; + hiddenNum += Object.keys(thread).length; + } + } + } + } + ref5 = hiddenPosts.boards; + for (ID in ref5) { + board = ref5[ID]; for (ID in board) { thread = board[ID]; hiddenNum += Object.keys(thread).length; @@ -9884,10 +12977,13 @@ Settings = (function() { }); $.on(button, 'click', function() { this.textContent = 'Hidden: 0'; - return $.get('hiddenThreads', {}, function(arg) { - var boardID, hiddenThreads; + return $.get('hiddenThreads', $.dict(), function(arg) { + var boardID, hiddenThreads, ref2; hiddenThreads = arg.hiddenThreads; - if ($.hasStorage) { + if ($.hasStorage && g.SITE.software === 'yotsuba') { + for (boardID in (ref2 = hiddenThreads['4chan.org']) != null ? ref2.boards : void 0) { + localStorage.removeItem("4chan-hide-t-" + boardID); + } for (boardID in hiddenThreads.boards) { localStorage.removeItem("4chan-hide-t-" + boardID); } @@ -9898,19 +12994,27 @@ Settings = (function() { return $.after($('input[name="Stubs"]', section).parentNode.parentNode, div); }, "export": function() { - return $.get(Conf, function(Conf) { + var Conf2; + Conf2 = $.dict(); + $.extend(Conf2, Conf); + return $.get(Conf2, function(Conf2) { + delete Conf2['boardConfig']; return Settings.downloadExport({ version: g.VERSION, date: Date.now(), - Conf: Conf + Conf: Conf2 }); }); }, downloadExport: function(data) { - var a, p; + var a, blob, p, url; + blob = new Blob([JSON.stringify(data, null, 2)], { + type: 'application/json' + }); + url = URL.createObjectURL(blob); a = $.el('a', { download: "4chan X 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); $.rmAll(p); @@ -9935,15 +13039,15 @@ Settings = (function() { reader.onload = function(e) { var err; try { - return Settings.loadSettings(JSON.parse(e.target.result), function(err) { + return Settings.loadSettings($.dict.json(e.target.result), function(err) { if (err) { return output.textContent = 'Import failed due to an error.'; } else if (confirm('Import successful. Reload now?')) { return window.location.reload(); } }); - } catch (_error) { - err = _error; + } catch (error) { + err = error; output.textContent = 'Import failed due to an error.'; return c.error(err.stack); } @@ -9968,17 +13072,20 @@ Settings = (function() { 'Disable 4chan\'s extension': 'Disable Native Extension', 'Comment Auto-Expansion': '', 'Remove Slug': '', + 'Always HTTPS': 'Redirect to HTTPS', 'Check for Updates': '', 'Recursive Filtering': 'Recursive Hiding', 'Reply Hiding': 'Reply Hiding Buttons', 'Thread Hiding': 'Thread Hiding Buttons', 'Show Stubs': 'Stubs', 'Image Auto-Gif': 'Replace GIF', + 'Expand All WebM': 'Expand videos', 'Reveal Spoilers': 'Reveal Spoiler Thumbnails', 'Expand From Current': 'Expand from here', 'Current Page': 'Page Count in Stats', 'Current Page Position': '', 'Alternative captcha': 'Use Recaptcha v1', + 'Alt index captcha': 'Use Recaptcha v1 on Index', 'Auto Submit': 'Post on Captcha Completion', 'Open Reply in New Tab': 'Open Post in New Tab', 'Remember QR size': 'Remember QR Size', @@ -10000,6 +13107,7 @@ Settings = (function() { 'spoiler': 'Spoiler tags', 'sageru': 'Toggle sage', 'code': 'Code tags', + 'sjis': 'SJIS tags', 'submit': 'Submit QR', 'watch': 'Watch', 'update': 'Update', @@ -10020,6 +13128,10 @@ Settings = (function() { 'Scrolling': 'Auto Scroll', 'Verbose': '' }); + if ('Always CDN' in data.Conf) { + data.Conf['fourchanImageHost'] = data.Conf['Always CDN'] ? 'i.4cdn.org' : ''; + delete data.Conf['Always CDN']; + } data.Conf.sauces = data.Conf.sauces.replace(/\$\d/g, function(c) { switch (c) { case '$1': @@ -10046,15 +13158,17 @@ Settings = (function() { } } if (data.WatchedThreads) { - data.Conf['watchedThreads'] = { - boards: {} - }; + data.Conf['watchedThreads'] = $.dict.clone({ + '4chan.org': { + boards: {} + } + }); ref1 = data.WatchedThreads; for (boardID in ref1) { threads = ref1[boardID]; for (threadID in threads) { threadData = threads[threadID]; - ((base = data.Conf['watchedThreads'].boards)[boardID] || (base[boardID] = {}))[threadID] = { + ((base = data.Conf['watchedThreads']['4chan.org'].boards)[boardID] || (base[boardID] = $.dict()))[threadID] = { excerpt: threadData.textContent }; } @@ -10064,11 +13178,16 @@ Settings = (function() { } }, upgrade: function(data, version) { - var addCSS, addSauces, boardID, changes, compareString, j, key, len, name, record, ref, ref1, ref2, ref3, ref4, ref5, rice, set, type, uids, value; - changes = {}; + var addCSS, addSauces, boardID, boards, changes, compareString, corrupted, db, hostname, j, k, key, l, lastChecked, len, len1, len2, len3, line, list, m, name, record, ref, ref1, ref10, ref11, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, rice, set, setD, siteProperties, software, type, uids, val, val2, value; + changes = $.dict(); set = function(key, value) { return data[key] = changes[key] = value; }; + setD = function(key, value) { + if (data[key] == null) { + return set(key, value); + } + }; addSauces = function(sauces) { if (data['sauces'] != null) { sauces = sauces.filter(function(s) { @@ -10087,9 +13206,35 @@ Settings = (function() { return set('usercss', css + '\n\n' + data['usercss']); } }; + if ((corrupted = version[0] === '"')) { + try { + version = JSON.parse(version); + } catch (error) {} + } compareString = version.replace(/\d+/g, function(x) { return ('0000' + x).slice(-5); }); + if (compareString < '00001.00013.00014.00008') { + for (key in data) { + val = data[key]; + if (!(typeof val === 'string' && typeof Conf[key] !== 'string' && (key !== 'Index Sort' && key !== 'Last Long Reply Thresholds 0' && key !== 'Last Long Reply Thresholds 1'))) { + continue; + } + corrupted = true; + break; + } + } + if (corrupted) { + for (key in data) { + val = data[key]; + if (typeof val === 'string') { + try { + val2 = JSON.parse(val); + set(key, val2); + } catch (error) {} + } + } + } if (compareString < '00001.00011.00008.00000') { if (data['Fixed Thread Watcher'] == null) { set('Fixed Thread Watcher', (ref = data['Toggleable Thread Watcher']) != null ? ref : true); @@ -10118,7 +13263,7 @@ Settings = (function() { record = ref2[boardID]; for (type in record) { name = record[type]; - if (name in uids) { + if ($.hasOwn(uids, name)) { record[type] = uids[name]; } } @@ -10147,7 +13292,7 @@ Settings = (function() { set('sauces', data['sauces'].replace(/^(#?\s*)http:\/\/iqdb\.org\//mg, '$1//iqdb.org/')); } } - if (compareString < '00001.00011.00019.00003' && !Settings.overlay) { + if (compareString < '00001.00011.00019.00003' && !Settings.dialog) { $.queueTask(function() { return Settings.warnings.ads(function(item) { return new Notice('warning', slice.call(item.childNodes)); @@ -10226,10 +13371,153 @@ Settings = (function() { addCSS('.qr-link-container-bottom {display: none;}'); } } + if (compareString < '00001.00012.00000.00006') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/^(#?\s*)https:\/\/(?:desustorage|cuckchan)\.org\//mg, '$1https://desuarchive.org/')); + } + } + if (compareString < '00001.00012.00001.00000') { + if ((data['Persistent Thread Watcher'] == null) && (data['Toggleable Thread Watcher'] != null)) { + set('Persistent Thread Watcher', !data['Toggleable Thread Watcher']); + } + } + if (compareString < '00001.00012.00003.00000') { + ref6 = ['Image Hover in Catalog', 'Auto Watch', 'Auto Watch Reply']; + for (k = 0, len1 = ref6.length; k < len1; k++) { + key = ref6[k]; + setD(key, false); + } + } + if (compareString < '00001.00013.00001.00002') { + addSauces(['#//www.bing.com/images/search?q=imgurl:%IMG&view=detailv2&iss=sbi#enterInsights']); + } + if (compareString < '00001.00013.00005.00000') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/^(#?\s*)http:\/\/regex\.info\/exif\.cgi/mg, '$1http://exif.regex.info/exif.cgi')); + } + addSauces(Config['sauces'].match(/# Known filename formats:(?:\n.+)*|$/)[0].split('\n')); + } + if (compareString < '00001.00013.00007.00002') { + setD('Require OP Quote Link', true); + } + if (compareString < '00001.00013.00008.00000') { + setD('Download Link', true); + } + if (compareString < '00001.00013.00009.00003') { + if (data['jsWhitelist'] != null) { + list = data['jsWhitelist'].split('\n'); + if (indexOf.call(list, 'https://cdnjs.cloudflare.com') < 0 && indexOf.call(list, 'https://cdn.mathjax.org') >= 0) { + set('jsWhitelist', data['jsWhitelist'] + '\n\nhttps://cdnjs.cloudflare.com'); + } + } + } + if (compareString < '00001.00014.00000.00006') { + if (data['siteSoftware'] != null) { + set('siteSoftware', data['siteSoftware'] + '\n4cdn.org yotsuba'); + } + } + if (compareString < '00001.00014.00003.00002') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/^(#?\s*)https:\/\/whatanime\.ga\//mg, '$1https://trace.moe/')); + } + } + if (compareString < '00001.00014.00004.00004') { + if ((data['siteSoftware'] != null) && !/^4channel\.org yotsuba$/m.test(data['siteSoftware'])) { + set('siteSoftware', data['siteSoftware'] + '\n4channel.org yotsuba'); + } + } + if (compareString < '00001.00014.00005.00000') { + ref7 = DataBoard.keys; + for (l = 0, len2 = ref7.length; l < len2; l++) { + db = ref7[l]; + if ((ref8 = data[db]) != null ? ref8.boards : void 0) { + ref9 = data[db], boards = ref9.boards, lastChecked = ref9.lastChecked; + data[db]['4chan.org'] = { + boards: boards, + lastChecked: lastChecked + }; + delete data[db].boards; + delete data[db].lastChecked; + set(db, data[db]); + } + } + if ((data['siteSoftware'] != null) && (data['siteProperties'] == null)) { + siteProperties = $.dict(); + ref10 = data['siteSoftware'].split('\n'); + for (m = 0, len3 = ref10.length; m < len3; m++) { + line = ref10[m]; + ref11 = line.split(' '), hostname = ref11[0], software = ref11[1]; + siteProperties[hostname] = { + software: software + }; + } + set('siteProperties', siteProperties); + } + } + if (compareString < '00001.00014.00006.00006') { + if (data['sauces'] != null) { + 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}-/')); + } + } + if (compareString < '00001.00014.00008.00000') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/https:\/\/www\.yandex\.com\/images\/search/g, 'https://yandex.com/images/search')); + } + } + if (compareString < '00001.00014.00009.00000') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/^(#?\s*)(?:http:)?\/\/(www\.pixiv\.net|www\.deviantart\.com|imgur\.com|flickr\.com)\//mg, '$1https://$2/')); + set('sauces', data['sauces'].replace(/https:\/\/yandex\.com\/images\/search\?rpt=imageview&img_url=%IMG/g, 'https://yandex.com/images/search?rpt=imageview&url=%IMG')); + } + } + if (compareString < '00001.00014.00009.00001') { + if ((data['Use Faster Image Host'] != null) && (data['fourchanImageHost'] == null)) { + set('fourchanImageHost', (data['Use Faster Image Host'] ? 'i.4cdn.org' : '')); + } + } + if (compareString < '00001.00014.00010.00001') { + if (data['Filter in Native Catalog'] == null) { + set('Filter in Native Catalog', false); + } + } + if (compareString < '00001.00014.00012.00008') { + if (data['boardnav'] == null) { + set('boardnav', "[ toggle-all ]\na-replace\nc-replace\ng-replace\nk-replace\nv-replace\nvg-replace\nvr-replace\nck-replace\nco-replace\nfit-replace\njp-replace\nmu-replace\nsp-replace\ntv-replace\nvp-replace\n[external-text:\"FAQ\",\"https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions\"]"); + } + } + if (compareString < '00001.00014.00016.00001') { + if (data['archiveLists'] != null) { + set('archiveLists', data['archiveLists'].replace('https://mayhemydg.github.io/archives.json/archives.json', 'https://nstepien.github.io/archives.json/archives.json')); + } + } + if (compareString < '00001.00014.00016.00007') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/https:\/\/www\.deviantart\.com\/gallery\/#\/d%\$1%\$2;regexp:\/\^\\w\+_by_\\w\+\[_-\]d\(\[\\da-z\]\{6\}\)\\b\|\^d\(\[\\da-z\]\{6\}\)-\[\\da-z\]\{8\}-\//g, 'javascript:void(open("https://www.deviantart.com/"+%$1.replace(/_/g,"-")+"/art/"+parseInt(%$2,36)));regexp:/^\\w+_by_(\\w+)[_-]d([\\da-z]{6})\\b/').replace(/\/\/imgops\.com\/%URL/g, '//imgops.com/start?url=%URL')); + } + } + if (compareString < '00001.00014.00017.00002') { + if (data['jsWhitelist'] != null) { + set('jsWhitelist', data['jsWhitelist'] + '\n\nhttps://hcaptcha.com\nhttps://*.hcaptcha.com'); + } + } + if (compareString < '00001.00014.00020.00004') { + if (data['archiveLists'] != null) { + set('archiveLists', data['archiveLists'].replace('https://nstepien.github.io/archives.json/archives.json', 'https://4chenz.github.io/archives.json/archives.json')); + } + } + if (compareString < '00001.00014.00022.00003') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/^#?\s*https:\/\/www\.google\.com\/searchbyimage\?image_url=%(IMG|T?URL)&safe=off(?=$|;)/mg, 'https://www.google.com/searchbyimage?sbisrc=4chanx&image_url=%$1&safe=off')); + if (compareString === '00001.00014.00022.00002' && !/\bsbisrc=/.test(data['sauces'])) { + set('sauces', data['sauces'].replace(/^#?\s*https:\/\/lens\.google\.com\/uploadbyurl\?url=%(IMG|T?URL)(?=$|;)/m, 'https://www.google.com/searchbyimage?sbisrc=4chanx&image_url=%$1&safe=off')); + } + } + addSauces(['#https://lens.google.com/uploadbyurl?url=%IMG;text:lens']); + } return changes; }, loadSettings: function(data, cb) { - if (data.version.split('.')[0] === '2') { + if (data.version.split('.')[0] === '2' && "Disable 4chan's extension" in data.Conf) { data = Settings.convertFrom.loadletter(data); } else if (data.version !== g.VERSION) { Settings.upgrade(data.Conf, data.version); @@ -10254,58 +13542,59 @@ Settings = (function() { }, filter: function(section) { var select; - $.extend(section, { - innerHTML: "
          " - }); + $.extend(section, {innerHTML: "
          "}); select = $('select', section); $.on(select, 'change', Settings.selectFilter); return Settings.selectFilter.call(select); }, selectFilter: function() { - var div, name, ta; + var div, filterTypes, name, ta; div = this.nextElementSibling; if ((name = this.value) !== 'guide') { + if (!$.hasOwn(Config.filter, name)) { + return; + } $.rmAll(div); ta = $.el('textarea', { name: name, className: 'field', spellcheck: false }); + $.on(ta, 'change', $.cb.value); $.get(name, Conf[name], function(item) { - return ta.value = item[name]; + ta.value = item[name]; + return $.add(div, ta); }); - $.on(ta, 'change', $.cb.value); - $.add(div, ta); return; } - $.extend(div, { - innerHTML: "
          Filter is disabled.

          Use regular expressions, one per line.
          Lines starting with a # will be ignored.
          For example, /weeaboo/i will filter posts containing the string \`weeaboo\`, case-insensitive.
          MD5 filtering uses exact string matching, not regular expressions.

            You can use these settings with each regular expression, separate them with semicolons:
          • Per boards, separate them with commas. It is global if not specified.
            For example: boards:a,jp;.
          • In case of a global rule, select boards to be excluded from the filter.
            For example: exclude:vg,v;.
          • Filter OPs only along with their threads (\`only\`), replies only (\`no\`), or both (\`yes\`, this is default).
            For example: op:only;, op:no; or op:yes;.
          • Overrule the \`Show Stubs\` setting if specified: create a stub (\`yes\`) or not (\`no\`).
            For example: stub:yes; or stub:no;.
          • Highlight instead of hiding. You can specify a class name to use with a userstyle.
            For example: highlight; or highlight:wallpaper;.
          • Highlighted OPs will have their threads put on top of the board index by default.
            For example: top:yes; or top:no;.

          Note: If you're using the native catalog rather than 4chan X's catalog, 4chan X's filters do not apply there.
          The native catalog has its own separate filter list.

          " + filterTypes = Object.keys(Config.filter).filter(function(x) { + return x !== 'general'; + }).map(function(x, i) { + return {innerHTML: ((i) ? "," : "") + "" + E(x)}; }); + $.extend(div, {innerHTML: "
          Filter is disabled.

          Use regular expressions, one per line.
          Lines starting with a # will be ignored.
          For example, /weeaboo/i will filter posts containing the string `weeaboo`, case-insensitive.
          MD5 and Unique ID filtering use exact string matching, not regular expressions.

            You can use these settings with each regular expression, separate them with semicolons:
          • Per boards, separate them with commas. It is global if not specified. Use sfw and nsfw to reference all worksafe or not-worksafe boards.
            For example: boards:a,jp;.
            To specify boards on a particular site, put the beginning of the domain and a slash character before the list.
            Any initial www. should not be included, and all 4chan domains are considered 4chan.org.
            For example: boards:4:a,jp,sama:a,z;.
            An asterisk can be used to specify all boards on a site.
            For example: boards:4:*;.
          • Select boards to be excluded from the filter. The syntax is the same as for the boards: option above.
            For example: exclude:vg,v;.
          • Filter OPs only along with their threads (`only`) or replies only (`no`).
            For example: op:only; or op:no;.
          • Filter only posts with files (`only`) or only posts without files (`no`).
            For example: file:only; or file:no;.
          • Overrule the `Show Stubs` setting if specified: create a stub (`yes`) or not (`no`).
            For example: stub:yes; or stub:no;.
          • Highlight instead of hiding. You can specify a class name to use with a userstyle.
            For example: highlight; or highlight:wallpaper;.
          • Highlighted OPs will have their threads put on top of the board index by default.
            For example: top:yes; or top:no;.
          • Show a desktop notification instead of hiding.
            For example: notify;.
          • Filters in the \"General\" section apply to multiple fields, by default subject,name,filename,comment.
            The fields can be specified with the type option, separated by commas.
            For example: type:" + E.cat(filterTypes) + ";.
            Types can also be combined with a + sign; this indicates the filter applies to the given fields joined by newlines.
            For example: type:filename+filesize+dimensions;.
          "}); return $('.warning', div).hidden = Conf['Filter']; }, sauce: function(section) { var ta; - $.extend(section, { - innerHTML: "
          Sauce is disabled.
          Lines starting with a # will be ignored.
          You can specify a display text by appending ;text:[text] to the URL.
          You can specify the applicable boards by appending ;boards:[board1],[board2].
          You can specify the applicable file types by appending ;types:[extension1],[extension2].
            These parameters will be replaced by their corresponding values:
          • %TURL: Thumbnail URL.
          • %URL: Full image URL.
          • %IMG: Full image URL for GIF, JPG, and PNG; thumbnail URL for other types.
          • %MD5: MD5 hash in base64.
          • %sMD5: MD5 hash in base64 using - and _.
          • %hMD5: MD5 hash in hexadecimal.
          • %name: Original file name.
          • %board: Current board.
          • %%, %semi: Literal % and ;.
          " - }); + $.extend(section, {innerHTML: "
          Sauce is disabled.
          These parameters will be replaced by their corresponding values in the URL and displayed text:
          • %IMG: Full image URL for GIF, JPG, and PNG; thumbnail URL for other types.
          • %URL: Full image URL.
          • %TURL: Thumbnail URL.
          • %name: Original file name.
          • %board: Current board.
          • %MD5: MD5 hash in base64.
          • %sMD5: MD5 hash in base64 using - and _.
          • %hMD5: MD5 hash in hexadecimal.
          • %$0: Matched regular expression within the filename.
          • %$1, %$2, %$3, ... : Subexpressions within the matched regular expression.
          • %%, %semi: Literal % and ;.
          Lines starting with a # will be ignored.
          You can specify a display text by appending ;text:[text] to the URL.
          You can specify the applicable boards/sites by appending ;boards:[board1],[board2]. See the Filter guide for details.
          You can specify the applicable file types by appending ;types:[extension1],[extension2].
          You can specify a regular expression the filename must match by appending ;regexp:[regular expression].
          "}); $('.warning', section).hidden = Conf['Sauce']; ta = $('textarea', section); $.get('sauces', Conf['sauces'], function(item) { - return ta.value = item['sauces']; + ta.value = item['sauces']; + return ta.hidden = false; }); return $.on(ta, 'change', $.cb.value); }, advanced: function(section) { - var applyCSS, boardSelect, customCSS, event, input, inputs, interval, items, itemsArchive, j, k, l, len, len1, len2, len3, m, name, ref, ref1, ref2, ref3, table, updateArchives, warning; - $.extend(section, { - innerHTML: "
          Archives
          404 Redirect is disabled.
          Thread redirectionPost fetchingFile redirection

          Archive Lists: Each line below should be an archive list in this format or a URL to load an archive list from.
          Archive properties can be overriden by another item with the same uid (or if absent, its name).
          Last updated:
          Captcha Language
          Choose from list of language codes. Leave blank to autoselect.
          Custom Board Navigation
          New lines will be converted into spaces.

          In the following examples for /g/, g can be changed to a different board ID (a, b, etc...), the current board (current), or the Twitter link (@).
          Board link: g
          Archive link: g-archive
          Internal archive link: g-expired
          Title link: g-title
          Board link (Replace with title when on that board): g-replace
          Full text link: g-full
          Custom text link: g-text:"Install Gentoo"
          Index-only link: g-index
          Catalog-only link: g-catalog
          Index mode: g-mode:"infinite scrolling"
          Index sort: g-sort:"creation date"
          External link: external-text:"Google","http://www.google.com"
          Combinations are possible: g-index-text:"Technology Index"
          Full board list toggle: toggle-all

          [ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:"Piracy"]
          will give you
          [ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
          if you are on /g/.
          Time Formatting is disabled.
          :
          Day: %a, %A, %d, %e
          Month: %m, %b, %B
          Year: %y, %Y
          Hour: %k, %H, %l, %I, %p, %P
          Minute: %M
          Second: %S
          Literal %: %%
          Quote Backlinks formatting is disabled.
          :
          File Info Formatting is disabled.
          :
          Link: %l (truncated), %L (untruncated), %T (4chan filename)
          Filename: %n (truncated), %N (untruncated), %t (4chan filename)
          Download button: %d
          Spoiler indicator: %p
          Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
          Resolution: %r (Displays 'PDF' for PDF files)
          Tag: %g
          Literal %: %%
          Quick Reply Personas

          One item per line.
          Items will be added in the relevant input's auto-completion list.
          Password items will always be used, since there is no password input.
          Lines starting with a # will be ignored.

            You can use these settings with each item, separate them with semicolons:
          • Possible items are: name, options (or equivalently email), subject and password.
          • Wrap values of items with quotes, like this: options:"sage".
          • Force values as defaults with the always keyword, for example: options:"sage";always.
          • Select specific boards for an item, separated with commas, for example: options:"sage";boards:jp;always.
          Unread Favicon is disabled.
          Thread Updater is disabled.
          Interval: seconds
          Custom Cooldown Time
          Seconds:
          Javascript Whitelist
          Sources from which Javascript is allowed to be loaded by Content Security Policy.
          " - }); + var applyCSS, boardSelect, customCSS, event, input, inputs, interval, items, itemsArchive, j, k, l, len, len1, len2, len3, listImageHost, m, name, ref, ref1, ref2, ref3, ref4, table, textContent, updateArchives, warning; + $.extend(section, {innerHTML: "
          Archives
          404 Redirect is disabled.
          Thread redirectionPost fetchingFile redirection

          Archive Lists: Each line below should be an archive list in this format or a URL to load an archive list from.
          Archive properties can be overriden by another item with the same uid (or if absent, its name).
          Last updated:
          External Catalog
          External Catalog is disabled. This will be used only as a fallback.
          URLs of external catalog sites, where %board is to be replaced by the board name.
          Each URL should be followed by ;boards: and optionally ;exclude: and a list of supported/excluded boards in the format explained in the Filter guide.
          Override 4chan Image Host
          Change 4chan image links to this domain. Leave blank for no change.
          Captcha Language
          Choose from list of language codes. Leave blank to autoselect.
          Custom Board Navigation
          New lines will be converted into spaces.

          In the following examples for /g/, g can be changed to a different board ID (a, b, etc...), the current board (current), or the Twitter link (@).
          Board link: g
          Archive link: g-archive
          Internal archive link: g-expired
          Title link: g-title
          Board link (Replace with title when on that board): g-replace
          Full text link: g-full
          Custom text link: g-text:"Install Gentoo"
          Index-only link: g-index
          Catalog-only link: g-catalog
          Index mode: g-mode:"infinite scrolling"
          Index sort: g-sort:"creation date rev"
          External link: external-text:"Google","http://www.google.com"
          Open in new tab: g-nt
          Combinations are possible: g-index-text:"Technology Index"
          Full board list toggle: toggle-all

          [ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:"Piracy"]
          will give you
          [ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
          if you are on /g/.
          Time Formatting is disabled.
          :
          Day: %a, %A, %d, %e
          Month: %m, %b, %B
          Year: %y, %Y
          Hour: %k, %H, %l, %I, %p, %P
          Minute: %M
          Second: %S
          Literal %: %%
          Quote Backlinks formatting is disabled.
          :
          Default pasted content filename
          .png
          File Info Formatting is disabled.
          :
          Link: %l (truncated), %L (untruncated), %T (4chan filename)
          Filename: %n (truncated), %N (untruncated), %t (4chan filename)
          Download button: %d
          Quick filter MD5: %f
          Spoiler indicator: %p
          Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
          Resolution: %r (Displays 'PDF' for PDF files)
          Tag: %g
          Literal %: %%
          Quick Reply Personas

          One item per line.
          Items will be added in the relevant input's auto-completion list.
          Password items will always be used, since there is no password input.
          Lines starting with a # will be ignored.

            You can use these settings with each item, separate them with semicolons:
          • Possible items are: name, options (or equivalently email), subject and password.
          • Wrap values of items with quotes, like this: options:"sage".
          • Force values as defaults with the always keyword, for example: options:"sage";always.
          • Select specific boards for an item, separated with commas, for example: options:"sage";boards:jp;always.
          Unread Favicon is disabled.
          Thread Updater is disabled.
          Interval: seconds
          Custom Cooldown Time
          Seconds:
          For more information about customizing 4chan X's CSS, see the styling guide.
          Javascript Whitelist
          Sources from which Javascript is allowed to be loaded by Content Security Policy.
          Lines starting with a # will be ignored. Remove or comment out all lines to allow everything.
          Known Banners
          List of known banners, used for click-to-change feature.
          "}); ref = $$('.warning', section); for (j = 0, len = ref.length; j < len; j++) { warning = ref[j]; warning.hidden = Conf[warning.dataset.feature]; } - inputs = {}; + inputs = $.dict(); ref1 = $$('[name]', section); for (k = 0, len1 = ref1.length; k < len1; k++) { input = ref1[k]; @@ -10316,13 +13605,14 @@ Settings = (function() { Conf['lastarchivecheck'] = 0; return $.id('lastarchivecheck').textContent = 'never'; }); - items = {}; - ref2 = ['archiveLists', 'archiveAutoUpdate', 'captchaLanguage', 'boardnav', 'time', 'backlink', 'fileInfo', 'QR.personas', 'favicon', 'usercss', 'customCooldown', 'jsWhitelist']; - for (l = 0, len2 = ref2.length; l < len2; l++) { - name = ref2[l]; - items[name] = Conf[name]; + items = $.dict(); + for (name in inputs) { input = inputs[name]; - event = name === 'archiveLists' || name === 'archiveAutoUpdate' || name === 'QR.personas' || name === 'favicon' || name === 'usercss' ? 'change' : 'input'; + if (!(name !== 'Interval' && name !== 'Custom CSS')) { + continue; + } + items[name] = Conf[name]; + event = (input.nodeName === 'SELECT' || ((ref2 = input.type) === 'checkbox' || ref2 === 'radio') || (input.nodeName === 'TEXTAREA' && !(name in Settings))) ? 'change' : 'input'; $.on(input, event, $.cb[input.type === 'checkbox' ? 'checked' : 'value']); if (name in Settings) { $.on(input, event, Settings[name]); @@ -10334,11 +13624,20 @@ Settings = (function() { val = items[key]; input = inputs[key]; input[input.type === 'checkbox' ? 'checked' : 'value'] = val; + input.hidden = false; if (key in Settings) { Settings[key].call(input); } } }); + listImageHost = $.id('list-fourchanImageHost'); + ref3 = ImageHost.suggestions; + for (l = 0, len2 = ref3.length; l < len2; l++) { + textContent = ref3[l]; + $.add(listImageHost, $.el('option', { + textContent: textContent + })); + } interval = inputs['Interval']; customCSS = inputs['Custom CSS']; applyCSS = $('#apply-css', section); @@ -10351,10 +13650,10 @@ Settings = (function() { $.on(applyCSS, 'click', function() { return CustomCSS.update(); }); - itemsArchive = {}; - ref3 = ['archives', 'selectedArchives', 'lastarchivecheck']; - for (m = 0, len3 = ref3.length; m < len3; m++) { - name = ref3[m]; + itemsArchive = $.dict(); + ref4 = ['archives', 'selectedArchives', 'lastarchivecheck']; + for (m = 0, len3 = ref4.length; m < len3; m++) { + name = ref4[m]; itemsArchive[name] = Conf[name]; } $.get(itemsArchive, function(itemsArchive) { @@ -10383,7 +13682,7 @@ Settings = (function() { tbody = $('tbody', section); $.rmAll(boardSelect); $.rmAll(tbody); - archBoards = {}; + archBoards = $.dict(); ref = Conf['archives']; for (j = 0, len = ref.length; j < len; j++) { ref1 = ref[j], uid = ref1.uid, name = ref1.name, boards = ref1.boards, files = ref1.files, software = ref1.software; @@ -10472,9 +13771,7 @@ Settings = (function() { textContent: archive[1] })); } - $.extend(td, { - innerHTML: "" - }); + $.extend(td, {innerHTML: ""}); select = td.firstElementChild; if (!(select.disabled = length === 1)) { select.setAttribute('data-boardid', boardID); @@ -10489,7 +13786,7 @@ Settings = (function() { return function(arg) { var name1, selectedArchives; selectedArchives = arg.selectedArchives; - (selectedArchives[name1 = _this.dataset.boardid] || (selectedArchives[name1] = {}))[_this.dataset.type] = JSON.parse(_this.value); + (selectedArchives[name1 = _this.dataset.boardid] || (selectedArchives[name1] = $.dict()))[_this.dataset.type] = JSON.parse(_this.value); $.set('selectedArchives', selectedArchives); Conf['selectedArchives'] = selectedArchives; return Redirect.selectArchives(); @@ -10502,6 +13799,9 @@ Settings = (function() { time: function() { return this.nextElementSibling.textContent = Time.format(this.value, new Date()); }, + timeLocale: function() { + return Settings.time.call($('[name=time]', Settings.dialog)); + }, backlink: function() { return this.nextElementSibling.textContent = this.value.replace(/%(?:id|%)/g, function(x) { return { @@ -10515,7 +13815,7 @@ Settings = (function() { data = { isReply: true, file: { - url: '//i.4cdn.org/g/1334437723720.jpg', + url: "//" + (ImageHost.host()) + "/g/1334437723720.jpg", name: 'd9bb2efc98dd0df141a94399ff5880b7.jpg', size: '276 KB', sizeInBytes: 276 * 1024, @@ -10529,16 +13829,21 @@ Settings = (function() { return FileInfo.format(this.value, data, this.nextElementSibling); }, favicon: function() { - var img; + var f, i, icon, img, j, len, ref; Favicon["switch"](); if (g.VIEW === 'thread' && Conf['Unread Favicon']) { Unread.update(); } img = this.nextElementSibling.children; - img[0].src = Favicon["default"]; - img[1].src = Favicon.unreadSFW; - img[2].src = Favicon.unreadNSFW; - return img[3].src = Favicon.unreadDead; + f = Favicon; + ref = [f.SFW, f.unreadSFW, f.unreadSFWY, f.NSFW, f.unreadNSFW, f.unreadNSFWY, f.dead, f.unreadDead, f.unreadDeadY]; + for (i = j = 0, len = ref.length; j < len; i = ++j) { + icon = ref[i]; + if (!img[i]) { + $.add(this.nextElementSibling, $.el('img')); + } + img[i].src = icon; + } }, togglecss: function() { if ($('textarea[name=usercss]', $.x('ancestor::fieldset[1]', this)).disabled = $.id('apply-css').disabled = !this.checked) { @@ -10550,19 +13855,15 @@ Settings = (function() { }, keybinds: function(section) { var arr, input, inputs, items, key, ref, tbody, tr; - $.extend(section, { - innerHTML: "
          Keybinds are disabled.
          Allowed keys: a-z, 0-9, Ctrl, Shift, Alt, Meta, Enter, Esc, Up, Down, Right, Left.
          Press Backspace to disable a keybind.
          ActionsKeybinds
          " - }); + $.extend(section, {innerHTML: "
          Keybinds are disabled.
          Allowed keys: a-z, 0-9, Ctrl, Shift, Alt, Meta, Enter, Esc, Up, Down, Right, Left.
          Press Backspace to disable a keybind.
          ActionsKeybinds
          "}); $('.warning', section).hidden = Conf['Keybinds']; tbody = $('tbody', section); - items = {}; - inputs = {}; + items = $.dict(); + inputs = $.dict(); ref = Config.hotkeys; for (key in ref) { arr = ref[key]; - tr = $.el('tr', { - innerHTML: "" + E(arr[1]) + "" - }); + tr = $.el('tr', {innerHTML: "" + E(arr[1]) + ""}); input = $('input', tr); input.name = key; input.spellcheck = false; @@ -10598,22 +13899,24 @@ Settings = (function() { }).call(this); +Test = (function() { + return Test; + +}).call(this); + UI = (function() { - var Menu, checkbox, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove, + var Menu, UI, checkbox, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, slice = [].slice; - dialog = function(id, position, properties) { + dialog = function(id, properties) { var child, el, i, len, move, ref; el = $.el('div', { className: 'dialog', id: id }); $.extend(el, properties); - el.style.cssText = position; - $.get(id + ".position", position, function(item) { - return el.style.cssText = item[id + ".position"]; - }); + el.style.cssText = Conf[id + ".position"]; move = $('.move', el); $.on(move, 'touchstart mousedown', dragstart); ref = move.children; @@ -10706,7 +14009,7 @@ UI = (function() { $.on(d, 'click CloseMenu', this.close); $.on(d, 'scroll', this.setPosition); $.on(window, 'resize', this.setPosition); - $.add(button, menu); + $.after(button, menu); this.setPosition(); entry = $('.entry', menu); this.focus(entry); @@ -10739,8 +14042,8 @@ UI = (function() { if (!entry.open(data)) { return; } - } catch (_error) { - err = _error; + } catch (error) { + err = error; Main.handleErrors({ message: "Error in building the " + this.type + " menu.", error: err @@ -10943,11 +14246,11 @@ UI = (function() { var bottom, clientX, clientY, left, right, style, top; clientX = e.clientX, clientY = e.clientY; left = clientX - this.dx; - left = left < 10 ? 0 : this.width - left < 10 ? null : left / this.screenWidth * 100 + '%'; + left = left < 10 ? 0 : this.width - left < 10 ? '' : left / this.screenWidth * 100 + '%'; top = clientY - this.dy; - top = top < (10 + this.topBorder) ? this.topBorder + 'px' : this.height - top < (10 + this.bottomBorder) ? null : top / this.screenHeight * 100 + '%'; - right = left === null ? 0 : null; - bottom = top === null ? this.bottomBorder + 'px' : null; + top = top < (10 + this.topBorder) ? this.topBorder + 'px' : this.height - top < (10 + this.bottomBorder) ? '' : top / this.screenHeight * 100 + '%'; + right = left === '' ? 0 : ''; + bottom = top === '' ? this.bottomBorder + 'px' : ''; style = this.style; style.left = left; style.right = right; @@ -10979,8 +14282,9 @@ UI = (function() { }; hoverstart = function(arg) { - var cb, el, endEvents, height, latestEvent, noRemove, o, ref, root; - root = arg.root, el = arg.el, latestEvent = arg.latestEvent, endEvents = arg.endEvents, height = arg.height, cb = arg.cb, noRemove = arg.noRemove; + var cb, el, endEvents, height, latestEvent, noRemove, o, rect, ref, root, width; + root = arg.root, el = arg.el, latestEvent = arg.latestEvent, endEvents = arg.endEvents, height = arg.height, width = arg.width, cb = arg.cb, noRemove = arg.noRemove; + rect = root.getBoundingClientRect(); o = { root: root, el: el, @@ -10992,7 +14296,10 @@ UI = (function() { clientHeight: doc.clientHeight, clientWidth: doc.clientWidth, height: height, - noRemove: noRemove + width: width, + noRemove: noRemove, + clientX: (rect.left + rect.right) / 2, + clientY: (rect.top + rect.bottom) / 2 }; o.hover = hover.bind(o); o.hoverend = hoverend.bind(o); @@ -11020,16 +14327,22 @@ UI = (function() { hoverstart.padding = 25; hover = function(e) { - var clientX, clientY, height, left, ref, right, style, threshold, top; + var clientX, clientY, height, left, marginX, ref, ref1, right, style, threshold, top, width; this.latestEvent = e; height = (this.height || this.el.offsetHeight) + hoverstart.padding; - clientX = e.clientX, clientY = e.clientY; + width = this.width || this.el.offsetWidth; + ref = Conf['Follow Cursor'] ? e : this, clientX = ref.clientX, clientY = ref.clientY; top = this.isImage ? Math.max(0, clientY * (this.clientHeight - height) / this.clientHeight) : Math.max(0, Math.min(this.clientHeight - height, clientY - 120)); threshold = this.clientWidth / 2; if (!this.isImage) { threshold = Math.max(threshold, this.clientWidth - 400); } - ref = clientX <= threshold ? [clientX + 45 + 'px', null] : [null, this.clientWidth - clientX + 45 + 'px'], left = ref[0], right = ref[1]; + marginX = (clientX <= threshold ? clientX : this.clientWidth - clientX) + 45; + if (this.isImage) { + marginX = Math.min(marginX, this.clientWidth - width); + } + marginX += 'px'; + ref1 = clientX <= threshold ? [marginX, ''] : ['', marginX], left = ref1[0], right = ref1[1]; style = this.style; style.top = top + 'px'; style.left = left; @@ -11067,13 +14380,15 @@ UI = (function() { return label; }; - return { + UI = { dialog: dialog, Menu: Menu, hover: hoverstart, checkbox: checkbox }; + return UI; + }).call(this); FappeTyme = (function() { @@ -11082,7 +14397,7 @@ FappeTyme = (function() { FappeTyme = { init: function() { var el, i, indicator, lc, len, ref, ref1, type; - if (!((Conf['Fappe Tyme'] || Conf['Werk Tyme']) && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { + if (!((Conf['Fappe Tyme'] || Conf['Werk Tyme']) && ((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive'))) { return; } this.nodes = {}; @@ -11115,7 +14430,7 @@ FappeTyme = (function() { }); $.on(indicator, 'click', function() { var check; - check = FappeTyme.nodes[this.parentNode.id.replace('shortcut-', '')]; + check = $.getOwn(FappeTyme.nodes, this.parentNode.id.replace('shortcut-', '')); check.checked = !check.checked; return $.event('change', null, check); }); @@ -11134,11 +14449,11 @@ FappeTyme = (function() { }); }, node: function() { - return this.nodes.root.classList.toggle('noFile', !this.file); + return this.nodes.root.classList.toggle('noFile', !this.files.length); }, catalogNode: function() { var file, filename; - file = this.thread.OP.file; + file = this.thread.OP.files[0]; if (!file) { return; } @@ -11170,7 +14485,7 @@ Gallery = (function() { Gallery = { init: function() { var el, ref; - if (!(this.enabled = Conf['Gallery'] && ((ref = g.VIEW) === 'index' || ref === 'thread') && g.BOARD.ID !== 'f')) { + if (!(this.enabled = Conf['Gallery'] && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { return; } this.delay = Conf['Slide Delay']; @@ -11188,20 +14503,28 @@ Gallery = (function() { }); }, node: function() { - var ref; - if (!((ref = this.file) != null ? ref.thumb : void 0)) { - return; - } - if (Gallery.nodes) { - Gallery.generateThumb(this); - Gallery.nodes.total.textContent = Gallery.images.length; - } - if (!Conf['Image Expansion']) { - return $.on(this.file.thumb.parentNode, 'click', Gallery.cb.image); + var file, i, len, ref, results; + ref = this.files; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + if (!file.thumb) { + continue; + } + if (Gallery.nodes) { + Gallery.generateThumb(this, file); + Gallery.nodes.total.textContent = Gallery.images.length; + } + if (!(Conf['Image Expansion'] || (g.SITE.software === 'tinyboard' && Main.jsEnabled))) { + results.push($.on(file.thumbLink, 'click', Gallery.cb.image)); + } else { + results.push(void 0); + } } + return results; }, build: function(image) { - var candidate, cb, dialog, entry, file, i, j, key, len, len1, menuButton, nodes, post, ref, ref1, ref2, ref3, thumb, value; + var candidate, cb, dialog, entry, file, i, j, k, key, len, len1, len2, menuButton, nodes, post, postThumb, ref, ref1, ref2, ref3, thumb, value; cb = Gallery.cb; if (Conf['Fullscreen Gallery']) { $.one(d, 'fullscreenchange mozfullscreenchange webkitfullscreenchange', function() { @@ -11216,20 +14539,19 @@ Gallery = (function() { } Gallery.images = []; nodes = Gallery.nodes = {}; - Gallery.fullIDs = {}; + Gallery.fileIDs = $.dict(); Gallery.slideshow = false; nodes.el = dialog = $.el('div', { id: 'a-gallery' }); - $.extend(dialog, { - innerHTML: "
          " - }); + $.extend(dialog, {innerHTML: "
          "}); ref = { buttons: '.gal-buttons', frame: '.gal-image', name: '.gal-name', count: '.count', total: '.total', + sauce: '.gal-sauce', thumbs: '.gal-thumbnails', next: '.gal-image a', current: '.gal-image img' @@ -11265,18 +14587,24 @@ Gallery = (function() { $.off(d, 'keydown', Keybinds.keydown); } $.on(window, 'resize', Gallery.cb.setHeight); - ref2 = $$('.post .file'); + ref2 = $$(g.SITE.selectors.file.thumb); for (j = 0, len1 = ref2.length; j < len1; j++) { - file = ref2[j]; - post = Get.postFromNode(file); - if (!((ref3 = post.file) != null ? ref3.thumb : void 0)) { + postThumb = ref2[j]; + if (!(post = Get.postFromNode(postThumb))) { continue; } - Gallery.generateThumb(post); - if (!image && Gallery.fullIDs[post.fullID]) { - candidate = post.file.thumb.parentNode; - if (Header.getTopOf(candidate) + candidate.getBoundingClientRect().height >= 0) { - image = candidate; + ref3 = post.files; + for (k = 0, len2 = ref3.length; k < len2; k++) { + file = ref3[k]; + if (!file.thumb) { + continue; + } + Gallery.generateThumb(post, file); + if (!image && Gallery.fileIDs[post.fullID + "." + file.index]) { + candidate = file.thumbLink; + if (Header.getTopOf(candidate) + candidate.getBoundingClientRect().height >= 0) { + image = candidate; + } } } } @@ -11294,27 +14622,28 @@ Gallery = (function() { doc.style.overflow = 'hidden'; return nodes.total.textContent = Gallery.images.length; }, - generateThumb: function(post) { + generateThumb: function(post, file) { var thumb, thumbImg; if (post.isClone || post.isHidden) { return; } - if (!(post.file && post.file.thumb && (post.file.isImage || post.file.isVideo || Conf['PDF in Gallery']))) { + if (!(file && file.thumb && (file.isImage || file.isVideo || Conf['PDF in Gallery']))) { return; } - if (Gallery.fullIDs[post.fullID]) { + if (Gallery.fileIDs[post.fullID + "." + file.index]) { return; } - Gallery.fullIDs[post.fullID] = true; + Gallery.fileIDs[post.fullID + "." + file.index] = true; thumb = $.el('a', { className: 'gal-thumb', - href: post.file.url, + href: file.url, target: '_blank', - title: post.file.name + title: file.name }); thumb.dataset.id = Gallery.images.length; thumb.dataset.post = post.fullID; - thumbImg = post.file.thumb.cloneNode(false); + thumb.dataset.file = file.index; + thumbImg = file.thumb.cloneNode(false); thumbImg.style.cssText = ''; $.add(thumb, thumbImg); $.on(thumb, 'click', Gallery.cb.open); @@ -11324,20 +14653,20 @@ Gallery = (function() { load: function(thumb, errorCB) { var elType, ext, file; ext = thumb.href.match(/\w*$/); - elType = { + elType = $.getOwn({ 'webm': 'video', + 'mp4': 'video', + 'ogv': 'video', 'pdf': 'iframe' - }[ext] || 'img'; - file = $.el(elType, { - title: thumb.title - }); + }, ext) || 'img'; + file = $.el(elType); $.extend(file.dataset, thumb.dataset); $.on(file, 'error', errorCB); file.src = thumb.href; return file; }, open: function(thumb) { - var el, file, newID, nodes, oldID, post, ref; + var el, file, i, len, link, newID, node, nodes, oldID, post, ref, ref1, sauces; nodes = Gallery.nodes; oldID = +nodes.current.dataset.id; newID = +thumb.dataset.id; @@ -11374,12 +14703,24 @@ Gallery = (function() { nodes.name.href = thumb.href; nodes.frame.scrollTop = 0; nodes.next.focus(); + $.rmAll(nodes.sauce); + if (Conf['Sauce'] && Sauce.links && (post = g.posts.get(file.dataset.post))) { + sauces = []; + ref1 = Sauce.links; + for (i = 0, len = ref1.length; i < len; i++) { + link = ref1[i]; + if ((node = Sauce.createSauceLink(link, post, post.files[+file.dataset.file]))) { + sauces.push($.tn(' '), node); + } + } + $.add(nodes.sauce, sauces); + } if (Gallery.slideshow && (newID > oldID || (oldID === Gallery.images.length - 1 && newID === 0))) { Gallery.setupTimer(); } else { Gallery.cb.stop(); } - if (Conf['Scroll to Post'] && (post = g.posts[file.dataset.post])) { + if (Conf['Scroll to Post'] && (post = g.posts.get(file.dataset.post))) { Header.scrollTo(post.nodes.root); } if (isNaN(oldID) || newID === (oldID + 1) % Gallery.images.length) { @@ -11387,19 +14728,21 @@ Gallery = (function() { } }, error: function() { - var ref; + var file, post, ref; if (((ref = this.error) != null ? ref.code : void 0) === MediaError.MEDIA_ERR_DECODE) { return new Notice('error', 'Corrupt or unplayable video', 30); } - if (this.src.split('/')[2] !== 'i.4cdn.org') { + if (ImageCommon.isFromArchive(this)) { return; } - return ImageCommon.error(this, g.posts[this.dataset.post], null, (function(_this) { + post = g.posts.get(this.dataset.post); + file = post.files[+this.dataset.file]; + return ImageCommon.error(this, post, file, null, (function(_this) { return function(url) { if (!url) { return; } - Gallery.images[_this.dataset.id].href = url; + Gallery.images[+_this.dataset.id].href = url; if (Gallery.nodes.current === _this) { return _this.src = url; } @@ -11454,17 +14797,22 @@ Gallery = (function() { case Conf['Close']: case Conf['Open Gallery']: return Gallery.cb.close; - case 'Right': + case Conf['Next Gallery Image']: return Gallery.cb.next; - case 'Enter': + case Conf['Advance Gallery']: return Gallery.cb.advance; - case 'Left': - case '': + case Conf['Previous Gallery Image']: return Gallery.cb.prev; case Conf['Pause']: return Gallery.cb.pause; case Conf['Slideshow']: return Gallery.cb.toggleSlideshow; + case Conf['Rotate image anticlockwise']: + return Gallery.cb.rotateLeft; + case Conf['Rotate image clockwise']: + return Gallery.cb.rotateRight; + case Conf['Download Gallery Image']: + return Gallery.cb.download; } })(); if (!cb) { @@ -11518,6 +14866,11 @@ Gallery = (function() { toggleSlideshow: function() { return Gallery.cb[Gallery.slideshow ? 'stop' : 'start'](); }, + download: function() { + var name; + name = $('.gal-name'); + return name.click(); + }, pause: function() { var current; Gallery.cb.stop(); @@ -11544,6 +14897,22 @@ Gallery = (function() { $.rmClass(Gallery.nodes.buttons, 'gal-playing'); return Gallery.slideshow = false; }, + rotateLeft: function() { + return Gallery.cb.rotate(270); + }, + rotateRight: function() { + return Gallery.cb.rotate(90); + }, + rotate: $.debounce(100, function(delta) { + var current; + current = Gallery.nodes.current; + if (current.nodeName === 'IFRAME') { + return; + } + current.dataRotate = ((current.dataRotate || 0) + delta) % 360; + current.style.transform = "rotate(" + current.dataRotate + "deg)"; + return Gallery.cb.setHeight(); + }), close: function() { $.off(Gallery.nodes.current, 'error', Gallery.error); ImageCommon.pause(Gallery.nodes.current); @@ -11559,7 +14928,7 @@ Gallery = (function() { } } delete Gallery.nodes; - delete Gallery.fullIDs; + delete Gallery.fileIDs; doc.style.overflow = ''; $.off(d, 'keydown', Gallery.cb.keybinds); if (Conf['Keybinds']) { @@ -11572,16 +14941,29 @@ Gallery = (function() { return (this.checked ? $.addClass : $.rmClass)(doc, "gal-" + (this.name.toLowerCase().replace(/\s+/g, '-'))); }, setHeight: $.debounce(100, function() { - var current, dim, frame, height, minHeight, ref, ref1, ref2, style, width; + var containerHeight, containerWidth, current, dim, frame, height, margin, minHeight, ref, ref1, ref2, ref3, style, width; ref = Gallery.nodes, current = ref.current, frame = ref.frame; style = current.style; - if (Conf['Stretch to Fit'] && (dim = (ref1 = g.posts[current.dataset.post]) != null ? ref1.file.dimensions : void 0)) { + if (Conf['Stretch to Fit'] && (dim = (ref1 = g.posts.get(current.dataset.post)) != null ? ref1.files[+current.dataset.file].dimensions : void 0)) { ref2 = dim.split('x'), width = ref2[0], height = ref2[1]; - minHeight = Math.min(doc.clientHeight - 25, height / width * frame.clientWidth); + containerWidth = frame.clientWidth; + containerHeight = doc.clientHeight - 25; + if ((current.dataRotate || 0) % 180 === 90) { + ref3 = [containerHeight, containerWidth], containerWidth = ref3[0], containerHeight = ref3[1]; + } + minHeight = Math.min(containerHeight, height / width * containerWidth); style.minHeight = minHeight + 'px'; - return style.minWidth = (width / height * minHeight) + 'px'; + style.minWidth = (width / height * minHeight) + 'px'; } else { - return style.minHeight = style.minWidth = null; + style.minHeight = style.minWidth = ''; + } + if ((current.dataRotate || 0) % 180 === 90) { + style.maxWidth = Conf['Fit Height'] ? (doc.clientHeight - 25) + "px" : 'none'; + style.maxHeight = Conf['Fit Width'] ? frame.clientWidth + "px" : 'none'; + margin = (current.clientWidth - current.clientHeight) / 2; + return style.margin = margin + "px " + (-margin) + "px"; + } else { + return style.maxWidth = style.maxHeight = style.margin = ''; } }), setDelay: function() { @@ -11632,9 +15014,7 @@ Gallery = (function() { } return results; })(); - delayLabel = $.el('label', { - innerHTML: "Slide Delay: " - }); + delayLabel = $.el('label', {innerHTML: "Slide Delay: "}); delayInput = delayLabel.firstElementChild; delayInput.value = Gallery.delay; $.on(delayInput, 'change', Gallery.cb.setDelay); @@ -11652,7 +15032,8 @@ Gallery = (function() { }).call(this); ImageCommon = (function() { - var ImageCommon; + var ImageCommon, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; ImageCommon = { pause: function(video) { @@ -11663,6 +15044,17 @@ ImageCommon = (function() { $.off(video, 'volumechange', Volume.change); return video.muted = true; }, + rewind: function(el) { + if (el.nodeName === 'VIDEO') { + if (el.readyState >= el.HAVE_METADATA) { + return el.currentTime = 0; + } + } else if (/\.gif$/.test(el.src)) { + return $.queueTask(function() { + return el.src = el.src; + }); + } + }, pushCache: function(el) { ImageCommon.cache = el; return $.on(el, 'error', ImageCommon.cacheError); @@ -11679,74 +15071,95 @@ ImageCommon = (function() { return delete ImageCommon.cache; } }, - decodeError: function(file, post) { + decodeError: function(file, fileObj) { var message, ref; if (((ref = file.error) != null ? ref.code : void 0) !== MediaError.MEDIA_ERR_DECODE) { return false; } - if (!(message = $('.warning', post.file.thumb.parentNode))) { + if (!(message = $('.warning', fileObj.thumb.parentNode))) { message = $.el('div', { className: 'warning' }); - $.after(post.file.thumb, message); + $.after(fileObj.thumb, message); } message.textContent = 'Error: Corrupt or unplayable video'; return true; }, - error: function(file, post, delay, cb) { - var URL, redirect, src, timeoutID; - src = post.file.url.split('/'); - URL = Redirect.to('file', { - boardID: post.board.ID, - filename: src[src.length - 1] - }); - if (!(Conf['404 Redirect'] && URL && Redirect.securityCheck(URL))) { - URL = null; + isFromArchive: function(file) { + return g.SITE.software === 'yotsuba' && !ImageHost.test(file.src.split('/')[2]); + }, + error: function(file, post, fileObj, delay, cb) { + var base, parseJSON, redirect, src, threadJSON, timeoutID, url; + src = fileObj.url.split('/'); + url = null; + if (g.SITE.software === 'yotsuba' && Conf['404 Redirect']) { + url = Redirect.to('file', { + boardID: post.board.ID, + filename: src[src.length - 1] + }); + } + if (!(url && Redirect.securityCheck(url))) { + url = null; } - if ((post.isDead || post.file.isDead) && file.src.split('/')[2] === 'i.4cdn.org') { - return cb(URL); + if ((post.isDead || fileObj.isDead) && !ImageCommon.isFromArchive(file)) { + return cb(url); } if (delay != null) { timeoutID = setTimeout((function() { - return cb(URL); + return cb(url); }), delay); } - if (post.isDead || post.file.isDead) { + if (post.isDead || fileObj.isDead) { return; } redirect = function() { - if (file.src.split('/')[2] === 'i.4cdn.org') { + if (!ImageCommon.isFromArchive(file)) { if (delay != null) { clearTimeout(timeoutID); } - return cb(URL); + return cb(url); + } + }; + threadJSON = typeof (base = g.SITE.urls).threadJSON === "function" ? base.threadJSON(post) : void 0; + if (!threadJSON) { + return; + } + parseJSON = function(isArchiveURL) { + var archivedThreadJSON, base1, i, len, postObj, ref, ref1; + if (this.status === 404) { + if (!isArchiveURL && (archivedThreadJSON = typeof (base1 = g.SITE.urls).archivedThreadJSON === "function" ? base1.archivedThreadJSON(post) : void 0)) { + $.ajax(archivedThreadJSON, { + onloadend: function() { + return parseJSON.call(this, true); + } + }); + } else { + post.kill(!post.isClone, fileObj.index); + } + } + if (this.status !== 200) { + return redirect(); + } + ref = this.response.posts; + for (i = 0, len = ref.length; i < len; i++) { + postObj = ref[i]; + if (postObj.no === post.ID) { + break; + } + } + if (postObj.no !== post.ID) { + post.kill(); + return redirect(); + } else if (ref1 = fileObj.docIndex, indexOf.call(g.SITE.Build.parseJSON(postObj, post.board).filesDeleted, ref1) >= 0) { + post.kill(true); + return redirect(); + } else { + return url = fileObj.url; } }; - return $.ajax("//a.4cdn.org/" + post.board + "/thread/" + post.thread + ".json", { - onload: function() { - var i, len, postObj, ref; - if (this.status === 404) { - post.kill(!post.isClone); - } - if (this.status !== 200) { - return redirect(); - } - ref = this.response.posts; - for (i = 0, len = ref.length; i < len; i++) { - postObj = ref[i]; - if (postObj.no === post.ID) { - break; - } - } - if (postObj.no !== post.ID) { - post.kill(); - return redirect(); - } else if (postObj.filedeleted) { - post.kill(true); - return redirect(); - } else { - return URL = post.file.url; - } + return $.ajax(threadJSON, { + onloadend: function() { + return parseJSON.call(this); } }); }, @@ -11768,12 +15181,12 @@ ImageCommon = (function() { return (Conf['Show Controls'] && Conf['Click Passthrough'] && e.target.nodeName === 'VIDEO') || (e.target.controls && e.target.getBoundingClientRect().bottom - e.clientY < 35); }, download: function(e) { - var download, href; + var download, href, ref; if (this.protocol === 'blob:') { return true; } e.preventDefault(); - href = this.href, download = this.download; + ref = this, href = ref.href, download = ref.download; return CrossOrigin.file(href, function(blob) { var a; if (blob) { @@ -11803,7 +15216,7 @@ ImageExpand = (function() { ImageExpand = { init: function() { var ref; - if (!(this.enabled = Conf['Image Expansion'] && ((ref = g.VIEW) === 'index' || ref === 'thread') && g.BOARD.ID !== 'f')) { + if (!(this.enabled = Conf['Image Expansion'] && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { return; } this.EAI = $.el('a', { @@ -11818,9 +15231,7 @@ ImageExpand = (function() { this.videoControls = $.el('span', { className: 'video-controls' }); - $.extend(this.videoControls, { - innerHTML: " contract" - }); + $.extend(this.videoControls, {innerHTML: " contract"}); return Callbacks.Post.push({ name: 'Image Expansion', cb: this.node @@ -11831,7 +15242,7 @@ ImageExpand = (function() { if (!(this.file && (this.file.isImage || this.file.isVideo))) { return; } - $.on(this.file.thumb.parentNode, 'click', ImageExpand.cb.toggle); + $.on(this.file.thumbLink, 'click', ImageExpand.cb.toggle); if (this.isClone) { if (this.file.isExpanding) { ImageExpand.contract(this); @@ -11848,7 +15259,7 @@ ImageExpand = (function() { cb: { toggle: function(e) { var file, post, ref; - if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { + if ($.modifiedClick(e)) { return; } post = Get.postFromNode(this); @@ -11864,15 +15275,16 @@ ImageExpand = (function() { } }, toggleAll: function() { - var func, toggle; + var func, threadRoot, toggle; $.event('CloseMenu'); + threadRoot = Nav.getThread(); toggle = function(post) { var file; file = post.file; if (!(file && (file.isImage || file.isVideo) && doc.contains(post.nodes.root))) { return; } - if (ImageExpand.on && (!Conf['Expand spoilers'] && file.isSpoiler || !Conf['Expand videos'] && file.isVideo || Conf['Expand from here'] && Header.getTopOf(file.thumb) < 0)) { + if (ImageExpand.on && (!Conf['Expand spoilers'] && file.isSpoiler || !Conf['Expand videos'] && file.isVideo || Conf['Expand from here'] && Header.getTopOf(file.thumb) < 0 || Conf['Expand thread only'] && g.VIEW === 'index' && !(threadRoot != null ? threadRoot.contains(file.thumb) : void 0))) { return; } return $.queueTask(func, post); @@ -11953,8 +15365,8 @@ ImageExpand = (function() { $.rmClass(post.nodes.root, 'expanded-image'); $.rmClass(file.thumb, 'expanding'); $.rm(file.videoControls); - file.thumb.parentNode.href = file.url; - file.thumb.parentNode.target = '_blank'; + file.thumbLink.href = file.url; + file.thumbLink.target = '_blank'; ref = ['isExpanding', 'isExpanded', 'videoControls', 'wasPlaying', 'scrollIntoView']; for (i = 0, len = ref.length; i < len; i++) { x = ref[i]; @@ -11983,6 +15395,9 @@ ImageExpand = (function() { $.off(el, eventName, cb); } } + if (Conf['Restart when Opened']) { + ImageCommon.rewind(file.thumb); + } delete file.fullImage; return $.queueTask(function() { if (file.isExpanding || file.isExpanded) { @@ -11996,9 +15411,9 @@ ImageExpand = (function() { }); }, expand: function(post, src) { - var el, file, isVideo, ref, thumb; + var el, file, isVideo, ref, thumb, thumbLink; file = post.file; - thumb = file.thumb, isVideo = file.isVideo; + thumb = file.thumb, thumbLink = file.thumbLink, isVideo = file.isVideo; if (post.isHidden || file.isExpanding || file.isExpanded) { return; } @@ -12006,25 +15421,28 @@ ImageExpand = (function() { file.isExpanding = true; if (file.fullImage) { el = file.fullImage; - } else if (((ref = ImageCommon.cache) != null ? ref.dataset.fullID : void 0) === post.fullID) { + } else if (((ref = ImageCommon.cache) != null ? ref.dataset.fileID : void 0) === (post.fullID + "." + file.index)) { el = file.fullImage = ImageCommon.popCache(); $.on(el, 'error', ImageExpand.error); + if (Conf['Restart when Opened'] && el.id !== 'ihover') { + ImageCommon.rewind(el); + } el.removeAttribute('id'); } else { el = file.fullImage = $.el((isVideo ? 'video' : 'img')); - el.dataset.fullID = post.fullID; + el.dataset.fileID = post.fullID + "." + file.index; $.on(el, 'error', ImageExpand.error); el.src = src || file.url; } el.className = 'full-image'; $.after(thumb, el); if (isVideo) { - if (Conf['Show Controls'] && Conf['Click Passthrough'] && !file.videoControls) { + if (!file.videoControls) { file.videoControls = ImageExpand.videoControls.cloneNode(true); $.add(file.text, file.videoControls); } - thumb.parentNode.removeAttribute('href'); - thumb.parentNode.removeAttribute('target'); + thumbLink.removeAttribute('href'); + thumbLink.removeAttribute('target'); el.loop = true; Volume.setup(el); ImageExpand.setupVideoCB(post); @@ -12109,7 +15527,7 @@ ImageExpand = (function() { } }, mouseout: function(e) { - if (mousedown && e.clientX <= this.getBoundingClientRect().left) { + if (((e.buttons & 1) || mousedown) && e.clientX <= this.getBoundingClientRect().left) { return ImageExpand.toggle(Get.postFromNode(this)); } } @@ -12136,13 +15554,13 @@ ImageExpand = (function() { if (!(post.file.isExpanding || post.file.isExpanded)) { return; } - if (ImageCommon.decodeError(this, post)) { + if (ImageCommon.decodeError(this, post.file)) { return ImageExpand.contract(post); } - if (this.src.split('/')[2] !== 'i.4cdn.org') { + if (ImageCommon.isFromArchive(this)) { return ImageExpand.contract(post); } - return ImageCommon.error(this, post, 10 * $.SECOND, function(URL) { + return ImageCommon.error(this, post, post.file, 10 * $.SECOND, function(URL) { if (post.file.isExpanding || post.file.isExpanded) { ImageExpand.contract(post); if (URL) { @@ -12195,6 +15613,68 @@ ImageExpand = (function() { }).call(this); +ImageHost = (function() { + var ImageHost; + + ImageHost = { + init: function() { + var ref; + if (!((this.useFaster = /\S/.test(Conf['fourchanImageHost'])) && g.SITE.software === 'yotsuba' && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { + return; + } + return Callbacks.Post.push({ + name: 'Image Host Rewriting', + cb: this.node + }); + }, + suggestions: ['i.4cdn.org', 'is2.4chan.org'], + host: function() { + return Conf['fourchanImageHost'].trim() || 'i.4cdn.org'; + }, + flashHost: function() { + return 'i.4cdn.org'; + }, + thumbHost: function() { + return 'i.4cdn.org'; + }, + test: function(hostname) { + return hostname === 'i.4cdn.org' || ImageHost.regex.test(hostname); + }, + regex: /^is\d*\.4chan(?:nel)?\.org$/, + node: function() { + var host; + if (this.isClone) { + return; + } + host = ImageHost.host(); + if (this.file && ImageHost.test(this.file.url.split('/')[2]) && !/\.swf$/.test(this.file.url)) { + this.file.link.hostname = host; + if (this.file.thumbLink) { + this.file.thumbLink.hostname = host; + } + this.file.url = this.file.link.href; + } + return ImageHost.fixLinks($$('a', this.nodes.comment)); + }, + fixLinks: function(links) { + var host, i, len, link; + for (i = 0, len = links.length; i < len; i++) { + link = links[i]; + if (!(ImageHost.test(link.hostname) && !/\.swf$/.test(link.pathname))) { + continue; + } + host = ImageHost.host(); + if (link.hostname !== host) { + link.hostname = host; + } + } + } + }; + + return ImageHost; + +}).call(this); + ImageHover = (function() { var ImageHover; @@ -12218,40 +15698,49 @@ ImageHover = (function() { } }, node: function() { - if (!(this.file && (this.file.isImage || this.file.isVideo))) { - return; + var file, i, len, ref, results; + ref = this.files; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + if ((file.isImage || file.isVideo) && file.thumb) { + results.push($.on(file.thumb, 'mouseover', ImageHover.mouseover(this, file))); + } } - return $.on(this.file.thumb, 'mouseover', ImageHover.mouseover(this)); + return results; }, catalogNode: function() { var file; - file = this.thread.OP.file; + file = this.thread.OP.files[0]; if (!(file && (file.isImage || file.isVideo))) { return; } - return $.on(this.nodes.thumb, 'mouseover', ImageHover.mouseover(this.thread.OP)); + return $.on(this.nodes.thumb, 'mouseover', ImageHover.mouseover(this.thread.OP, file)); }, - mouseover: function(post) { + mouseover: function(post, file) { return function(e) { - var el, error, file, height, isVideo, left, maxHeight, maxWidth, ref, ref1, ref2, right, scale, width, x; + var base, el, error, height, isVideo, maxHeight, maxWidth, ref, ref1, scale, width, x; if (!doc.contains(this)) { return; } - file = post.file; isVideo = file.isVideo; - if (file.isExpanding || file.isExpanded) { + if (file.isExpanding || file.isExpanded || (typeof (base = g.SITE).isThumbExpanded === "function" ? base.isThumbExpanded(file) : void 0)) { return; } - error = ImageHover.error(post); - if (((ref = ImageCommon.cache) != null ? ref.dataset.fullID : void 0) === post.fullID) { + error = ImageHover.error(post, file); + if (((ref = ImageCommon.cache) != null ? ref.dataset.fileID : void 0) === (post.fullID + "." + file.index)) { el = ImageCommon.popCache(); $.on(el, 'error', error); } else { el = $.el((isVideo ? 'video' : 'img')); - el.dataset.fullID = post.fullID; + el.dataset.fileID = post.fullID + "." + file.index; $.on(el, 'error', error); el.src = file.url; } + if (Conf['Restart when Opened']) { + ImageCommon.rewind(el); + ImageCommon.rewind(this); + } el.id = 'ihover'; $.add(Header.hover, el); if (isVideo) { @@ -12265,28 +15754,32 @@ ImageHover = (function() { } } } - ref1 = (function() { - var i, len, ref1, results; - ref1 = file.dimensions.split('x'); - results = []; - for (i = 0, len = ref1.length; i < len; i++) { - x = ref1[i]; - results.push(+x); - } - return results; - })(), width = ref1[0], height = ref1[1]; - ref2 = this.getBoundingClientRect(), left = ref2.left, right = ref2.right; - maxWidth = Math.max(left, doc.clientWidth - right); - maxHeight = doc.clientHeight - UI.hover.padding; - scale = Math.min(1, maxWidth / width, maxHeight / height); - el.style.maxWidth = (scale * width) + "px"; - el.style.maxHeight = (scale * height) + "px"; + if (file.dimensions) { + ref1 = (function() { + var i, len, ref1, results; + ref1 = file.dimensions.split('x'); + results = []; + for (i = 0, len = ref1.length; i < len; i++) { + x = ref1[i]; + results.push(+x); + } + return results; + })(), width = ref1[0], height = ref1[1]; + maxWidth = doc.clientWidth; + maxHeight = doc.clientHeight - UI.hover.padding; + scale = Math.min(1, maxWidth / width, maxHeight / height); + width *= scale; + height *= scale; + el.style.maxWidth = width + "px"; + el.style.maxHeight = height + "px"; + } return UI.hover({ root: this, el: el, latestEvent: e, endEvents: 'mouseout click', - height: scale * height, + height: height, + width: width, noRemove: true, cb: function() { $.off(el, 'error', error); @@ -12298,12 +15791,12 @@ ImageHover = (function() { }); }; }, - error: function(post) { + error: function(post, file) { return function() { - if (ImageCommon.decodeError(this, post)) { + if (ImageCommon.decodeError(this, file)) { return; } - return ImageCommon.error(this, post, 3 * $.SECOND, (function(_this) { + return ImageCommon.error(this, post, file, 3 * $.SECOND, (function(_this) { return function(URL) { if (URL) { return _this.src = URL + (_this.src === URL ? '?' + Date.now() : ''); @@ -12326,11 +15819,12 @@ ImageLoader = (function() { ImageLoader = { init: function() { - var prefetch, ref; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && g.BOARD.ID !== 'f')) { + var el, ref, ref1, replace; + if ((ref = g.VIEW) !== 'index' && ref !== 'thread' && ref !== 'archive') { return; } - if (!(Conf['Image Prefetching'] || Conf['Replace JPG'] || Conf['Replace PNG'] || Conf['Replace GIF'] || Conf['Replace WEBM'])) { + replace = Conf['Replace JPG'] || Conf['Replace PNG'] || Conf['Replace GIF'] || Conf['Replace WEBM']; + if (!(Conf['Image Prefetching'] || replace)) { return; } Callbacks.Post.push({ @@ -12338,36 +15832,41 @@ ImageLoader = (function() { cb: this.node }); $.on(d, 'PostsInserted', function() { - return g.posts.forEach(ImageLoader.prefetch); + if (ImageLoader.prefetchEnabled || replace) { + return g.posts.forEach(ImageLoader.prefetchAll); + } }); if (Conf['Replace WEBM']) { $.on(d, 'scroll visibilitychange 4chanXInitFinished PostsInserted', this.playVideos); } - if (!Conf['Image Prefetching']) { + if (!(Conf['Image Prefetching'] && ((ref1 = g.VIEW) === 'index' || ref1 === 'thread'))) { return; } - prefetch = $.el('label', { - innerHTML: " Prefetch Images" - }); - this.el = prefetch.firstElementChild; - $.on(this.el, 'change', this.toggle); - return Header.menu.addEntry({ - el: prefetch, - order: 98 + el = $.el('a', { + href: 'javascript:;', + title: 'Prefetch Images', + className: 'fa fa-bolt disabled', + textContent: 'Prefetch' }); + $.on(el, 'click', this.toggle); + return Header.addShortcut('prefetch', el, 525); }, node: function() { - if (this.isClone || !this.file) { + var file, i, len, ref; + if (this.isClone) { return; } - if (Conf['Replace WEBM'] && this.file.isVideo) { - ImageLoader.replaceVideo(this); + ref = this.files; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + if (Conf['Replace WEBM'] && file.isVideo) { + ImageLoader.replaceVideo(this, file); + } + ImageLoader.prefetch(this, file); } - return ImageLoader.prefetch(this); }, - replaceVideo: function(post) { - var attr, file, i, len, ref, thumb, video; - file = post.file; + replaceVideo: function(post, file) { + var attr, i, len, ref, thumb, video; thumb = file.thumb; video = $.el('video', { preload: 'none', @@ -12389,19 +15888,25 @@ ImageLoader = (function() { file.thumb = video; return file.videoThumb = true; }, - prefetch: function(post) { - var clone, el, file, i, isImage, isVideo, len, match, ref, replace, thumb, type, url; - file = post.file; - if (!file) { - return; - } + prefetch: function(post, file) { + var clone, el, i, isImage, isVideo, len, ref, ref1, replace, thumb, type, url; isImage = file.isImage, isVideo = file.isVideo, thumb = file.thumb, url = file.url; if (file.isPrefetched || !(isImage || isVideo) || post.isHidden || post.thread.isHidden) { return; } - type = (match = url.match(/\.([^.]+)$/)[1].toUpperCase()) === 'JPEG' ? 'JPG' : match; + if (isVideo) { + type = 'WEBM'; + } else { + type = (ref = url.match(/\.([^.]+)$/)) != null ? ref[1].toUpperCase() : void 0; + if (type === 'JPEG') { + type = 'JPG'; + } + } replace = Conf["Replace " + type] && !/spoiler/.test(thumb.src || thumb.dataset.src); - if (!(replace || Conf['prefetch'])) { + if (!(replace || ImageLoader.prefetchEnabled)) { + return; + } + if ($.hasClass(doc, 'catalog-mode')) { return; } if (![post].concat(slice.call(post.clones)).some(function(clone) { @@ -12411,9 +15916,9 @@ ImageLoader = (function() { } file.isPrefetched = true; if (file.videoThumb) { - ref = post.clones; - for (i = 0, len = ref.length; i < len; i++) { - clone = ref[i]; + ref1 = post.clones; + for (i = 0, len = ref1.length; i < len; i++) { + clone = ref1[i]; clone.file.thumb.preload = 'auto'; } thumb.preload = 'auto'; @@ -12425,41 +15930,57 @@ ImageLoader = (function() { return; } el = $.el(isImage ? 'img' : 'video'); + if (isVideo) { + el.preload = 'auto'; + } if (replace && isImage) { $.on(el, 'load', function() { - var j, len1, ref1; - ref1 = post.clones; - for (j = 0, len1 = ref1.length; j < len1; j++) { - clone = ref1[j]; + var j, len1, ref2; + ref2 = post.clones; + for (j = 0, len1 = ref2.length; j < len1; j++) { + clone = ref2[j]; clone.file.thumb.src = url; } - thumb.src = url; - return thumb.removeAttribute('data-src'); + return thumb.src = url; }); } return el.src = url; }, + prefetchAll: function(post) { + var file, i, len, ref; + ref = post.files; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + ImageLoader.prefetch(post, file); + } + }, toggle: function() { - if (Conf['prefetch'] = this.checked) { - g.posts.forEach(ImageLoader.prefetch); + ImageLoader.prefetchEnabled = !ImageLoader.prefetchEnabled; + this.classList.toggle('disabled', !ImageLoader.prefetchEnabled); + if (ImageLoader.prefetchEnabled) { + g.posts.forEach(ImageLoader.prefetchAll); } }, playVideos: function() { var qpClone, ref; qpClone = (ref = $.id('qp')) != null ? ref.firstElementChild : void 0; return g.posts.forEach(function(post) { - var i, len, ref1, ref2, thumb; + var file, i, j, len, len1, ref1, ref2, thumb; ref1 = [post].concat(slice.call(post.clones)); for (i = 0, len = ref1.length; i < len; i++) { post = ref1[i]; - if (!((ref2 = post.file) != null ? ref2.videoThumb : void 0)) { - continue; - } - thumb = post.file.thumb; - if (Header.isNodeVisible(thumb) || post.nodes.root === qpClone) { - thumb.play(); - } else { - thumb.pause(); + ref2 = post.files; + for (j = 0, len1 = ref2.length; j < len1; j++) { + file = ref2[j]; + if (!file.videoThumb) { + continue; + } + thumb = file.thumb; + if (Header.isNodeVisible(thumb) || post.nodes.root === qpClone) { + thumb.play(); + } else { + thumb.pause(); + } } } }); @@ -12476,7 +15997,7 @@ Metadata = (function() { Metadata = { init: function() { var ref; - if (!(Conf['WEBM Metadata'] && ((ref = g.VIEW) === 'index' || ref === 'thread') && g.BOARD.ID !== 'f')) { + if (!(Conf['WEBM Metadata'] && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { return; } return Callbacks.Post.push({ @@ -12485,29 +16006,34 @@ Metadata = (function() { }); }, node: function() { - var el; - if (!(this.file && /webm$/i.test(this.file.url))) { - return; - } - if (this.isClone) { - el = $('.webm-title', this.file.text); - } else { - el = $.el('span', { - className: 'webm-title' - }); - $.extend(el, { - innerHTML: "" - }); - $.add(this.file.text, [$.tn(' '), el]); - } - if (el.children.length === 1) { - return $.one(el.lastElementChild, 'mouseover focus', Metadata.load); + var el, file, i, j, len1, ref; + ref = this.files; + for (i = j = 0, len1 = ref.length; j < len1; i = ++j) { + file = ref[i]; + if (!(/webm$/i.test(file.url))) { + continue; + } + if (this.isClone) { + el = $('.webm-title', file.text); + } else { + el = $.el('span', { + className: 'webm-title' + }); + el.dataset.index = i; + $.extend(el, {innerHTML: ""}); + $.add(file.text, [$.tn(' '), el]); + } + if (el.children.length === 1) { + $.one(el.lastElementChild, 'mouseover focus', Metadata.load); + } } }, load: function() { + var index; $.rmClass(this.parentNode, 'error'); $.addClass(this.parentNode, 'loading'); - return CrossOrigin.binary(Get.postFromNode(this).file.url, (function(_this) { + index = this.parentNode.dataset.index; + return CrossOrigin.binary(Get.postFromNode(this).files[+index].url, (function(_this) { return function(data) { var output, title; $.rmClass(_this.parentNode, 'loading'); @@ -12577,7 +16103,7 @@ RevealSpoilers = (function() { RevealSpoilers = { init: function() { var ref; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Reveal Spoiler Thumbnails'])) { + if (!(((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Reveal Spoiler Thumbnails'])) { return; } return Callbacks.Post.push({ @@ -12586,17 +16112,24 @@ RevealSpoilers = (function() { }); }, node: function() { - var thumb; - if (!(!this.isClone && this.file && this.file.thumb && this.file.isSpoiler)) { + var file, i, len, ref, thumb; + if (this.isClone) { return; } - thumb = this.file.thumb; - thumb.removeAttribute('style'); - thumb.style.maxHeight = thumb.style.maxWidth = this.isReply ? '125px' : '250px'; - if (thumb.src) { - return thumb.src = this.file.thumbURL; - } else { - return thumb.dataset.src = this.file.thumbURL; + ref = this.files; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + if (!(file.thumb && file.isSpoiler)) { + continue; + } + thumb = file.thumb; + thumb.removeAttribute('style'); + thumb.style.maxHeight = thumb.style.maxWidth = this.isReply ? '125px' : '250px'; + if (thumb.src) { + thumb.src = file.thumbURL; + } else { + thumb.dataset.src = file.thumbURL; + } } } }; @@ -12611,20 +16144,17 @@ Sauce = (function() { Sauce = { init: function() { - var err, j, len, link, links, ref, ref1; + var j, len, link, linkData, links, ref, ref1; if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Sauce'])) { return; } + $.addClass(doc, 'show-sauce'); links = []; ref1 = Conf['sauces'].split('\n'); for (j = 0, len = ref1.length; j < len; j++) { link = ref1[j]; - try { - if (link[0] !== '#') { - links.push(link.trim()); - } - } catch (_error) { - err = _error; + if (link[0] !== '#' && (linkData = this.parseLink(link))) { + links.push(linkData); } } if (!links.length) { @@ -12640,13 +16170,13 @@ Sauce = (function() { cb: this.node }); }, - createSauceLink: function(link, post) { - var a, ext, i, j, key, len, m, part, parts, ref, ref1, ref2, skip; + parseLink: function(link) { + var err, i, j, len, m, part, parts, ref, ref1, regexp; if (!(link = link.trim())) { return null; } - parts = {}; - ref = link.split(/;(?=(?:text|boards|types|sandbox):?)/); + parts = $.dict(); + ref = link.split(/;(?=(?:text|boards|types|regexp|sandbox):?)/); for (i = j = 0, len = ref.length; j < len; i = ++j) { part = ref[i]; if (i === 0) { @@ -12657,15 +16187,55 @@ Sauce = (function() { } } parts['text'] || (parts['text'] = ((ref1 = parts['url'].match(/(\w+)\.\w+\//)) != null ? ref1[1] : void 0) || '?'); - ext = post.file.url.match(/[^.]*$/)[0]; - skip = false; - for (key in parts) { - parts[key] = parts[key].replace(/%(T?URL|IMG|[sh]?MD5|board|name|%|semi)/g, function(_, parameter) { + if ('boards' in parts) { + parts['boards'] = Filter.parseBoards(parts['boards']); + } + if ('regexp' in parts) { + try { + if ((regexp = parts['regexp'].match(/^\/(.*)\/(\w*)$/))) { + parts['regexp'] = RegExp(regexp[1], regexp[2]); + } else { + parts['regexp'] = RegExp(parts['regexp']); + } + } catch (error) { + err = error; + new Notice('warning', [$.tn("Invalid regexp for Sauce link:"), $.el('br'), $.tn(link), $.el('br'), $.tn(err.message)], 60); + return null; + } + } + return parts; + }, + createSauceLink: function(link, post, file) { + var a, base, ext, j, key, len, matches, missing, parts, ref; + ext = file.url.match(/[^.]*$/)[0]; + parts = $.dict(); + $.extend(parts, link); + if (!(!parts['boards'] || parts['boards'][post.siteID + "/" + post.boardID] || parts['boards'][post.siteID + "/*"])) { + return null; + } + if (!(!parts['types'] || indexOf.call(parts['types'].split(','), ext) >= 0)) { + return null; + } + if (!(!parts['regexp'] || (matches = file.name.match(parts['regexp'])))) { + return null; + } + missing = []; + ref = ['url', 'text']; + for (j = 0, len = ref.length; j < len; j++) { + key = ref[j]; + parts[key] = parts[key].replace(/%(T?URL|IMG|[sh]?MD5|board|name|%|semi|\$\d+)/g, function(orig, parameter) { var type; - type = Sauce.formatters[parameter](post, ext); - if (type == null) { - skip = true; - return ''; + if (parameter[0] === '$') { + if (!matches) { + return orig; + } + type = matches[parameter.slice(1)] || ''; + } else { + type = Sauce.formatters[parameter](post, file, ext); + if (type == null) { + missing.push(parameter); + return ''; + } } if (key === 'url' && (parameter !== '%' && parameter !== 'semi')) { if (/^javascript:/i.test(parts['url'])) { @@ -12676,13 +16246,14 @@ Sauce = (function() { return type; }); } - if (skip) { - return null; - } - if (!(!parts['boards'] || (ref2 = post.board.ID, indexOf.call(parts['boards'].split(','), ref2) >= 0))) { - return null; + if ((typeof (base = g.SITE).areMD5sDeferred === "function" ? base.areMD5sDeferred(post.board) : void 0) && missing.length && !missing.filter(function(x) { + return !/^.?MD5$/.test(x); + }).length) { + a = Sauce.link.cloneNode(false); + a.dataset.skip = '1'; + return a; } - if (!(!parts['types'] || indexOf.call(parts['types'].split(','), ext) >= 0)) { + if (missing.length) { return null; } a = Sauce.link.cloneNode(false); @@ -12694,62 +16265,69 @@ Sauce = (function() { return a; }, node: function() { - var j, len, link, node, nodes, observer, ref, skipped; - if (this.isClone || !this.file) { + var file, j, len, ref; + if (this.isClone) { return; } + ref = this.files; + for (j = 0, len = ref.length; j < len; j++) { + file = ref[j]; + Sauce.file(this, file); + } + }, + file: function(post, file) { + var j, len, link, node, nodes, observer, ref, skipped; nodes = []; skipped = []; ref = Sauce.links; for (j = 0, len = ref.length; j < len; j++) { link = ref[j]; - if (!(node = Sauce.createSauceLink(link, this))) { - node = Sauce.link.cloneNode(false); - skipped.push([link, node]); - } - nodes.push($.tn(' '), node); - } - $.add(this.file.text, nodes); - if (this.board.ID === 'f') { - observer = new MutationObserver((function(_this) { - return function() { - var k, len1, node2, ref1; - if (_this.file.text.dataset.md5) { - for (k = 0, len1 = skipped.length; k < len1; k++) { - ref1 = skipped[k], link = ref1[0], node = ref1[1]; - if ((node2 = Sauce.createSauceLink(link, _this))) { - $.replace(node, node2); - } + if ((node = Sauce.createSauceLink(link, post, file))) { + nodes.push($.tn(' '), node); + if (node.dataset.skip) { + skipped.push([link, node]); + } + } + } + $.add(file.text, nodes); + if (skipped.length) { + observer = new MutationObserver(function() { + var k, len1, node2, ref1; + if (file.text.dataset.md5) { + for (k = 0, len1 = skipped.length; k < len1; k++) { + ref1 = skipped[k], link = ref1[0], node = ref1[1]; + if ((node2 = Sauce.createSauceLink(link, post, file))) { + $.replace(node, node2); } - return observer.disconnect(); } - }; - })(this)); - return observer.observe(this.file.text, { + return observer.disconnect(); + } + }); + return observer.observe(file.text, { attributes: true }); } }, formatters: { - TURL: function(post) { - return post.file.thumbURL; + TURL: function(post, file) { + return file.thumbURL; }, - URL: function(post) { - return post.file.url; + URL: function(post, file) { + return file.url; }, - IMG: function(post, ext) { - if (ext === 'gif' || ext === 'jpg' || ext === 'png') { - return post.file.url; + IMG: function(post, file, ext) { + if (ext === 'gif' || ext === 'jpg' || ext === 'jpeg' || ext === 'png') { + return file.url; } else { - return post.file.thumbURL; + return file.thumbURL; } }, - MD5: function(post) { - return post.file.MD5; + MD5: function(post, file) { + return file.MD5; }, - sMD5: function(post) { + sMD5: function(post, file) { var ref; - return (ref = post.file.MD5) != null ? ref.replace(/[+\/=]/g, function(c) { + return (ref = file.MD5) != null ? ref.replace(/[+\/=]/g, function(c) { return { '+': '-', '/': '_', @@ -12757,12 +16335,12 @@ Sauce = (function() { }[c]; }) : void 0; }, - hMD5: function(post) { + hMD5: function(post, file) { var c; - if (post.file.MD5) { + if (file.MD5) { return ((function() { var j, len, ref, results; - ref = atob(post.file.MD5); + ref = atob(file.MD5); results = []; for (j = 0, len = ref.length; j < len; j++) { c = ref[j]; @@ -12775,8 +16353,8 @@ Sauce = (function() { board: function(post) { return post.board.ID; }, - name: function(post) { - return post.file.name; + name: function(post, file) { + return file.name; }, '%': function() { return '%'; @@ -12796,7 +16374,7 @@ Volume = (function() { Volume = { init: function() { - var ref, ref1, unmuteEntry, volumeEntry; + var base, ref, unmuteEntry, volumeEntry; if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && (Conf['Image Expansion'] || Conf['Image Hover'] || Conf['Image Hover in Catalog'] || Conf['Gallery']))) { return; } @@ -12816,7 +16394,7 @@ Volume = (function() { cb: this.node }); } - if ((ref1 = g.BOARD.ID) !== 'gif' && ref1 !== 'wsg') { + if (typeof (base = g.SITE).noAudio === "function" ? base.noAudio(g.BOARD) : void 0) { return; } if (Conf['Mouse Wheel Volume']) { @@ -12830,9 +16408,7 @@ Volume = (function() { volumeEntry = $.el('label', { title: 'Default volume for videos.' }); - $.extend(volumeEntry, { - innerHTML: " Volume" - }); + $.extend(volumeEntry, {innerHTML: " Volume"}); this.inputs = { unmute: unmuteEntry.firstElementChild, volume: volumeEntry.firstElementChild @@ -12854,8 +16430,8 @@ Volume = (function() { return $.on(video, 'volumechange', Volume.change); }, change: function() { - var items, key, muted, val, volume; - muted = this.muted, volume = this.volume; + var items, key, muted, ref, val, volume; + ref = this, muted = ref.muted, volume = ref.volume; items = { 'Allow Sound': !muted, 'Default Volume': volume @@ -12874,16 +16450,25 @@ Volume = (function() { } }, node: function() { - var ref, ref1; - if (!(((ref = this.board.ID) === 'gif' || ref === 'wsg') && ((ref1 = this.file) != null ? ref1.isVideo : void 0))) { + var base, file, i, len, ref; + if (typeof (base = g.SITE).noAudio === "function" ? base.noAudio(this.board) : void 0) { return; } - $.on(this.file.thumb, 'wheel', Volume.wheel.bind(Header.hover)); - return $.on($('a', this.file.text), 'wheel', Volume.wheel.bind(this.file.thumb.parentNode)); + ref = this.files; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + if (!file.isVideo) { + continue; + } + if (file.thumb) { + $.on(file.thumb, 'wheel', Volume.wheel.bind(Header.hover)); + } + $.on($('.file-info', file.text) || file.link, 'wheel', Volume.wheel.bind(file.thumbLink)); + } }, catalogNode: function() { var file; - file = this.thread.OP.file; + file = this.thread.OP.files[0]; if (!(file != null ? file.isVideo : void 0)) { return; } @@ -12917,34 +16502,47 @@ Volume = (function() { }).call(this); Embedding = (function() { - var Embedding; + var Embedding, + slice = [].slice; Embedding = { init: function() { - var j, len, ref, type; - if (!(Conf['Embedding'] || Conf['Link Title'])) { + var j, len, ref, ref1, type; + if (!(((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Linkify'] && (Conf['Embedding'] || Conf['Link Title'] || Conf['Cover Preview']))) { return; } - this.types = {}; - ref = this.ordered_types; - for (j = 0, len = ref.length; j < len; j++) { - type = ref[j]; + this.types = $.dict(); + ref1 = this.ordered_types; + for (j = 0, len = ref1.length; j < len; j++) { + type = ref1[j]; this.types[type.key] = type; } - if (Conf['Floating Embeds']) { - this.dialog = UI.dialog('embedding', 'top: 50px; right: 0px;', { - innerHTML: "
          " - }); + if (Conf['Embedding'] && g.VIEW !== 'archive') { + this.dialog = UI.dialog('embedding', {innerHTML: "
          "}); this.media = $('#media-embed', this.dialog); $.one(d, '4chanXInitFinished', this.ready); + $.on(d, 'IndexRefreshInternal', function() { + return g.posts.forEach(function(post) { + var embed, k, l, len1, len2, ref2, ref3; + ref2 = [post].concat(slice.call(post.clones)); + for (k = 0, len1 = ref2.length; k < len1; k++) { + post = ref2[k]; + ref3 = post.nodes.embedlinks; + for (l = 0, len2 = ref3.length; l < len2; l++) { + embed = ref3[l]; + Embedding.cb.catalogRemove.call(embed); + } + } + }); + }); } if (Conf['Link Title']) { return $.on(d, '4chanXInitFinished PostsInserted', function() { - var key, ref1, ref2, service; - ref1 = Embedding.types; - for (key in ref1) { - service = ref1[key]; - if ((ref2 = service.title) != null ? ref2.batchSize : void 0) { + var key, ref2, ref3, service; + ref2 = Embedding.types; + for (key in ref2) { + service = ref2[key]; + if ((ref3 = service.title) != null ? ref3.batchSize : void 0) { Embedding.flushTitles(service.title); } } @@ -12952,22 +16550,33 @@ Embedding = (function() { } }, events: function(post) { - var el, i, items; - if (!Conf['Embedding']) { + var data, el, i, items; + if (g.VIEW === 'archive') { return; } - i = 0; - items = $$('.embedder', post.nodes.comment); - while (el = items[i++]) { - $.on(el, 'click', Embedding.cb.toggle); - if ($.hasClass(el, 'embedded')) { - Embedding.cb.toggle.call(el); + if (Conf['Embedding']) { + i = 0; + items = post.nodes.embedlinks = $$('.embedder', post.nodes.comment); + while (el = items[i++]) { + $.on(el, 'click', Embedding.cb.click); + if ($.hasClass(el, 'embedded')) { + Embedding.cb.toggle.call(el); + } + } + } + if (Conf['Cover Preview']) { + i = 0; + items = $$('.linkify', post.nodes.comment); + while (el = items[i++]) { + if ((data = Embedding.services(el))) { + Embedding.preview(data); + } } } }, process: function(link, post) { var data; - if (!(Conf['Embedding'] || Conf['Link Title'])) { + if (!(Conf['Embedding'] || Conf['Link Title'] || Conf['Cover Preview'])) { return; } if ($.x('ancestor::pre', link)) { @@ -12975,11 +16584,14 @@ Embedding = (function() { } if (data = Embedding.services(link)) { data.post = post; - if (Conf['Embedding']) { + if (Conf['Embedding'] && g.VIEW !== 'archive') { Embedding.embed(data); } if (Conf['Link Title']) { - return Embedding.title(data); + Embedding.title(data); + } + if (Conf['Cover Preview'] && g.VIEW !== 'archive') { + return Embedding.preview(data); } } }, @@ -13003,15 +16615,11 @@ Embedding = (function() { var embed, href, key, link, name, options, post, ref, uid, value; key = data.key, uid = data.uid, options = data.options, link = data.link, post = data.post; href = link.href; - if (Embedding.types[key].httpOnly && location.protocol !== 'http:') { - return; - } $.addClass(link, key.toLowerCase()); embed = $.el('a', { className: 'embedder', - href: 'javascript:;', - textContent: '(embed)' - }); + href: 'javascript:;' + }, {innerHTML: "(unembed)"}); ref = { key: key, uid: uid, @@ -13022,17 +16630,21 @@ Embedding = (function() { value = ref[name]; embed.dataset[name] = value; } - $.on(embed, 'click', Embedding.cb.toggle); + $.on(embed, 'click', Embedding.cb.click); $.after(link, [$.tn(' '), embed]); + post.nodes.embedlinks.push(embed); if (Conf['Auto-embed'] && !Conf['Floating Embeds'] && !post.isFetchedQuote) { - return $.asap((function() { - return doc.contains(embed); - }), function() { + if ($.hasClass(doc, 'catalog-mode')) { + return $.addClass(embed, 'embed-removed'); + } else { return Embedding.cb.toggle.call(embed); - }); + } } }, ready: function() { + if (!Main.isThisPageLegit()) { + return; + } $.addClass(Embedding.dialog, 'empty'); $.on($('.close', Embedding.dialog), 'click', Embedding.closeFloat); $.on($('.move', Embedding.dialog), 'mousedown', Embedding.dragEmbed); @@ -13054,12 +16666,12 @@ Embedding = (function() { if (Embedding.dragEmbed.mouseup) { $.off(d, 'mouseup', Embedding.dragEmbed); Embedding.dragEmbed.mouseup = false; - style.visibility = ''; + style.pointerEvents = ''; return; } $.on(d, 'mouseup', Embedding.dragEmbed); Embedding.dragEmbed.mouseup = true; - return style.visibility = 'hidden'; + return style.pointerEvents = 'none'; }, title: function(data) { var key, link, options, post, service, uid; @@ -13074,19 +16686,13 @@ Embedding = (function() { return Embedding.flushTitles(service); } } else { - if (!$.cache(service.api(uid), (function() { + return CrossOrigin.cache(service.api(uid), (function() { return Embedding.cb.title(this, data); - }), { - responseType: 'json' - })) { - return $.extend(link, { - innerHTML: "[" + E(key) + "] Title Link Blocked (are you using NoScript?)" - }); - } + })); } }, flushTitles: function(service) { - var cb, data, j, len, queue; + var cb, data, queue; queue = service.queue; if (!(queue != null ? queue.length : void 0)) { return; @@ -13099,7 +16705,7 @@ Embedding = (function() { Embedding.cb.title(this, data); } }; - if (!$.cache(service.api((function() { + return CrossOrigin.cache(service.api((function() { var j, len, results; results = []; for (j = 0, len = queue.length; j < len; j++) { @@ -13107,61 +16713,98 @@ Embedding = (function() { results.push(data.uid); } return results; - })()), cb, { - responseType: 'json' - })) { - for (j = 0, len = queue.length; j < len; j++) { - data = queue[j]; - $.extend(data.link, { - innerHTML: "[" + E(data.key) + "] Title Link Blocked (are you using NoScript?)" - }); - } + })()), cb); + }, + preview: function(data) { + var key, link, service, uid; + key = data.key, uid = data.uid, link = data.link; + if (!(service = Embedding.types[key].preview)) { + return; } + return $.on(link, 'mouseover', function(e) { + var el, height, src; + src = service.url(uid); + height = service.height; + el = $.el('img', { + src: src, + id: 'ihover' + }); + $.add(Header.hover, el); + return UI.hover({ + root: link, + el: el, + latestEvent: e, + endEvents: 'mouseout click', + height: height + }); + }); }, cb: { - toggle: function(e) { + click: function(e) { var div; - if (e != null) { - e.preventDefault(); - } - if (Conf['Floating Embeds']) { + e.preventDefault(); + if (!$.hasClass(this, 'embedded') && (Conf['Floating Embeds'] || $.hasClass(doc, 'catalog-mode'))) { if (!(div = Embedding.media.firstChild)) { return; } $.replace(div, Embedding.cb.embed(this)); Embedding.lastEmbed = Get.postFromNode(this).nodes.root; - $.rmClass(Embedding.dialog, 'empty'); - return; + return $.rmClass(Embedding.dialog, 'empty'); + } else { + return Embedding.cb.toggle.call(this); } + }, + toggle: function() { if ($.hasClass(this, "embedded")) { $.rm(this.nextElementSibling); - this.textContent = '(embed)'; } else { $.after(this, Embedding.cb.embed(this)); - this.textContent = '(unembed)'; } return $.toggleClass(this, 'embedded'); }, embed: function(a) { var container, el, type; - container = $.el('div'); + container = $.el('div', { + className: 'media-embed' + }); $.add(container, el = (type = Embedding.types[a.dataset.key]).el(a)); el.style.cssText = type.style != null ? type.style : 'border: none; width: 640px; height: 360px;'; return container; }, + catalogRemove: function() { + var isCatalog; + isCatalog = $.hasClass(doc, 'catalog-mode'); + if ((isCatalog && $.hasClass(this, 'embedded')) || (!isCatalog && $.hasClass(this, 'embed-removed'))) { + Embedding.cb.toggle.call(this); + return $.toggleClass(this, 'embed-removed'); + } + }, title: function(req, data) { var base1, j, k, key, len, len1, link, link2, options, post, post2, ref, ref1, service, status, text, uid; key = data.key, uid = data.uid, options = data.options, link = data.link, post = data.post; - status = req.status; service = Embedding.types[key].title; + status = req.status; + if ((status === 200 || status === 304) && service.status) { + status = service.status(req.response)[0]; + } + if (!status) { + return; + } text = "[" + key + "] " + ((function() { switch (status) { case 200: case 304: - return service.text(req.response, uid); + text = service.text(req.response, uid); + if (typeof text === 'string') { + return text; + } else { + return text = link.textContent; + } + break; case 404: return "Not Found"; case 403: + case 401: return "Forbidden or Private"; default: return status + "'d"; @@ -13189,7 +16832,7 @@ Embedding = (function() { ordered_types: [ { key: 'audio', - regExp: /^[^?#]+\.(?:mp3|oga|wav)(?:[?#]|$)/i, + regExp: /^[^?#]+\.(?:mp3|m4a|oga|wav|flac)(?:[?#]|$)/i, style: '', el: function(a) { return $.el('audio', { @@ -13200,12 +16843,10 @@ Embedding = (function() { } }, { key: 'image', - regExp: /^[^?#]+\.(?:gif|png|jpg|jpeg|bmp)(?:[?#]|$)/i, + regExp: /^[^?#]+\.(?:gif|png|jpg|jpeg|bmp|webp)(?::\w+)?(?:[?#]|$)/i, style: '', el: function(a) { - return $.el('div', { - innerHTML: "" - }); + return $.el('div', {innerHTML: ""}); } }, { key: 'video', @@ -13218,7 +16859,7 @@ Embedding = (function() { controls: true, preload: 'auto', src: a.dataset.href, - loop: /^https?:\/\/i\.4cdn\.org\//.test(a.dataset.href) + loop: ImageHost.test(a.dataset.href.split('/')[2]) }); $.on(el, 'loadedmetadata', function() { if (el.videoHeight === 0 && el.parentNode) { @@ -13230,19 +16871,45 @@ Embedding = (function() { return el; } }, { - key: 'Clyp', - regExp: /^\w+:\/\/(?:www\.)?clyp\.it\/(\w+)/, - style: '', + key: 'PeerTube', + regExp: /^(\w+:\/\/[^\/]+\/videos\/watch\/\w{8}-\w{4}-\w{4}-\w{4}-\w{12})(.*)/, + el: function(a) { + var el, options, start; + options = (start = a.dataset.options.match(/[?&](start=\w+)/)) ? "?" + start[1] : ''; + el = $.el('iframe', { + src: a.dataset.uid.replace('/videos/watch/', '/videos/embed/') + options + }); + el.setAttribute("allowfullscreen", "true"); + return el; + } + }, { + key: 'BitChute', + regExp: /^\w+:\/\/(?:www\.)?bitchute\.com\/video\/([\w\-]+)/, el: function(a) { - var el, type; - el = $.el('audio', { - controls: true, - preload: 'auto' + var el; + el = $.el('iframe', { + src: "https://www.bitchute.com/embed/" + a.dataset.uid + "/" }); - type = el.canPlayType('audio/ogg') ? 'ogg' : 'mp3'; - el.src = "https://clyp.it/" + a.dataset.uid + "." + type; + el.setAttribute("allowfullscreen", "true"); return el; } + }, { + key: 'Clyp', + regExp: /^\w+:\/\/(?:www\.)?clyp\.it\/(\w{8})/, + style: 'border: 0; width: 640px; height: 160px;', + el: function(a) { + return $.el('iframe', { + src: "https://clyp.it/" + a.dataset.uid + "/widget" + }); + }, + title: { + api: function(uid) { + return "https://api.clyp.it/oembed?url=https://clyp.it/" + uid; + }, + text: function(_) { + return _.title; + } + } }, { key: 'Dailymotion', regExp: /^\w+:\/\/(?:(?:www\.)?dailymotion\.com\/(?:embed\/)?video|dai\.ly)\/([A-Za-z0-9]+)[^?]*(.*)/, @@ -13262,29 +16929,50 @@ Embedding = (function() { text: function(_) { return _.title; } + }, + preview: { + url: function(uid) { + return "https://www.dailymotion.com/thumbnail/video/" + uid; + }, + height: 240 } }, { key: 'Gfycat', regExp: /^\w+:\/\/(?:www\.)?gfycat\.com\/(?:iframe\/)?(\w+)/, el: function(a) { - var div; - return div = $.el('iframe', { - src: "//gfycat.com/iframe/" + a.dataset.uid + var el; + el = $.el('iframe', { + src: "//gfycat.com/ifr/" + a.dataset.uid }); + el.setAttribute("allowfullscreen", "true"); + return el; } }, { key: 'Gist', regExp: /^\w+:\/\/gist\.github\.com\/[\w\-]+\/(\w+)/, - el: function(a) { - var content, el; - el = $.el('iframe'); - el.setAttribute('sandbox', 'allow-scripts'); - content = { - innerHTML: "" + E(a.dataset.uid) + "" + style: '', + el: (function() { + var counter; + counter = 0; + return function(a) { + var el; + el = $.el('pre', { + hidden: true, + id: "gist-embed-" + (counter++) + }); + CrossOrigin.cache("https://api.github.com/gists/" + a.dataset.uid, function() { + el.textContent = Object.values(this.response.files)[0].content; + el.className = 'prettyprint'; + $.global(function() { + return typeof window.prettyPrint === "function" ? window.prettyPrint((function() {}), document.getElementById(document.currentScript.dataset.id).parentNode) : void 0; + }, { + id: el.id + }); + return el.hidden = false; + }); + return el; }; - el.src = E.url(content); - return el; - }, + })(), title: { api: function(uid) { return "https://api.github.com/gists/" + uid; @@ -13309,19 +16997,18 @@ Embedding = (function() { } }, { key: 'LiveLeak', - regExp: /^\w+:\/\/(?:\w+\.)?liveleak\.com\/.*\?.*i=(\w+)/, - httpOnly: true, + regExp: /^\w+:\/\/(?:\w+\.)?liveleak\.com\/.*\?.*[tif]=(\w+)/, el: function(a) { var el; el = $.el('iframe', { - src: "http://www.liveleak.com/ll_embed?i=" + a.dataset.uid + src: "https://www.liveleak.com/e/" + a.dataset.uid }); el.setAttribute("allowfullscreen", "true"); return el; } }, { key: 'Loopvid', - regExp: /^\w+:\/\/(?:www\.)?loopvid.appspot.com\/#?((?:pf|kd|lv|gd|gh|db|dx|nn|cp|wu|ig|ky|mf|m2|pc|1c|pi|wl|ko|gc)\/[\w\-\/]+(?:,[\w\-\/]+)*|fc\/\w+\/\d+|https?:\/\/.+)/, + regExp: /^\w+:\/\/(?:www\.)?loopvid.appspot.com\/#?((?:pf|kd|lv|gd|gh|db|dx|nn|cp|wu|ig|ky|mf|m2|pc|1c|pi|ni|wl|ko|mm|ic|gc)\/[\w\-\/]+(?:,[\w\-\/]+)*|fc\/\w+\/\d+|https?:\/\/.+)/, style: 'max-width: 80vw; max-height: 80vh;', el: function(a) { var _, base, el, host, j, k, l, len, len1, len2, name, names, ref, ref1, type, types, url, urls; @@ -13360,9 +17047,9 @@ Embedding = (function() { case 'pf': return ["https://kastden.org/_loopvid_media/pf/" + base, "https://web.archive.org/web/2/http://a.pomf.se/" + base]; case 'kd': - return ["http://kastden.org/loopvid/" + base]; + return ["https://kastden.org/loopvid/" + base]; case 'lv': - return ["http://lv.kastden.org/" + base]; + return ["https://lv.kastden.org/" + base]; case 'gd': return ["https://docs.google.com/uc?export=download&id=" + base]; case 'gh': @@ -13372,7 +17059,7 @@ Embedding = (function() { case 'dx': return ["https://dl.dropboxusercontent.com/" + base]; case 'nn': - return ["http://naenara.eu/loopvids/" + base]; + return ["https://kastden.org/_loopvid_media/nn/" + base]; case 'cp': return ["https://copy.com/" + base]; case 'wu': @@ -13380,23 +17067,29 @@ Embedding = (function() { case 'ig': return ["https://i.imgur.com/" + base]; case 'ky': - return ["https://kiyo.me/" + base]; + return ["https://kastden.org/_loopvid_media/ky/" + base]; case 'mf': return ["https://kastden.org/_loopvid_media/mf/" + base, "https://web.archive.org/web/2/https://d.maxfile.ro/" + base]; case 'm2': return ["https://kastden.org/_loopvid_media/m2/" + base]; case 'pc': - return ["http://a.pomf.cat/" + base]; + return ["https://kastden.org/_loopvid_media/pc/" + base, "https://web.archive.org/web/2/http://a.pomf.cat/" + base]; case '1c': return ["http://b.1339.cf/" + base]; case 'pi': - return ["https://u.pomf.is/" + base]; + return ["https://kastden.org/_loopvid_media/pi/" + base, "https://web.archive.org/web/2/https://u.pomf.is/" + base]; + case 'ni': + return ["https://kastden.org/_loopvid_media/ni/" + base, "https://web.archive.org/web/2/https://u.nya.is/" + base]; case 'wl': return ["http://webm.land/media/" + base]; case 'ko': return ["https://kordy.kastden.org/loopvid/" + base]; + case 'mm': + return ["https://kastden.org/_loopvid_media/mm/" + base, "https://web.archive.org/web/2/https://my.mixtape.moe/" + base]; + case 'ic': + return ["https://media.8ch.net/file_store/" + base]; case 'fc': - return ["//i.4cdn.org/" + base + ".webm"]; + return ["//" + (ImageHost.host()) + "/" + base + ".webm"]; case 'gc': return ["https://" + type + ".gfycat.com/" + name + ".webm"]; } @@ -13413,15 +17106,15 @@ Embedding = (function() { } }, { key: 'Openings.moe', - regExp: /^\w+:\/\/openings.moe\/\?video=([^&=]+\.webm)/, - style: 'max-width: 80vw; max-height: 80vh;', + regExp: /^\w+:\/\/openings.moe\/\?video=([^.&=]+)/, + style: 'width: 1280px; height: 720px; max-width: 80vw; max-height: 80vh;', el: function(a) { - return $.el('video', { - controls: true, - preload: 'auto', - src: "//openings.moe/video/" + a.dataset.uid, - loop: true + var el; + el = $.el('iframe', { + src: "https://openings.moe/?video=" + a.dataset.uid }); + el.setAttribute("allowfullscreen", "true"); + return el; } }, { key: 'Pastebin', @@ -13443,7 +17136,7 @@ Embedding = (function() { }, title: { api: function(uid) { - return "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid)); + return location.protocol + "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid)); }, text: function(_) { return _.title; @@ -13455,18 +17148,42 @@ Embedding = (function() { style: 'border: 0; width: 600px; height: 406px;', el: function(a) { return $.el('iframe', { - src: "//www.strawpoll.me/embed_1/" + a.dataset.uid + src: "https://www.strawpoll.me/embed_1/" + a.dataset.uid + }); + } + }, { + key: 'Streamable', + regExp: /^\w+:\/\/(?:www\.)?streamable\.com\/(\w+)/, + el: function(a) { + var el; + el = $.el('iframe', { + src: "https://streamable.com/o/" + a.dataset.uid }); + el.setAttribute("allowfullscreen", "true"); + return el; + }, + title: { + api: function(uid) { + return "https://api.streamable.com/oembed?url=https://streamable.com/" + uid; + }, + text: function(_) { + return _.title; + } } }, { key: 'TwitchTV', - regExp: /^\w+:\/\/(?:www\.|secure\.)?twitch\.tv\/(\w[^#\&\?]*)/, + regExp: /^\w+:\/\/(?:www\.|secure\.|clips\.|m\.)?twitch\.tv\/(\w[^#\&\?]*)/, el: function(a) { var el, m, time, url; - m = a.dataset.uid.match(/(\w+)(?:\/v\/(\d+))?/); - url = "//player.twitch.tv/?" + (m[2] ? "video=v" + m[2] : "channel=" + m[1]) + "&autoplay=false"; - if ((time = a.dataset.href.match(/\bt=(\w+)/))) { - url += "&time=" + time[1]; + m = a.dataset.href.match(/^\w+:\/\/(?:(clips\.)|\w+\.)?twitch\.tv\/(?:\w+\/)?(clip\/)?(\w[^#\&\?]*)/); + if (m[1] || m[2]) { + url = "//clips.twitch.tv/embed?clip=" + m[3] + "&parent=" + location.hostname; + } else { + m = a.dataset.uid.match(/(\w+)(?:\/(?:v\/)?(\d+))?/); + url = "//player.twitch.tv/?" + (m[2] ? "video=v" + m[2] : "channel=" + m[1]) + "&autoplay=false&parent=" + location.hostname; + if ((time = a.dataset.href.match(/\bt=(\w+)/))) { + url += "&time=" + time[1]; + } } el = $.el('iframe', { src: url @@ -13476,11 +17193,45 @@ Embedding = (function() { } }, { key: 'Twitter', - regExp: /^\w+:\/\/(?:www\.)?twitter\.com\/(\w+\/status\/\d+)/, + regExp: /^\w+:\/\/(?:www\.|mobile\.)?twitter\.com\/(\w+\/status\/\d+)/, + style: 'border: none; width: 550px; height: 250px; overflow: hidden; resize: both;', el: function(a) { - return $.el('iframe', { - src: "https://twitframe.com/show?url=https://twitter.com/" + a.dataset.uid + var cont, el, onMessage; + el = $.el('iframe'); + $.on(el, 'load', function() { + return this.contentWindow.postMessage({ + element: 't', + query: 'height' + }, 'https://twitframe.com'); + }); + onMessage = function(e) { + if (e.source === el.contentWindow && e.origin === 'https://twitframe.com') { + $.off(window, 'message', onMessage); + return (cont || el).style.height = (+$.minmax(e.data.height, 250, 0.8 * doc.clientHeight)) + "px"; + } + }; + $.on(window, 'message', onMessage); + el.src = "https://twitframe.com/show?url=https://twitter.com/" + a.dataset.uid; + if ($.engine === 'gecko') { + el.style.cssText = 'border: none; width: 100%; height: 100%;'; + cont = $.el('div'); + $.add(cont, el); + return cont; + } else { + return el; + } + } + }, { + key: 'VidLii', + regExp: /^\w+:\/\/(?:www\.)?vidlii\.com\/watch\?v=(\w{11})/, + style: 'border: none; width: 640px; height: 392px;', + el: function(a) { + var el; + el = $.el('iframe', { + src: "https://www.vidlii.com/embed?v=" + a.dataset.uid + "&a=0" }); + el.setAttribute("allowfullscreen", "true"); + return el; } }, { key: 'Vimeo', @@ -13512,21 +17263,20 @@ Embedding = (function() { } }, { key: 'Vocaroo', - regExp: /^\w+:\/\/(?:www\.)?vocaroo\.com\/i\/(\w+)/, + regExp: /^\w+:\/\/(?:(?:www\.|old\.)?vocaroo\.com|voca\.ro)\/((?:i\/)?\w+)/, style: '', el: function(a) { - var el, type; - el = $.el('audio', { - controls: true, - preload: 'auto' - }); - type = el.canPlayType('audio/webm') ? 'webm' : 'mp3'; - el.src = "http://vocaroo.com/media_command.php?media=" + a.dataset.uid + "&command=download_" + type; + var el; + el = $.el('iframe'); + el.width = 300; + el.height = 60; + el.setAttribute('frameborder', 0); + el.src = "https://vocaroo.com/embed/" + (a.dataset.uid.replace(/^i\//, '')) + "?autoplay=0"; return el; } }, { key: 'YouTube', - regExp: /^\w+:\/\/(?:youtu.be\/|[\w.]*youtube[\w.]*\/.*(?:v=|\bembed\/|\bv\/))([\w\-]{11})(.*)/, + regExp: /^\w+:\/\/(?:youtu.be\/|[\w.]*youtube[\w.]*\/.*(?:v=|\bembed\/|\bv\/|live\/|shorts\/))([\w\-]{11})(.*)/, el: function(a) { var el, start; start = a.dataset.options.match(/\b(?:star)?t\=(\w+)/); @@ -13538,30 +17288,33 @@ Embedding = (function() { start = 3600 * start.match(/(\d+)h/)[1] + 60 * start.match(/(\d+)m/)[1] + 1 * start.match(/(\d+)s/)[1]; } el = $.el('iframe', { - src: "//www.youtube.com/embed/" + a.dataset.uid + "?wmode=opaque" + (start ? '&start=' + start : '') + src: "//www.youtube.com/embed/" + a.dataset.uid + "?rel=0&wmode=opaque" + (start ? '&start=' + start : '') }); el.setAttribute("allowfullscreen", "true"); return el; }, title: { - batchSize: 50, - api: function(uids) { - var ids, key; - ids = encodeURIComponent(uids.join(',')); - key = 'AIzaSyB5_zaen_-46Uhz1xGR-lz1YoUMHqCD6CE'; - return "https://www.googleapis.com/youtube/v3/videos?part=snippet&id=" + ids + "&fields=items%28id%2Csnippet%28title%29%29&key=" + key; + api: function(uid) { + return "https://www.youtube.com/oembed?url=https%3A//www.youtube.com/watch%3Fv%3D" + uid + "&format=json"; }, - text: function(data, uid) { - var item, j, len, ref; - ref = data.items; - for (j = 0, len = ref.length; j < len; j++) { - item = ref[j]; - if (item.id === uid) { - return item.snippet.title; - } + text: function(_) { + return _.title; + }, + status: function(_) { + var m; + if (_.error) { + m = _.error.match(/^(\d*)\s*(.*)/); + return [+m[1], m[2]]; + } else { + return [200, 'OK']; } - return 'Not Found'; } + }, + preview: { + url: function(uid) { + return "https://img.youtube.com/vi/" + uid + "/0.jpg"; + }, + height: 360 } } ] @@ -13577,7 +17330,7 @@ Linkify = (function() { Linkify = { init: function() { var ref; - if (((ref = g.VIEW) !== 'index' && ref !== 'thread') || !Conf['Linkify']) { + if (((ref = g.VIEW) !== 'index' && ref !== 'thread' && ref !== 'archive') || !Conf['Linkify']) { return; } if (Conf['Comment Expansion']) { @@ -13587,38 +17340,37 @@ Linkify = (function() { name: 'Linkify', cb: this.node }); - Callbacks.CatalogThread.push({ - name: 'Linkify', - cb: this.catalogNode - }); return Embedding.init(); }, node: function() { - var j, k, len, len1, link, links, ref; + var base, j, k, len, len1, link, links, ref; if (this.isClone) { return Embedding.events(this); } if (!Linkify.regString.test(this.info.comment)) { return; } - ref = $$('a[href^="http://i.4cdn.org/"], a[href^="https://i.4cdn.org/"]', this.nodes.comment); + ref = $$('a', this.nodes.comment); for (j = 0, len = ref.length; j < len; j++) { link = ref[j]; + if (!(typeof (base = g.SITE).isLinkified === "function" ? base.isLinkified(link) : void 0)) { + continue; + } $.addClass(link, 'linkify'); + if (ImageHost.useFaster) { + ImageHost.fixLinks([link]); + } Embedding.process(link, this); } links = Linkify.process(this.nodes.comment); + if (ImageHost.useFaster) { + ImageHost.fixLinks(links); + } for (k = 0, len1 = links.length; k < len1; k++) { link = links[k]; Embedding.process(link, this); } }, - catalogNode: function() { - if (!Linkify.regString.test(this.thread.OP.info.comment)) { - return; - } - return Linkify.process(this.nodes.comment); - }, process: function(node) { var data, end, endNode, i, index, length, links, part1, part2, ref, ref1, result, saved, snapshot, space, test, word; test = /[^\s"]+/g; @@ -13638,13 +17390,16 @@ Linkify = (function() { if ((length = index + word.length) === data.length) { test.lastIndex = 0; while ((saved = snapshot.snapshotItem(i++))) { - if (saved.nodeName === 'BR') { + if (saved.nodeName === 'BR' || (saved.parentElement.nodeName === 'P' && !saved.previousSibling)) { if ((part1 = word.match(/(https?:\/\/)?([a-z\d-]+\.)*[a-z\d-]+$/i)) && (part2 = (ref = snapshot.snapshotItem(i)) != null ? (ref1 = ref.data) != null ? ref1.match(/^(\.[a-z\d-]+)*\//i) : void 0 : void 0) && (part1[0] + part2[0]).search(Linkify.regString) === 0) { continue; } else { break; } } + if (saved.parentElement.nodeName === "A" && !Linkify.regString.test(word)) { + break; + } endNode = saved; data = saved.data; if (end = space.exec(data)) { @@ -13743,7 +17498,7 @@ ArchiveLink = (function() { ArchiveLink = { init: function() { var div, entry, i, len, ref, ref1, type; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Menu'] && Conf['Archive Link'])) { + if (!(g.SITE.software === 'yotsuba' && ((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Menu'] && Conf['Archive Link'])) { return; } div = $.el('div', { @@ -13751,7 +17506,7 @@ ArchiveLink = (function() { }); entry = { el: div, - order: 90, + order: 60, open: function(arg) { var ID, board, thread; ID = arg.ID, thread = arg.thread, board = arg.board; @@ -13786,14 +17541,15 @@ ArchiveLink = (function() { }); return true; } : function(post) { - var value; - value = type === 'country' ? post.info.flagCode : Filter[type](post); + var ref, typeParam, value; + typeParam = type === 'country' && post.info.flagCodeTroll ? 'troll_country' : type; + value = type === 'country' ? post.info.flagCode || ((ref = post.info.flagCodeTroll) != null ? ref.toLowerCase() : void 0) : Filter.values(type, post)[0]; if (!value) { return false; } el.href = Redirect.to('search', { boardID: post.board.ID, - type: type, + type: typeParam, value: value, isSearch: true }); @@ -13810,11 +17566,54 @@ ArchiveLink = (function() { }).call(this); +CopyTextLink = (function() { + var CopyTextLink; + + CopyTextLink = { + init: function() { + var a, ref; + if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Menu'] && Conf['Copy Text Link'])) { + return; + } + a = $.el('a', { + className: 'copy-text-link', + href: 'javascript:;', + textContent: 'Copy Text' + }); + $.on(a, 'click', CopyTextLink.copy); + return Menu.menu.addEntry({ + el: a, + order: 12, + open: function(post) { + CopyTextLink.text = (post.origin || post).commentOrig(); + return true; + } + }); + }, + copy: function() { + var el; + el = $.el('textarea', { + className: 'copy-text-element', + value: CopyTextLink.text + }); + $.add(d.body, el); + el.select(); + try { + d.execCommand('copy'); + } catch (error) {} + return $.rm(el); + } + }; + + return CopyTextLink; + +}).call(this); + DeleteLink = (function() { var DeleteLink; DeleteLink = { - auto: [{}, {}], + auto: [$.dict(), $.dict()], init: function() { var div, fileEl, fileEntry, postEl, postEntry, ref; if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Menu'] && Conf['Delete Link'])) { @@ -13915,27 +17714,28 @@ DeleteLink = (function() { onlyimgdel: fileOnly, pwd: QR.persona.getPassword() }; - form[post.ID] = 'delete'; + form[+post.ID] = 'delete'; return $.ajax($.id('delform').action.replace("/" + g.BOARD + "/", "/" + post.board + "/"), { responseType: 'document', withCredentials: true, - onload: function() { + onloadend: function() { return DeleteLink.load(link, post, fileOnly, this.response); }, - onerror: function() { - return DeleteLink.error(link, post); - } - }, { form: $.formData(form) }); }, load: function(link, post, fileOnly, resDoc) { var el, msg; + if (!resDoc) { + new Notice('warning', 'Connection error, please retry.', 20); + if (post.fullID === DeleteLink.post.fullID) { + $.on(link, 'click', DeleteLink.toggle); + } + return; + } link.textContent = DeleteLink.linkText(fileOnly); if (resDoc.title === '4chan - Banned') { - el = $.el('span', { - innerHTML: "You can't delete posts because you are banned." - }); + el = $.el('span', {innerHTML: "You can't delete posts because you are banned."}); return new Notice('warning', el, 20); } else if (msg = resDoc.getElementById('errmsg')) { new Notice('warning', msg.textContent, 20); @@ -13959,14 +17759,8 @@ DeleteLink = (function() { } } }, - error: function(link, post) { - new Notice('warning', 'Connection error, please retry.', 20); - if (post.fullID === DeleteLink.post.fullID) { - return $.on(link, 'click', DeleteLink.toggle); - } - }, cooldown: { - seconds: {}, + seconds: $.dict(), start: function(post, seconds) { if (DeleteLink.cooldown.seconds[post.fullID] != null) { return; @@ -14053,9 +17847,7 @@ Menu = (function() { className: 'menu-button', href: 'javascript:;' }); - $.extend(this.button, { - innerHTML: "" - }); + $.extend(this.button, {innerHTML: ""}); this.menu = new UI.Menu('post'); Callbacks.Post.push({ name: 'Menu', @@ -14071,7 +17863,7 @@ Menu = (function() { if (this.isClone) { button = $('.menu-button', this.nodes.info); $.rmClass(button, 'active'); - $.rm($('.dialog', button)); + $.rm($('.dialog', this.nodes.info)); Menu.makeButton(this, button); return; } @@ -14104,26 +17896,21 @@ ReportLink = (function() { } a = $.el('a', { className: 'report-link', - href: 'javascript:;' + href: 'javascript:;', + textContent: 'Report' }); $.on(a, 'click', ReportLink.report); return Menu.menu.addEntry({ el: a, order: 10, open: function(post) { - if (!(post.isDead || (post.thread.isDead && !post.thread.isArchived))) { - a.textContent = 'Report'; - ReportLink.url = "//sys.4chan.org/" + post.board + "/imgboard.php?mode=report&no=" + post; - if ((Conf['Use Recaptcha v1 in Reports'] && Main.jsEnabled) || d.cookie.indexOf('pass_enabled=1') >= 0) { - ReportLink.url += '&altc=1'; - ReportLink.dims = 'width=350,height=275'; - } else { - ReportLink.dims = 'width=400,height=550'; - } + ReportLink.url = "//sys." + (location.hostname.split('.')[1]) + ".org/" + post.board + "/imgboard.php?mode=report&no=" + post; + if (d.cookie.indexOf('pass_enabled=1') >= 0) { + ReportLink.dims = 'width=350,height=275'; } else { - ReportLink.url = ''; + ReportLink.dims = 'width=400,height=550'; } - return !!ReportLink.url; + return true; } }); }, @@ -14164,10 +17951,6 @@ AntiAutoplay = (function() { name: 'Disable Autoplaying Sounds', cb: this.node }); - Callbacks.CatalogThread.push({ - name: 'Disable Autoplaying Sounds', - cb: this.node - }); return $.ready((function(_this) { return function() { return _this.process(d.body); @@ -14187,22 +17970,27 @@ AntiAutoplay = (function() { return $.addClass(audio, 'controls-added'); }, node: function() { - return AntiAutoplay.process(this.nodes.root); + return AntiAutoplay.process(this.nodes.comment); }, process: function(root) { var i, iframe, j, len, len1, object, ref, ref1; ref = $$('iframe[src*="youtube"][src*="autoplay=1"]', root); for (i = 0, len = ref.length; i < len; i++) { iframe = ref[i]; - iframe.src = iframe.src.replace(/\?autoplay=1&?/, '?').replace('&autoplay=1', ''); - $.addClass(iframe, 'autoplay-removed'); + AntiAutoplay.processVideo(iframe, 'src'); } ref1 = $$('object[data*="youtube"][data*="autoplay=1"]', root); for (j = 0, len1 = ref1.length; j < len1; j++) { object = ref1[j]; - object.data = object.data.replace(/\?autoplay=1&?/, '?').replace('&autoplay=1', ''); - $.addClass(object, 'autoplay-removed'); + AntiAutoplay.processVideo(object, 'data'); + } + }, + processVideo: function(el, attr) { + el[attr] = el[attr].replace(/\?autoplay=1&?/, '?').replace('&autoplay=1', ''); + if (window.getComputedStyle(el).display === 'none') { + el.style.display = 'block'; } + return $.addClass(el, 'autoplay-removed'); } }; @@ -14215,7 +18003,6 @@ Banner = (function() { slice = [].slice; Banner = { - banners: ["0.jpg","1.jpg","2.jpg","4.jpg","6.jpg","7.jpg","8.jpg","9.jpg","10.jpg","11.jpg","12.jpg","13.jpg","14.jpg","16.jpg","17.jpg","18.jpg","19.jpg","20.jpg","21.jpg","22.jpg","24.jpg","25.jpg","26.jpg","28.jpg","29.jpg","33.jpg","38.jpg","39.jpg","43.jpg","44.jpg","45.jpg","46.jpg","47.jpg","52.jpg","54.jpg","57.jpg","59.jpg","60.jpg","61.jpg","64.jpg","66.jpg","67.jpg","69.jpg","71.jpg","72.jpg","76.jpg","77.jpg","81.jpg","82.jpg","83.jpg","84.jpg","88.jpg","90.jpg","91.jpg","96.jpg","98.jpg","99.jpg","100.jpg","104.jpg","106.jpg","116.jpg","119.jpg","137.jpg","140.jpg","148.jpg","149.jpg","150.jpg","154.jpg","156.jpg","157.jpg","158.jpg","159.jpg","161.jpg","162.jpg","164.jpg","165.jpg","166.jpg","167.jpg","168.jpg","169.jpg","170.jpg","171.jpg","172.jpg","173.jpg","174.jpg","175.jpg","176.jpg","178.jpg","179.jpg","180.jpg","181.jpg","182.jpg","183.jpg","186.jpg","189.jpg","190.jpg","192.jpg","193.jpg","194.jpg","197.jpg","198.jpg","200.jpg","201.jpg","202.jpg","203.jpg","205.jpg","206.jpg","207.jpg","208.jpg","210.jpg","213.jpg","214.jpg","215.jpg","216.jpg","218.jpg","219.jpg","220.jpg","221.jpg","222.jpg","223.jpg","224.jpg","227.jpg","0.png","1.png","2.png","3.png","5.png","6.png","9.png","10.png","11.png","12.png","14.png","16.png","19.png","20.png","21.png","22.png","23.png","24.png","26.png","27.png","28.png","29.png","30.png","31.png","32.png","33.png","34.png","37.png","39.png","40.png","41.png","42.png","43.png","44.png","45.png","48.png","49.png","50.png","51.png","52.png","53.png","57.png","58.png","59.png","64.png","66.png","67.png","68.png","69.png","70.png","71.png","72.png","76.png","78.png","79.png","81.png","82.png","85.png","86.png","87.png","89.png","95.png","98.png","100.png","101.png","102.png","105.png","106.png","107.png","109.png","110.png","111.png","112.png","113.png","114.png","115.png","116.png","118.png","119.png","120.png","121.png","122.png","123.png","126.png","128.png","130.png","134.png","136.png","138.png","139.png","140.png","142.png","145.png","146.png","149.png","150.png","151.png","152.png","153.png","154.png","155.png","156.png","157.png","158.png","159.png","160.png","163.png","164.png","165.png","166.png","167.png","168.png","169.png","170.png","171.png","172.png","173.png","174.png","178.png","179.png","180.png","181.png","182.png","184.png","186.png","188.png","190.png","192.png","193.png","194.png","195.png","196.png","197.png","198.png","200.png","202.png","203.png","205.png","206.png","207.png","209.png","212.png","213.png","214.png","216.png","217.png","218.png","219.png","220.png","221.png","222.png","223.png","224.png","225.png","226.png","229.png","231.png","232.png","233.png","234.png","235.png","237.png","238.png","239.png","240.png","241.png","242.png","244.png","245.png","246.png","247.png","248.png","249.png","250.png","253.png","254.png","255.png","256.png","257.png","258.png","259.png","260.png","262.png","268.png","0.gif","1.gif","2.gif","3.gif","4.gif","5.gif","6.gif","7.gif","8.gif","9.gif","10.gif","12.gif","13.gif","14.gif","15.gif","16.gif","18.gif","19.gif","20.gif","21.gif","22.gif","23.gif","24.gif","28.gif","29.gif","30.gif","33.gif","34.gif","35.gif","36.gif","37.gif","39.gif","40.gif","42.gif","44.gif","45.gif","46.gif","48.gif","50.gif","52.gif","54.gif","55.gif","57.gif","58.gif","59.gif","60.gif","61.gif","63.gif","64.gif","66.gif","67.gif","68.gif","69.gif","70.gif","72.gif","73.gif","75.gif","76.gif","77.gif","78.gif","80.gif","81.gif","82.gif","83.gif","86.gif","87.gif","88.gif","92.gif","93.gif","94.gif","95.gif","96.gif","97.gif","98.gif","99.gif","100.gif","101.gif","102.gif","103.gif","104.gif","105.gif","106.gif","108.gif","109.gif","110.gif","111.gif","112.gif","113.gif","115.gif","116.gif","117.gif","118.gif","119.gif","120.gif","122.gif","123.gif","124.gif","127.gif","129.gif","130.gif","131.gif","134.gif","135.gif","136.gif","138.gif","139.gif","141.gif","144.gif","146.gif","148.gif","149.gif","153.gif","154.gif","155.gif","157.gif","158.gif","159.gif","160.gif","161.gif","162.gif","164.gif","166.gif","167.gif","168.gif","169.gif","170.gif","171.gif","172.gif","173.gif","174.gif","175.gif","176.gif","177.gif","178.gif","181.gif","182.gif","183.gif","185.gif","186.gif","187.gif","188.gif","189.gif","190.gif","191.gif","192.gif","193.gif","195.gif","196.gif","197.gif","200.gif","201.gif","202.gif","203.gif","204.gif","205.gif","206.gif","207.gif","208.gif","209.gif","210.gif","211.gif","212.gif","213.gif","214.gif","215.gif","216.gif","217.gif","219.gif","220.gif","221.gif","222.gif","224.gif","225.gif","226.gif","227.gif","228.gif","230.gif","232.gif","233.gif","234.gif","235.gif","238.gif","240.gif","241.gif","243.gif","244.gif","245.gif","246.gif","247.gif","249.gif","250.gif","251.gif","253.gif"], init: function() { if (Conf['Custom Board Titles']) { this.db = new DataBoard('customTitles', null, true); @@ -14237,6 +18024,9 @@ Banner = (function() { var banner, children; banner = $(".boardBanner"); children = banner.children; + if (g.VIEW === 'thread' && Conf['Remove Thread Excerpt']) { + Banner.setTitle(children[1].textContent); + } children[0].title = "Click to change"; $.on(children[0], 'click', Banner.cb.toggle); if (Conf['Custom Board Titles']) { @@ -14269,7 +18059,7 @@ Banner = (function() { toggle: function() { var banner, i, ref; if (!((ref = Banner.choices) != null ? ref.length : void 0)) { - Banner.choices = Banner.banners.slice(); + Banner.choices = Conf['knownBanners'].split(',').slice(); } i = Math.floor(Banner.choices.length * Math.random()); banner = Banner.choices.splice(i, 1); @@ -14324,7 +18114,7 @@ Banner = (function() { } } }, - original: {}, + original: $.dict(), custom: function(child) { var className, data, event, j, len, ref; className = child.className; @@ -14362,7 +18152,7 @@ CatalogLinks = (function() { CatalogLinks = { init: function() { var el, input, selector; - if ((Conf['External Catalog'] || Conf['JSON Index']) && !(Conf['JSON Index'] && g.VIEW === 'index')) { + if (g.SITE.software === 'yotsuba' && (Conf['External Catalog'] || Conf['JSON Index']) && !(Conf['JSON Index'] && g.VIEW === 'index')) { selector = (function() { switch (g.VIEW) { case 'thread': @@ -14375,7 +18165,7 @@ CatalogLinks = (function() { } })(); $.ready(function() { - var catalogLink, i, len, link, ref; + var base, catalogLink, catalogURL, i, len, link, link2, ref; ref = $$(selector); for (i = 0, len = ref.length; i < len; i++) { link = ref[i]; @@ -14389,26 +18179,23 @@ CatalogLinks = (function() { case "/" + g.BOARD + "/catalog": link.href = CatalogLinks.catalog(); } - if (g.VIEW === 'catalog' && Conf['JSON Index'] && Conf['Use 4chan X Catalog']) { + if (g.VIEW === 'catalog' && (catalogURL = CatalogLinks.catalog()) !== (typeof (base = g.SITE.urls).catalog === "function" ? base.catalog(g.BOARD) : void 0)) { catalogLink = link.parentNode.cloneNode(true); - catalogLink.firstElementChild.textContent = '4chan X Catalog'; - catalogLink.firstElementChild.href = CatalogLinks.catalog(); + link2 = catalogLink.firstElementChild; + link2.href = catalogURL; + link2.textContent = link2.hostname === location.hostname ? '4chan X Catalog' : 'External Catalog'; $.after(link.parentNode, [$.tn(' '), catalogLink]); } } }); } - if (Conf['JSON Index'] && Conf['Use 4chan X Catalog']) { + if (g.SITE.software === 'yotsuba' && Conf['JSON Index'] && Conf['Use 4chan X Catalog']) { Callbacks.Post.push({ name: 'Catalog Link Rewrite', cb: this.node }); - Callbacks.CatalogThread.push({ - name: 'Catalog Link Rewrite', - cb: this.node - }); } - if (Conf['Catalog Links']) { + if ((this.enabled = Conf['Catalog Links'])) { CatalogLinks.el = el = UI.checkbox('Header catalog links', 'Catalog Links'); el.id = 'toggleCatalog'; input = $('input', el); @@ -14425,66 +18212,121 @@ CatalogLinks = (function() { ref = $$('a', this.nodes.comment); for (i = 0, len = ref.length; i < len; i++) { a = ref[i]; - if (m = a.href.match(/^https?:\/\/boards\.4chan\.org\/([^\/]+)\/catalog(#s=.*)?/)) { - a.href = "//boards.4chan.org/" + m[1] + "/" + (m[2] || '#catalog'); + if (m = a.href.match(/^https?:\/\/(boards\.4chan(?:nel)?\.org\/[^\/]+)\/catalog(#s=.*)?/)) { + a.href = "//" + m[1] + "/" + (m[2] || '#catalog'); } } }, - initBoardList: function() { - if (!CatalogLinks.el) { - return; - } - return CatalogLinks.set(Conf['Header catalog links']); - }, toggle: function() { $.event('CloseMenu'); $.set('Header catalog links', this.checked); return CatalogLinks.set(this.checked); }, set: function(useCatalog) { - var a, board, i, len, ref, ref1; - ref = $$('a:not([data-only])', Header.boardList).concat($$('a', Header.bottomBoardList)); + Conf['Header catalog links'] = useCatalog; + CatalogLinks.setLinks(Header.boardList); + CatalogLinks.setLinks(Header.bottomBoardList); + CatalogLinks.el.title = "Turn catalog links " + (useCatalog ? 'off' : 'on') + "."; + return $('input', CatalogLinks.el).checked = useCatalog; + }, + setLinks: function(list) { + var VIEW, a, board, boardID, i, len, ref, ref1, ref2, ref3, siteID, tail, url; + if (!(((ref = CatalogLinks.enabled) != null ? ref : Conf['Catalog Links']) && list)) { + return; + } + tail = /(?:index)?(?:\.\w+)?$/; + ref1 = $$('a:not([data-only])', list); + for (i = 0, len = ref1.length; i < len; i++) { + a = ref1[i]; + ref2 = a.dataset, siteID = ref2.siteID, boardID = ref2.boardID; + if (!(siteID && boardID)) { + ref3 = Site.parseURL(a), siteID = ref3.siteID, boardID = ref3.boardID, VIEW = ref3.VIEW; + if (!(siteID && boardID && (VIEW === 'index' || VIEW === 'catalog') && (a.dataset.indexOptions || a.href.replace(tail, '') === (Get.url(VIEW, { + siteID: siteID, + boardID: boardID + }) || '').replace(tail, '')))) { + continue; + } + $.extend(a.dataset, { + siteID: siteID, + boardID: boardID + }); + } + board = { + siteID: siteID, + boardID: boardID + }; + url = Conf['Header catalog links'] ? CatalogLinks.catalog(board) : Get.url('index', board); + if (url) { + a.href = url; + if (a.dataset.indexOptions && url.split('#')[0] === Get.url('index', board)) { + a.href += (a.hash ? '/' : '#') + a.dataset.indexOptions; + } + } + } + }, + externalParse: function() { + var board, boards, excludes, i, len, line, ref, ref1, ref2, url; + CatalogLinks.externalList = $.dict(); + ref = Conf['externalCatalogURLs'].split('\n'); for (i = 0, len = ref.length; i < len; i++) { - a = ref[i]; - if (((ref1 = a.hostname) !== 'boards.4chan.org' && ref1 !== 'catalog.neet.tv') || !(board = a.pathname.split('/')[1]) || (board === 'f' || board === 'status' || board === '4chan') || a.pathname.split('/')[2] === 'archive' || $.hasClass(a, 'external')) { + line = ref[i]; + if (line[0] === '#') { continue; } - a.href = useCatalog ? CatalogLinks.catalog(board) : "/" + board + "/"; - if (a.dataset.indexOptions && a.hostname === 'boards.4chan.org' && a.pathname.split('/')[2] === '') { - a.href += (a.hash ? '/' : '#') + a.dataset.indexOptions; + url = line.split(';')[0]; + boards = Filter.parseBoards(((ref1 = line.match(/;boards:([^;]+)/)) != null ? ref1[1] : void 0) || '*'); + excludes = Filter.parseBoards((ref2 = line.match(/;exclude:([^;]+)/)) != null ? ref2[1] : void 0) || $.dict(); + for (board in boards) { + if (!(excludes[board] || excludes[board.split('/')[0] + '/*'])) { + CatalogLinks.externalList[board] = url; + } } } - CatalogLinks.el.title = "Turn catalog links " + (useCatalog ? 'off' : 'on') + "."; - return $('input', CatalogLinks.el).checked = useCatalog; + }, + external: function(arg) { + var boardID, external, siteID; + siteID = arg.siteID, boardID = arg.boardID; + if (!CatalogLinks.externalList) { + CatalogLinks.externalParse(); + } + external = CatalogLinks.externalList[siteID + "/" + boardID] || CatalogLinks.externalList[siteID + "/*"]; + if (external) { + return external.replace(/%board/g, boardID); + } else { + return void 0; + } + }, + jsonIndex: function(board, hash) { + if (g.SITE.ID === board.siteID && g.BOARD.ID === board.boardID && g.VIEW === 'index') { + return hash; + } else { + return Get.url('index', board) + hash; + } }, catalog: function(board) { + var external, nativeCatalog; if (board == null) { - board = g.BOARD.ID; - } - if (Conf['External Catalog'] && (board === 'a' || board === 'c' || board === 'g' || board === 'biz' || board === 'k' || board === 'm' || board === 'o' || board === 'p' || board === 'v' || board === 'vg' || board === 'vr' || board === 'w' || board === 'wg' || board === 'cm' || board === '3' || board === 'adv' || board === 'an' || board === 'asp' || board === 'cgl' || board === 'ck' || board === 'co' || board === 'diy' || board === 'fa' || board === 'fit' || board === 'gd' || board === 'int' || board === 'jp' || board === 'lit' || board === 'mlp' || board === 'mu' || board === 'n' || board === 'out' || board === 'po' || board === 'sci' || board === 'sp' || board === 'tg' || board === 'toy' || board === 'trv' || board === 'tv' || board === 'vp' || board === 'wsg' || board === 'x' || board === 'f' || board === 'pol' || board === 's4s' || board === 'lgbt')) { - return "http://catalog.neet.tv/" + board + "/"; - } else if (Conf['JSON Index'] && Conf['Use 4chan X Catalog']) { - if (g.BOARD.ID === board && g.VIEW === 'index') { - return '#catalog'; - } else { - return "/" + board + "/#catalog"; - } + board = g.BOARD; + } + if (Conf['External Catalog'] && (external = CatalogLinks.external(board))) { + return external; + } else if (Index.enabledOn(board) && Conf['Use 4chan X Catalog']) { + return CatalogLinks.jsonIndex(board, '#catalog'); + } else if ((nativeCatalog = Get.url('catalog', board))) { + return nativeCatalog; } else { - return "/" + board + "/catalog"; + return CatalogLinks.external(board); } }, index: function(board) { if (board == null) { - board = g.BOARD.ID; + board = g.BOARD; } - if (Conf['JSON Index'] && board !== 'f') { - if (g.BOARD.ID === board && g.VIEW === 'index') { - return '#index'; - } else { - return "/" + board + "/#index"; - } + if (Index.enabledOn(board)) { + return CatalogLinks.jsonIndex(board, '#index'); } else { - return "/" + board + "/"; + return Get.url('index', board); } } }; @@ -14504,7 +18346,7 @@ CustomCSS = (function() { return this.addStyle(); }, addStyle: function() { - return this.style = $.addStyle(Conf['usercss'], 'custom-css', '#fourchanx-css'); + return this.style = $.addStyle(CSS.sub(Conf['usercss']), 'custom-css', '#fourchanx-css'); }, rmStyle: function() { if (this.style) { @@ -14516,7 +18358,7 @@ CustomCSS = (function() { if (!this.style) { return this.addStyle(); } - return this.style.textContent = Conf['usercss']; + return this.style.textContent = CSS.sub(Conf['usercss']); } }; @@ -14532,12 +18374,6 @@ ExpandComment = (function() { if (g.VIEW !== 'index' || !Conf['Comment Expansion'] || Conf['JSON Index']) { return; } - if (g.BOARD.ID === 'g') { - this.callbacks.push(Fourchan.code); - } - if (g.BOARD.ID === 'sci') { - this.callbacks.push(Fourchan.math); - } return Callbacks.Post.push({ name: 'Comment Expansion', cb: this.node @@ -14565,7 +18401,10 @@ ExpandComment = (function() { return; } a.textContent = "Post No." + post + " Loading..."; - return $.cache("//a.4cdn.org" + (a.pathname.split(/\/+/).splice(0, 4).join('/')) + ".json", function() { + return $.cache(g.SITE.urls.threadJSON({ + boardID: post.boardID, + threadID: post.threadID + }), function() { return ExpandComment.parse(this, a, post); }); }, @@ -14583,12 +18422,12 @@ ExpandComment = (function() { var callback, clone, comment, href, i, j, k, len, len1, len2, postObj, posts, quote, ref, ref1, spoilerRange, status; status = req.status; if (status !== 200 && status !== 304) { - a.textContent = "Error " + req.statusText + " (" + status + ")"; + a.textContent = status ? "Error " + req.statusText + " (" + status + ")" : 'Connection Error'; return; } posts = req.response.posts; if (spoilerRange = posts[0].custom_spoiler) { - Build.spoilerRange[g.BOARD] = spoilerRange; + g.SITE.Build.spoilerRange[g.BOARD] = spoilerRange; } for (i = 0, len = posts.length; i < len; i++) { postObj = posts[i]; @@ -14638,13 +18477,13 @@ ExpandThread = (function() { slice = [].slice; ExpandThread = { - statuses: {}, + statuses: $.dict(), init: function() { - if (g.VIEW === 'thread' || !Conf['Thread Expansion']) { + if (!(g.VIEW === 'index' && Conf['Thread Expansion'])) { return; } if (Conf['JSON Index']) { - return $.on(d, 'IndexRefresh', this.onIndexRefresh); + return $.on(d, 'IndexRefreshInternal', this.onIndexRefresh); } else { return Callbacks.Thread.push({ name: 'Expand Thread', @@ -14655,29 +18494,30 @@ ExpandThread = (function() { } }, setButton: function(thread) { - var a; - if (!(a = $.x('following-sibling::*[contains(@class,"summary")][1]', thread.OP.nodes.root))) { + var a, ref; + if (!(thread.nodes.root && (a = $('.summary', thread.nodes.root)))) { return; } - a.textContent = Build.summaryText.apply(Build, ['+'].concat(slice.call(a.textContent.match(/\d+/g)))); + a.textContent = (ref = g.SITE.Build).summaryText.apply(ref, ['+'].concat(slice.call(a.textContent.match(/\d+/g)))); a.style.cursor = 'pointer'; return $.on(a, 'click', ExpandThread.cbToggle); }, disconnect: function(refresh) { - var ref, ref1, status, threadID; + var oldReq, ref, status, threadID; if (g.VIEW === 'thread' || !Conf['Thread Expansion']) { return; } ref = ExpandThread.statuses; for (threadID in ref) { status = ref[threadID]; - if ((ref1 = status.req) != null) { - ref1.abort(); + if ((oldReq = status.req)) { + delete status.req; + oldReq.abort(); } delete ExpandThread.statuses[threadID]; } if (!refresh) { - return $.off(d, 'IndexRefresh', this.onIndexRefresh); + return $.off(d, 'IndexRefreshInternal', this.onIndexRefresh); } }, onIndexRefresh: function() { @@ -14687,62 +18527,66 @@ ExpandThread = (function() { }); }, cbToggle: function(e) { - if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { + if ($.modifiedClick(e)) { return; } e.preventDefault(); return ExpandThread.toggle(Get.threadFromNode(this)); }, + cbToggleBottom: function(e) { + var bottom, thread; + if ($.modifiedClick(e)) { + return; + } + e.preventDefault(); + thread = Get.threadFromNode(this); + $.rm(this); + bottom = thread.nodes.root.getBoundingClientRect().bottom; + ExpandThread.toggle(thread); + return window.scrollBy(0, thread.nodes.root.getBoundingClientRect().bottom - bottom); + }, toggle: function(thread) { - var a, threadRoot; - threadRoot = thread.OP.nodes.root.parentNode; - if (!(a = $('.summary', threadRoot))) { + var a; + if (!(thread.nodes.root && (a = $('.summary', thread.nodes.root)))) { return; } if (thread.ID in ExpandThread.statuses) { - return ExpandThread.contract(thread, a, threadRoot); + return ExpandThread.contract(thread, a, thread.nodes.root); } else { return ExpandThread.expand(thread, a); } }, expand: function(thread, a) { - var status; + var ref, status; ExpandThread.statuses[thread] = status = {}; - a.textContent = Build.summaryText.apply(Build, ['...'].concat(slice.call(a.textContent.match(/\d+/g)))); - return status.req = $.cache("//a.4cdn.org/" + thread.board + "/thread/" + thread + ".json", function() { + a.textContent = (ref = g.SITE.Build).summaryText.apply(ref, ['...'].concat(slice.call(a.textContent.match(/\d+/g)))); + status.req = $.cache(g.SITE.urls.threadJSON({ + boardID: thread.board.ID, + threadID: thread.ID + }), function() { + if (this !== status.req) { + return; + } delete status.req; return ExpandThread.parse(this, thread, a); }); + return status.numReplies = $$(g.SITE.selectors.replyOriginal, thread.nodes.root).length; }, contract: function(thread, a, threadRoot) { - var filesCount, i, inlined, len, num, postsCount, replies, reply, status; + var filesCount, i, inlined, len, oldReq, postsCount, ref, replies, reply, status; status = ExpandThread.statuses[thread]; delete ExpandThread.statuses[thread]; - if (status.req) { - status.req.abort(); + if ((oldReq = status.req)) { + delete status.req; + oldReq.abort(); if (a) { - a.textContent = Build.summaryText.apply(Build, ['+'].concat(slice.call(a.textContent.match(/\d+/g)))); + a.textContent = (ref = g.SITE.Build).summaryText.apply(ref, ['+'].concat(slice.call(a.textContent.match(/\d+/g)))); } return; } replies = $$('.thread > .replyContainer', threadRoot); - if (!Conf['JSON Index'] || Conf['Show Replies']) { - num = (function() { - if (thread.isSticky) { - return 1; - } else { - switch (g.BOARD.ID) { - case 'b': - case 'vg': - return 3; - case 't': - return 1; - default: - return 5; - } - } - })(); - replies = replies.slice(0, -num); + if (status.numReplies) { + replies = replies.slice(0, -status.numReplies); } postsCount = 0; filesCount = 0; @@ -14759,15 +18603,19 @@ ExpandThread = (function() { } $.rm(reply); } - return a.textContent = Build.summaryText('+', postsCount, filesCount); + if (Index.enabled) { + $.event('PostsRemoved', null, a.parentNode); + } + a.textContent = g.SITE.Build.summaryText('+', postsCount, filesCount); + return $.rm($('.summary-bottom', threadRoot)); }, parse: function(req, thread, a) { - var filesCount, i, len, post, postData, posts, postsCount, postsRoot, ref, ref1, root; + var a2, filesCount, i, len, post, postData, posts, postsCount, postsRoot, ref, ref1, root; if ((ref = req.status) !== 200 && ref !== 304) { - a.textContent = "Error " + req.statusText + " (" + req.status + ")"; + a.textContent = req.status ? "Error " + req.statusText + " (" + req.status + ")" : 'Connection Error'; return; } - Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler; + g.SITE.Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler; posts = []; postsRoot = []; filesCount = 0; @@ -14777,14 +18625,15 @@ ExpandThread = (function() { if (postData.no === thread.ID) { continue; } - if ((post = thread.posts[postData.no]) && !post.isFetchedQuote) { + if ((post = thread.posts.get(postData.no)) && !post.isFetchedQuote) { if ('file' in post) { filesCount++; } - postsRoot.push(post.nodes.root); + root = post.nodes.root; + postsRoot.push(root); continue; } - root = Build.postFromObject(postData, thread.board.ID); + root = g.SITE.Build.postFromObject(postData, thread.board.ID); post = new Post(root, thread, thread.board); if ('file' in post) { filesCount++; @@ -14794,9 +18643,15 @@ ExpandThread = (function() { } Main.callbackNodes('Post', posts); $.after(a, postsRoot); - $.event('PostsInserted'); + $.event('PostsInserted', null, a.parentNode); postsCount = postsRoot.length; - return a.textContent = Build.summaryText('-', postsCount, filesCount); + a.textContent = g.SITE.Build.summaryText('-', postsCount, filesCount); + if (root) { + a2 = a.cloneNode(true); + a2.classList.add('summary-bottom'); + $.on(a2, 'click', ExpandThread.cbToggleBottom); + return $.after(root, a2); + } } }; @@ -14810,7 +18665,7 @@ FileInfo = (function() { FileInfo = { init: function() { var ref; - if (((ref = g.VIEW) !== 'index' && ref !== 'thread') || !Conf['File Info Formatting']) { + if (((ref = g.VIEW) !== 'index' && ref !== 'thread' && ref !== 'archive') || !Conf['File Info Formatting']) { return; } return Callbacks.Post.push({ @@ -14819,7 +18674,7 @@ FileInfo = (function() { }); }, node: function() { - var a, i, info, len, oldInfo, ref; + var a, i, info, j, len, len1, oldInfo, ref, ref1; if (!this.file) { return; } @@ -14829,6 +18684,11 @@ FileInfo = (function() { a = ref[i]; $.on(a, 'click', ImageCommon.download); } + ref1 = $$('.file-info .quick-filter-md5', this.file.text); + for (j = 0, len1 = ref1.length; j < len1; j++) { + a = ref1[j]; + $.on(a, 'click', Filter.quickFilterMD5); + } return; } oldInfo = $.el('span', { @@ -14843,107 +18703,79 @@ FileInfo = (function() { return $.prepend(this.file.text, info); }, format: function(formatString, post, outputNode) { - var a, i, len, output, ref; + var a, i, j, len, len1, output, ref, ref1; output = []; formatString.replace(/%(.)|[^%]+/g, function(s, c) { - output.push(c in FileInfo.formatters ? FileInfo.formatters[c].call(post) : { - innerHTML: E(s) - }); + output.push($.hasOwn(FileInfo.formatters, c) ? FileInfo.formatters[c].call(post) : {innerHTML: E(s)}); return ''; }); - $.extend(outputNode, { - innerHTML: E.cat(output) - }); + $.extend(outputNode, {innerHTML: E.cat(output)}); ref = $$('.download-button', outputNode); for (i = 0, len = ref.length; i < len; i++) { a = ref[i]; $.on(a, 'click', ImageCommon.download); } + ref1 = $$('.quick-filter-md5', outputNode); + for (j = 0, len1 = ref1.length; j < len1; j++) { + a = ref1[j]; + $.on(a, 'click', Filter.quickFilterMD5); + } }, formatters: { t: function() { - return { - innerHTML: E(this.file.url.match(/[^/]*$/)[0]) - }; + return {innerHTML: E(this.file.url.match(/[^/]*$/)[0])}; }, T: function() { - return { - innerHTML: "" + (FileInfo.formatters.t.call(this)).innerHTML + "" - }; + return {innerHTML: "" + (FileInfo.formatters.t.call(this)).innerHTML + ""}; }, l: function() { - return { - innerHTML: "" + (FileInfo.formatters.n.call(this)).innerHTML + "" - }; + return {innerHTML: "" + (FileInfo.formatters.n.call(this)).innerHTML + ""}; }, L: function() { - return { - innerHTML: "" + (FileInfo.formatters.N.call(this)).innerHTML + "" - }; + return {innerHTML: "" + (FileInfo.formatters.N.call(this)).innerHTML + ""}; }, n: function() { var fullname, shortname; fullname = this.file.name; - shortname = Build.shortFilename(this.file.name, this.isReply); + shortname = SW.yotsuba.Build.shortFilename(this.file.name, this.isReply); if (fullname === shortname) { - return { - innerHTML: E(fullname) - }; + return {innerHTML: E(fullname)}; } else { - return { - innerHTML: "" + E(shortname) + "" + E(fullname) + "" - }; + return {innerHTML: "" + E(shortname) + "" + E(fullname) + ""}; } }, N: function() { - return { - innerHTML: E(this.file.name) - }; + return {innerHTML: E(this.file.name)}; }, d: function() { - return { - innerHTML: "" - }; + return {innerHTML: ""}; }, - p: function() { - return { - innerHTML: ((this.file.isSpoiler) ? "Spoiler, " : "") - }; + f: function() { + return {innerHTML: ""}; + }, + p: function() { + return {innerHTML: ((this.file.isSpoiler) ? "Spoiler, " : "")}; }, s: function() { - return { - innerHTML: E(this.file.size) - }; + return {innerHTML: E(this.file.size)}; }, B: function() { - return { - innerHTML: E(Math.round(this.file.sizeInBytes)) + " Bytes" - }; + return {innerHTML: E(Math.round(this.file.sizeInBytes)) + " Bytes"}; }, K: function() { - return { - innerHTML: E(Math.round(this.file.sizeInBytes/1024)) + " KB" - }; + return {innerHTML: E(Math.round(this.file.sizeInBytes/1024)) + " KB"}; }, M: function() { - return { - innerHTML: E(Math.round(this.file.sizeInBytes/1048576*100)/100) + " MB" - }; + return {innerHTML: E(Math.round(this.file.sizeInBytes/1048576*100)/100) + " MB"}; }, r: function() { - return { - innerHTML: E(this.file.dimensions || "PDF") - }; + return {innerHTML: E(this.file.dimensions || "PDF")}; }, g: function() { - return { - innerHTML: ((this.file.tag) ? ", " + E(this.file.tag) : "") - }; + return {innerHTML: ((this.file.tag) ? ", " + E(this.file.tag) : "")}; }, '%': function() { - return { - innerHTML: "%" - }; + return {innerHTML: "%"}; } } }; @@ -14991,16 +18823,20 @@ Fourchan = (function() { Fourchan = { init: function() { var ref; - if ((ref = g.VIEW) !== 'index' && ref !== 'thread') { + if (!(g.SITE.software === 'yotsuba' && ((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive'))) { return; } - if (g.BOARD.ID === 'g') { + BoardConfig.ready(this.initBoard); + return Main.ready(this.initReady); + }, + initBoard: function() { + if (g.BOARD.config.code_tags) { $.on(window, 'prettyprint:cb', function(e) { var post, pre; - if (!(post = g.posts[e.detail.ID])) { + if (!(post = g.posts.get(e.detail.ID))) { return; } - if (!(pre = $$('.prettyprint', post.nodes.comment)[e.detail.i])) { + if (!(pre = $$('.prettyprint', post.nodes.comment)[+e.detail.i])) { return; } if (!$.hasClass(pre, 'prettyprinted')) { @@ -15008,13 +18844,29 @@ Fourchan = (function() { return $.addClass(pre, 'prettyprinted'); } }); - $.globalEval('window.addEventListener(\'prettyprint\', function(e) {\n window.dispatchEvent(new CustomEvent(\'prettyprint:cb\', {\n detail: {\n ID: e.detail.ID,\n i: e.detail.i,\n html: prettyPrintOne(e.detail.html)\n }\n }));\n}, false);'); + $.global(function() { + return window.addEventListener('prettyprint', function(e) { + return window.dispatchEvent(new CustomEvent('prettyprint:cb', { + detail: { + ID: e.detail.ID, + i: e.detail.i, + html: window.prettyPrintOne(e.detail.html) + } + })); + }, false); + }); Callbacks.Post.push({ - name: 'Parse /g/ code', - cb: this.code + name: 'Parse [code] tags', + cb: Fourchan.code + }); + g.posts.forEach(function(post) { + if (post.callbacksExecuted) { + return Callbacks.Post.execute(post, ['Parse [code] tags'], true); + } }); + ExpandComment.callbacks.push(Fourchan.code); } - if (g.BOARD.ID === 'sci') { + if (g.BOARD.config.math_tags) { $.global(function() { return window.addEventListener('mathjax', function(e) { if (window.MathJax) { @@ -15033,24 +18885,26 @@ Fourchan = (function() { }, false); }); Callbacks.Post.push({ - name: 'Parse /sci/ math', - cb: this.math - }); - Callbacks.CatalogThread.push({ - name: 'Parse /sci/ math', - cb: this.math + name: 'Parse [math] tags', + cb: Fourchan.math }); - } - return Main.ready(function() { - return $.global(function() { - var j, len, node, ref1; - window.clickable_ids = false; - ref1 = document.querySelectorAll('.posteruid, .capcode'); - for (j = 0, len = ref1.length; j < len; j++) { - node = ref1[j]; - node.removeEventListener('click', window.idClick, false); + g.posts.forEach(function(post) { + if (post.callbacksExecuted) { + return Callbacks.Post.execute(post, ['Parse [math] tags'], true); } }); + return ExpandComment.callbacks.push(Fourchan.math); + } + }, + initReady: function() { + return $.global(function() { + var j, len, node, ref; + window.clickable_ids = false; + ref = document.querySelectorAll('.posteruid, .capcode'); + for (j = 0, len = ref.length; j < len; j++) { + node = ref[j]; + node.removeEventListener('click', window.idClick, false); + } }); }, code: function() { @@ -15113,9 +18967,8 @@ IDColor = (function() { if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Color User IDs'])) { return; } - this.ids = { - Heaven: [0, 0, 0, '#fff'] - }; + this.ids = $.dict(); + this.ids['Heaven'] = [0, 0, 0, '#fff']; return Callbacks.Post.push({ name: 'Color User IDs', cb: this.node @@ -15123,7 +18976,7 @@ IDColor = (function() { }, node: function() { var rgb, span, style, uid; - if (this.isClone || !((uid = this.info.uniqueID) && (span = $('span.hand', this.nodes.uniqueID)))) { + if (this.isClone || !((uid = this.info.uniqueID) && (span = this.nodes.uniqueID))) { return; } rgb = IDColor.ids[uid] || IDColor.compute(uid); @@ -15134,19 +18987,10 @@ IDColor = (function() { }, compute: function(uid) { var hash, rgb; - hash = IDColor.hash(uid); - rgb = [(hash >> 24) & 0xFF, (hash >> 16) & 0xFF, (hash >> 8) & 0xFF]; - rgb.push((rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114) > 125 ? '#000' : '#fff'); + hash = g.SITE.uidColor ? g.SITE.uidColor(uid) : parseInt(uid, 16); + rgb = [(hash >> 16) & 0xFF, (hash >> 8) & 0xFF, hash & 0xFF]; + rgb.push($.luma(rgb) > 125 ? '#000' : '#fff'); return this.ids[uid] = rgb; - }, - hash: function(uid) { - var i, msg; - msg = 0; - i = 0; - while (i < 8) { - msg = (msg << 5) - msg + uid.charCodeAt(i++); - } - return msg; } }; @@ -15170,8 +19014,8 @@ IDHighlight = (function() { }, uniqueID: null, node: function() { - if (this.nodes.uniqueID) { - $.on(this.nodes.uniqueID, 'click', IDHighlight.click(this)); + if (this.nodes.uniqueIDRoot) { + $.on(this.nodes.uniqueIDRoot, 'click', IDHighlight.click(this)); } if (this.nodes.capcode) { $.on(this.nodes.capcode, 'click', IDHighlight.click(this)); @@ -15199,6 +19043,47 @@ IDHighlight = (function() { }).call(this); +IDPostCount = (function() { + var IDPostCount; + + IDPostCount = { + init: function() { + if (!(g.VIEW === 'thread' && Conf['Count Posts by ID'])) { + return; + } + Callbacks.Thread.push({ + name: 'Count Posts by ID', + cb: function() { + return IDPostCount.thread = this; + } + }); + return Callbacks.Post.push({ + name: 'Count Posts by ID', + cb: this.node + }); + }, + node: function() { + if (this.nodes.uniqueID && this.thread === IDPostCount.thread) { + return $.on(this.nodes.uniqueID, 'mouseover', IDPostCount.count); + } + }, + count: function() { + var n, uniqueID; + uniqueID = Get.postFromNode(this).info.uniqueID; + n = 0; + IDPostCount.thread.posts.forEach(function(post) { + if (post.info.uniqueID === uniqueID) { + return n++; + } + }); + return this.title = n + " post" + (n === 1 ? '' : 's') + " by this ID"; + } + }; + + return IDPostCount; + +}).call(this); + Keybinds = (function() { var Keybinds; @@ -15227,7 +19112,7 @@ Keybinds = (function() { return Conf[hotkey] = key; }, keydown: function(e) { - var form, i, key, len, notification, notifications, op, ref, ref1, ref2, ref3, ref4, ref5, searchInput, target, thread, threadRoot; + var base, base1, catalog, i, key, len, notification, notifications, post, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, searchInput, target, thread, threadRoot; if (!(key = Keybinds.keyCode(e))) { return; } @@ -15237,11 +19122,9 @@ Keybinds = (function() { return; } } - if (!(((ref1 = g.VIEW) !== 'index' && ref1 !== 'thread') || g.VIEW === 'index' && Conf['JSON Index'] && Conf['Index Mode'] === 'catalog' || g.VIEW === 'index' && g.BOARD.ID === 'f')) { + if ((ref1 = g.VIEW) === 'index' || ref1 === 'thread') { threadRoot = Nav.getThread(); - if (op = $('.op', threadRoot)) { - thread = Get.postFromNode(op).thread; - } + thread = Get.threadFromRoot(threadRoot); } switch (key) { case Conf['Toggle board list']: @@ -15324,6 +19207,24 @@ Keybinds = (function() { } Keybinds.sage(); break; + case Conf['Toggle Cooldown']: + if (!(QR.nodes && !QR.nodes.el.hidden && $.hasClass(QR.nodes.fileSubmit, 'custom-cooldown'))) { + return; + } + QR.toggleCustomCooldown(); + break; + case Conf['Post from URL']: + if (!QR.postingIsEnabled) { + return; + } + QR.handleUrl(''); + break; + case Conf['Add new post']: + if (!QR.postingIsEnabled) { + return; + } + QR.addPost(); + break; case Conf['Submit QR']: if (!(QR.nodes && !QR.nodes.el.hidden)) { return; @@ -15335,13 +19236,13 @@ Keybinds = (function() { case Conf['Update']: switch (g.VIEW) { case 'thread': - if (!Conf['Thread Updater']) { + if (!ThreadUpdater.enabled) { return; } ThreadUpdater.update(); break; case 'index': - if (!(Conf['JSON Index'] && g.BOARD.ID !== 'f')) { + if (!Index.enabled) { return; } Index.update(); @@ -15362,17 +19263,38 @@ Keybinds = (function() { } ThreadWatcher.buttonFetchAll(); break; + case Conf['Toggle thread watcher']: + if (!ThreadWatcher.enabled) { + return; + } + ThreadWatcher.toggleWatcher(); + break; + case Conf['Toggle threading']: + if (!QuoteThreading.ready) { + return; + } + QuoteThreading.toggleThreading(); + break; + case Conf['Mark thread read']: + if (!(g.VIEW === 'index' && thread && UnreadIndex.enabled)) { + return; + } + UnreadIndex.markRead.call(threadRoot); + break; case Conf['Expand image']: if (!(ImageExpand.enabled && threadRoot)) { return; } - Keybinds.img(threadRoot); + post = Get.postFromNode(Keybinds.post(threadRoot)); + if (post.file) { + ImageExpand.toggle(post); + } break; case Conf['Expand images']: - if (!(ImageExpand.enabled && threadRoot)) { + if (!ImageExpand.enabled) { return; } - Keybinds.img(threadRoot, true); + ImageExpand.cb.toggleAll(); break; case Conf['Open Gallery']: if (!Gallery.enabled) { @@ -15381,91 +19303,94 @@ Keybinds = (function() { Gallery.cb.toggle(); break; case Conf['fappeTyme']: - if (!(Conf['Fappe Tyme'] && ((ref2 = g.VIEW) === 'index' || ref2 === 'thread'))) { + if (!((ref2 = FappeTyme.nodes) != null ? ref2.fappe : void 0)) { return; } FappeTyme.toggle('fappe'); break; case Conf['werkTyme']: - if (!(Conf['Werk Tyme'] && ((ref3 = g.VIEW) === 'index' || ref3 === 'thread'))) { + if (!((ref3 = FappeTyme.nodes) != null ? ref3.werk : void 0)) { return; } FappeTyme.toggle('werk'); break; case Conf['Front page']: - if (Conf['JSON Index'] && g.VIEW === 'index' && g.BOARD.ID !== 'f') { + if (Index.enabled) { Index.userPageNav(1); } else { - window.location = "/" + g.BOARD + "/"; + location.href = "/" + g.BOARD + "/"; } break; case Conf['Open front page']: - $.open("/" + g.BOARD + "/"); + $.open(location.origin + "/" + g.BOARD + "/"); break; case Conf['Next page']: - if (!(g.VIEW === 'index' && g.BOARD.ID !== 'f')) { + if (!(g.VIEW === 'index' && !(typeof (base = g.SITE).isOnePage === "function" ? base.isOnePage(g.BOARD) : void 0))) { return; } - if (Conf['JSON Index']) { + if (Index.enabled) { if ((ref4 = Conf['Index Mode']) !== 'paged' && ref4 !== 'infinite') { return; } $('.next button', Index.pagelist).click(); } else { - if (form = $('.next form')) { - window.location = form.action; + if ((ref5 = $(g.SITE.selectors.nav.next)) != null) { + ref5.click(); } } break; case Conf['Previous page']: - if (!(g.VIEW === 'index' && g.BOARD.ID !== 'f')) { + if (!(g.VIEW === 'index' && !(typeof (base1 = g.SITE).isOnePage === "function" ? base1.isOnePage(g.BOARD) : void 0))) { return; } - if (Conf['JSON Index']) { - if ((ref5 = Conf['Index Mode']) !== 'paged' && ref5 !== 'infinite') { + if (Index.enabled) { + if ((ref6 = Conf['Index Mode']) !== 'paged' && ref6 !== 'infinite') { return; } $('.prev button', Index.pagelist).click(); } else { - if (form = $('.prev form')) { - window.location = form.action; + if ((ref7 = $(g.SITE.selectors.nav.prev)) != null) { + ref7.click(); } } break; case Conf['Search form']: - if (!(g.VIEW === 'index' && g.BOARD.ID !== 'f')) { + if (g.VIEW !== 'index') { + return; + } + searchInput = Index.enabled ? Index.searchInput : g.SITE.selectors.searchBox ? $(g.SITE.selectors.searchBox) : void 0; + if (!searchInput) { return; } - searchInput = Conf['JSON Index'] ? Index.searchInput : $.id('search-box'); Header.scrollToIfNeeded(searchInput); searchInput.focus(); break; case Conf['Paged mode']: - if (!(Conf['JSON Index'] && g.BOARD.ID !== 'f')) { + if (!Index.enabledOn(g.BOARD)) { return; } - window.location = g.VIEW === 'index' ? '#paged' : "/" + g.BOARD + "/#paged"; + location.href = g.VIEW === 'index' ? '#paged' : "/" + g.BOARD + "/#paged"; break; case Conf['Infinite scrolling mode']: - if (!(Conf['JSON Index'] && g.BOARD.ID !== 'f')) { + if (!Index.enabledOn(g.BOARD)) { return; } - window.location = g.VIEW === 'index' ? '#infinite' : "/" + g.BOARD + "/#infinite"; + location.href = g.VIEW === 'index' ? '#infinite' : "/" + g.BOARD + "/#infinite"; break; case Conf['All pages mode']: - if (!(Conf['JSON Index'] && g.BOARD.ID !== 'f')) { + if (!Index.enabledOn(g.BOARD)) { return; } - window.location = g.VIEW === 'index' ? '#all-pages' : "/" + g.BOARD + "/#all-pages"; + location.href = g.VIEW === 'index' ? '#all-pages' : "/" + g.BOARD + "/#all-pages"; break; case Conf['Open catalog']: - if (g.BOARD.ID === 'f') { + if (!(catalog = CatalogLinks.catalog())) { return; } - window.location = CatalogLinks.catalog(); + location.href = catalog; break; case Conf['Cycle sort type']: - if (!(Conf['JSON Index'] && g.VIEW === 'index' && g.BOARD.ID !== 'f')) { + if (!Index.enabled) { return; } Index.cycleSortType(); @@ -15487,6 +19412,7 @@ Keybinds = (function() { return; } ExpandThread.toggle(thread); + Header.scrollTo(threadRoot); break; case Conf['Open thread']: if (!(g.VIEW === 'index' && threadRoot)) { @@ -15525,6 +19451,14 @@ Keybinds = (function() { Header.scrollTo(threadRoot); ThreadHiding.toggle(thread); break; + case Conf['Quick Filter MD5']: + if (!threadRoot) { + return; + } + post = Keybinds.post(threadRoot); + Keybinds.hl(+1, threadRoot); + Filter.quickFilterMD5.call(post, e); + break; case Conf['Previous Post Quoting You']: if (!(threadRoot && QuoteYou.db)) { return; @@ -15598,31 +19532,40 @@ Keybinds = (function() { } return key; }, + post: function(thread) { + var s; + s = g.SITE.selectors; + return $("" + s.postContainer + s.highlightable.reply + "." + g.SITE.classes.highlight, thread) || $("" + (g.SITE.isOPContainerThread ? s.thread : s.postContainer) + s.highlightable.op, thread); + }, qr: function(thread) { QR.open(); if (thread != null) { - QR.quote.call($('input', $('.post.highlight', thread) || thread)); + QR.quote.call(Keybinds.post(thread)); } return QR.nodes.com.focus(); }, tags: function(tag, ta) { - var range, selEnd, selStart, supported, value; - supported = (function() { - switch (tag) { - case 'spoiler': - return !!$('.postForm input[name=spoiler]'); - case 'code': - return g.BOARD.ID === 'g'; - case 'math': - case 'eqn': - return g.BOARD.ID === 'sci'; - case 'sjis': - return g.BOARD.ID === 'jp'; + var range, selEnd, selStart, value; + BoardConfig.ready(function() { + var config, supported; + config = g.BOARD.config; + supported = (function() { + switch (tag) { + case 'spoiler': + return !!config.spoilers; + case 'code': + return !!config.code_tags; + case 'math': + case 'eqn': + return !!config.math_tags; + case 'sjis': + return !!config.sjis_tags; + } + })(); + if (!supported) { + return new Notice('warning', "[" + tag + "] tags are not supported on /" + g.BOARD + "/.", 20); } - })(); - if (!supported) { - new Notice('warning', "[" + tag + "] tags are not supported on /" + g.BOARD + "/.", 20); - } + }); value = ta.value; selStart = ta.selectionStart; selEnd = ta.selectionEnd; @@ -15636,21 +19579,12 @@ Keybinds = (function() { isSage = /sage/i.test(QR.nodes.email.value); return QR.nodes.email.value = isSage ? "" : "sage"; }, - img: function(thread, all) { - var post; - if (all) { - return ImageExpand.cb.toggleAll(); - } else { - post = Get.postFromNode($('.post.highlight', thread) || $('.op', thread)); - return ImageExpand.toggle(post); - } - }, open: function(thread, tab) { var url; if (g.VIEW !== 'index') { return; } - url = "/" + thread.board + "/thread/" + thread; + url = Get.url('thread', thread); if (tab) { return $.open(url); } else { @@ -15658,43 +19592,45 @@ Keybinds = (function() { } }, hl: function(delta, thread) { - var axis, height, i, len, next, postEl, replies, reply, root; - postEl = $('.reply.highlight', thread); + var axis, height, highlight, i, len, next, postEl, replies, reply, replySelector, root; + replySelector = "" + g.SITE.selectors.postContainer + g.SITE.selectors.highlightable.reply; + highlight = g.SITE.classes.highlight; + postEl = $(replySelector + "." + highlight, thread); if (!delta) { if (postEl) { - $.rmClass(postEl, 'highlight'); + $.rmClass(postEl, highlight); } return; } if (postEl) { height = postEl.getBoundingClientRect().height; if (Header.getTopOf(postEl) >= -height && Header.getBottomOf(postEl) >= -height) { - root = postEl.parentNode; + root = Get.postFromNode(postEl).nodes.root; axis = delta === +1 ? 'following' : 'preceding'; - if (!(next = $.x(axis + "-sibling::div[contains(@class,'replyContainer') and not(@hidden) and not(child::div[@class='stub'])][1]/child::div[contains(@class,'reply')]", root))) { + if (!(next = $.x(axis + "-sibling::" + g.SITE.xpath.replyContainer + "[not(@hidden) and not(child::div[@class='stub'])][1]", root))) { return; } + if (!next.matches(replySelector)) { + next = $(replySelector, next); + } Header.scrollToIfNeeded(next, delta === +1); - this.focus(next); - $.rmClass(postEl, 'highlight'); + $.addClass(next, highlight); + $.rmClass(postEl, highlight); return; } - $.rmClass(postEl, 'highlight'); + $.rmClass(postEl, highlight); } - replies = $$('.reply', thread); + replies = $$(replySelector, thread); if (delta === -1) { replies.reverse(); } for (i = 0, len = replies.length; i < len; i++) { reply = replies[i]; if (delta === +1 && Header.getTopOf(reply) > 0 || delta === -1 && Header.getBottomOf(reply) > 0) { - this.focus(reply); + $.addClass(reply, highlight); return; } } - }, - focus: function(post) { - return $.addClass(post, 'highlight'); } }; @@ -15702,6 +19638,64 @@ Keybinds = (function() { }).call(this); +ModContact = (function() { + var ModContact; + + ModContact = { + init: function() { + var ref; + if (!(g.SITE.software === 'yotsuba' && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { + return; + } + return Callbacks.Post.push({ + name: 'Mod Contact Links', + cb: this.node + }); + }, + node: function() { + var links, moveNote, moved; + if (this.isClone || !$.hasOwn(ModContact.specific, this.info.capcode)) { + return; + } + links = $.el('span', { + className: 'contact-links brackets-wrap' + }); + $.extend(links, ModContact.template(this.info.capcode)); + $.after(this.nodes.capcode, links); + if ((moved = this.info.comment.match(/This thread was moved to >>>\/(\w+)\//)) && $.hasOwn(ModContact.moveNote, moved[1])) { + moveNote = $.el('div', { + className: 'move-note' + }); + $.extend(moveNote, ModContact.moveNote[moved[1]]); + return $.add(this.nodes.post, moveNote); + } + }, + template: function(capcode) { + return {innerHTML: "feedback" + (ModContact.specific[capcode]()).innerHTML}; + }, + specific: { + Mod: function() { + return {innerHTML: " IRC"}; + }, + Manager: function() { + return ModContact.specific.Mod(); + }, + Developer: function() { + return {innerHTML: " github"}; + }, + Admin: function() { + return {innerHTML: " twitter"}; + } + }, + moveNote: { + qa: {innerHTML: "Moving a thread to /qa/ does not imply mods will read it. If you wish to contact mods, use feedback (https://www.4chan.org/feedback) or IRC (https://www.4chan-x.net/4chan-irc.html)."} + } + }; + + return ModContact; + +}).call(this); + Nav = (function() { var Nav; @@ -15758,7 +19752,13 @@ Nav = (function() { }, getThread: function() { var i, len, ref, thread, threadRoot; - ref = $$('.thread'); + if (g.VIEW === 'thread') { + return g.threads.get(g.BOARD + "." + g.THREADID).nodes.root; + } + if ($.hasClass(doc, 'catalog-mode')) { + return; + } + ref = $$(g.SITE.selectors.thread); for (i = 0, len = ref.length; i < len; i++) { threadRoot = ref[i]; thread = Get.threadFromRoot(threadRoot); @@ -15769,7 +19769,6 @@ Nav = (function() { return threadRoot; } } - return $('.board'); }, scroll: function(delta) { var axis, extra, next, ref, thread, top; @@ -15777,8 +19776,11 @@ Nav = (function() { ref.blur(); } thread = Nav.getThread(); + if (!thread) { + return; + } axis = delta === +1 ? 'following' : 'preceding'; - if (next = $.x(axis + "-sibling::div[contains(@class,'thread') and not(@hidden)][1]", thread)) { + if (next = $.x(axis + "-sibling::" + g.SITE.xpath.thread + "[not(@hidden)][1]", thread)) { top = Header.getTopOf(thread); if (delta === +1 && top < 5 || delta === -1 && top > -5) { thread = next; @@ -15800,7 +19802,7 @@ Nav = (function() { if (extra > 0) { return d.body.style.marginBottom = extra + "px"; } else { - d.body.style.marginBottom = null; + d.body.style.marginBottom = ''; delete Nav.haveExtra; return $.off(d, 'scroll', Nav.removeExtra); } @@ -15821,13 +19823,15 @@ NormalizeURL = (function() { return; } pathname = location.pathname.split(/\/+/); - switch (g.VIEW) { - case 'thread': - pathname[2] = 'thread'; - pathname = pathname.slice(0, 4); - break; - case 'index': - pathname = pathname.slice(0, 3); + if (g.SITE.software === 'yotsuba') { + switch (g.VIEW) { + case 'thread': + pathname[2] = 'thread'; + pathname = pathname.slice(0, 4); + break; + case 'index': + pathname = pathname.slice(0, 3); + } } pathname = pathname.join('/'); if (location.pathname !== pathname) { @@ -15840,26 +19844,66 @@ NormalizeURL = (function() { }).call(this); +PSA = (function() { + var PSA, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + PSA = { + init: function() { + var announcement, el; + if (g.SITE.software === 'yotsuba' && g.BOARD.ID === 'qa') { + announcement = { + innerHTML: "Stay in touch with your /qa/ friends!" + }; + el = $.el('div', { + className: 'fcx-announcement' + }, announcement); + $.onExists(doc, '.boardBanner', function(banner) { + return $.after(banner, el); + }); + } + if ('samachan.org' in Conf['siteProperties'] && indexOf.call(Conf['PSAseen'], 'samachan') < 0) { + el = $.el('span', { + innerHTML: "Looking for a new home?
          Some former Samachan users are regrouping on SushiChan.

          (a message from 4chan X)" + }); + return Main.ready(function() { + new Notice('info', el); + Conf['PSAseen'].push('samachan'); + return $.set('PSAseen', Conf['PSAseen']); + }); + } + } + }; + + return PSA; + +}).call(this); + PSAHiding = (function() { - var PSAHiding; + var PSAHiding, + slice = [].slice; PSAHiding = { init: function() { - if (!Conf['Announcement Hiding']) { + if (!(Conf['Announcement Hiding'] && g.SITE.selectors.psa)) { return; } $.addClass(doc, 'hide-announcement'); - return $.one(d, '4chanXInitFinished', this.setup); + $.onExists(doc, g.SITE.selectors.psa, this.setup); + return $.ready(function() { + if (!$(g.SITE.selectors.psa)) { + return $.rmClass(doc, 'hide-announcement'); + } + }); }, - setup: function() { - var btn, entry, hr, psa, ref; - if (!(psa = PSAHiding.psa = $.id('globalMessage'))) { - $.rmClass(doc, 'hide-announcement'); - return; - } - if ((hr = (ref = $.id('globalToggle')) != null ? ref.previousElementSibling : void 0) && hr.nodeName === 'HR') { + setup: function(psa) { + var btn, entry, hr, ref, ref1, ref2; + PSAHiding.psa = psa; + PSAHiding.text = (ref = psa.dataset.utc) != null ? ref : psa.innerHTML; + if (g.SITE.selectors.psaTop && (hr = (ref1 = $(g.SITE.selectors.psaTop)) != null ? ref1.previousElementSibling : void 0) && hr.nodeName === 'HR') { PSAHiding.hr = hr; } + PSAHiding.content = $.el('div'); entry = { el: $.el('a', { textContent: 'Show announcement', @@ -15868,55 +19912,193 @@ PSAHiding = (function() { }), order: 50, open: function() { - return PSAHiding.hidden; + return psa.hidden; } }; Header.menu.addEntry(entry); $.on(entry.el, 'click', PSAHiding.toggle); - PSAHiding.btn = btn = $.el('span', { + PSAHiding.btn = btn = $.el('a', { title: 'Mark announcement as read and hide.', - className: 'hide-announcement' - }); - $.extend(btn, { - innerHTML: "[Dismiss]" + className: 'hide-announcement-button fa fa-minus-square', + href: 'javascript:;' }); $.on(btn, 'click', PSAHiding.toggle); - $.get('hiddenPSA', 0, function(arg) { - var hiddenPSA; - hiddenPSA = arg.hiddenPSA; - PSAHiding.sync(hiddenPSA); - $.add(psa, btn); - return $.rmClass(doc, 'hide-announcement'); - }); - return $.sync('hiddenPSA', PSAHiding.sync); + if (((ref2 = psa.firstChild) != null ? ref2.tagName : void 0) === 'HR') { + $.after(psa.firstChild, btn); + } else { + $.prepend(psa, btn); + } + PSAHiding.sync(Conf['hiddenPSAList']); + $.rmClass(doc, 'hide-announcement'); + return $.sync('hiddenPSAList', PSAHiding.sync); }, toggle: function() { - var UTC; - if ($.hasClass(this, 'hide-announcement')) { - UTC = +$.id('globalMessage').dataset.utc; - $.set('hiddenPSA', UTC); + var hide, set; + hide = $.hasClass(this, 'hide-announcement-button'); + set = function(hiddenPSAList) { + if (hide) { + return hiddenPSAList[g.SITE.ID] = PSAHiding.text; + } else { + return delete hiddenPSAList[g.SITE.ID]; + } + }; + set(Conf['hiddenPSAList']); + PSAHiding.sync(Conf['hiddenPSAList']); + return $.get('hiddenPSAList', Conf['hiddenPSAList'], function(arg) { + var hiddenPSAList; + hiddenPSAList = arg.hiddenPSAList; + set(hiddenPSAList); + return $.set('hiddenPSAList', hiddenPSAList); + }); + }, + sync: function(hiddenPSAList) { + var content, psa, ref; + psa = PSAHiding.psa, content = PSAHiding.content; + psa.hidden = hiddenPSAList[g.SITE.ID] === PSAHiding.text; + if (psa.hidden) { + $.add(content, slice.call(psa.childNodes)); } else { - $.event('CloseMenu'); - $["delete"]('hiddenPSA'); + $.add(psa, slice.call(content.childNodes)); + } + return (ref = PSAHiding.hr) != null ? ref.hidden = psa.hidden : void 0; + } + }; + + return PSAHiding; + +}).call(this); + +PassMessage = (function() { + var PassMessage; + + PassMessage = { + init: function() { + var close, msg; + if (Conf['passMessageClosed']) { + return; + } + msg = $.el('div', { + className: 'box-outer top-box' + }, {innerHTML: "

          Trouble buying a 4chan Pass? (a message from 4chan X) ×

          Check the 4chan X wiki for alternative solutions.
          "}); + msg.style.cssText = 'padding-bottom: 0;'; + close = $('a', msg); + $.on(close, 'click', function() { + $.rm(msg); + return $.set('passMessageClosed', true); + }); + return $.ready(function() { + var hd; + if ((hd = $.id('hd'))) { + return $.after(hd, msg); + } else { + return $.prepend(d.body, msg); + } + }); + } + }; + + return PassMessage; + +}).call(this); + +PostJumper = (function() { + var PostJumper; + + PostJumper = { + init: function() { + var ref; + if (!(Conf['Unique ID and Capcode Navigation'] && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { + return; } - return PSAHiding.sync(UTC); + this.buttons = this.makeButtons(); + return Callbacks.Post.push({ + name: 'Post Jumper', + cb: this.node + }); }, - sync: function(UTC) { - var psa, ref; - psa = PSAHiding.psa; - PSAHiding.hidden = PSAHiding.btn.hidden = (UTC != null) && UTC >= +psa.dataset.utc; - if (PSAHiding.hidden) { - $.rm(psa); - } else { - $.after($.id('globalToggle'), psa); + node: function() { + var buttons, i, len, ref; + if (this.isClone) { + ref = $$('.postJumper', this.nodes.info); + for (i = 0, len = ref.length; i < len; i++) { + buttons = ref[i]; + PostJumper.addListeners(buttons); + } + return; + } + if (this.nodes.uniqueIDRoot) { + PostJumper.addButtons(this, 'uniqueID'); + } + if (this.nodes.capcode) { + return PostJumper.addButtons(this, 'capcode'); + } + }, + addButtons: function(post, type) { + var buttons, value; + value = post.info[type]; + buttons = PostJumper.buttons.cloneNode(true); + $.extend(buttons.dataset, { + type: type, + value: value + }); + $.after(post.nodes[type + (type === 'capcode' ? '' : 'Root')], buttons); + return PostJumper.addListeners(buttons); + }, + addListeners: function(buttons) { + $.on(buttons.firstChild, 'click', PostJumper.buttonClick); + return $.on(buttons.lastChild, 'click', PostJumper.buttonClick); + }, + buttonClick: function() { + var dir, toJumper; + dir = $.hasClass(this, 'prev') ? -1 : 1; + if ((toJumper = PostJumper.find(this.parentNode, dir))) { + return PostJumper.scroll(this.parentNode, toJumper); + } + }, + find: function(jumper, dir) { + var axis, jumper2, ref, type, value, xpath; + ref = jumper.dataset, type = ref.type, value = ref.value; + xpath = "span[contains(@class,\"postJumper\") and @data-value=\"" + value + "\" and @data-type=\"" + type + "\"]"; + axis = dir < 0 ? 'preceding' : 'following'; + jumper2 = jumper; + while ((jumper2 = $.x(axis + "::" + xpath, jumper2))) { + if (jumper2.getBoundingClientRect().height) { + return jumper2; + } } - if ((ref = PSAHiding.hr) != null) { - ref.hidden = PSAHiding.hidden; + if ((jumper2 = $.x("(//" + xpath + ")[" + (dir < 0 ? 'last()' : '1') + "]"))) { + if (jumper2.getBoundingClientRect().height) { + return jumper2; + } + } + while ((jumper2 = $.x(axis + "::" + xpath, jumper2)) && jumper2 !== jumper) { + if (jumper2.getBoundingClientRect().height) { + return jumper2; + } } + return null; + }, + makeButtons: function() { + var charNext, charPrev, classNext, classPrev, span; + charPrev = '\u23EB'; + charNext = '\u23EC'; + classPrev = 'prev'; + classNext = 'next'; + span = $.el('span', { + className: 'postJumper' + }); + $.extend(span, {innerHTML: "" + E(charPrev) + "" + E(charNext) + ""}); + return span; + }, + scroll: function(fromJumper, toJumper) { + var destPos, prevPos; + prevPos = fromJumper.getBoundingClientRect().top; + destPos = toJumper.getBoundingClientRect().top; + return window.scrollBy(0, destPos - prevPos); } }; - return PSAHiding; + return PostJumper; }).call(this); @@ -15928,9 +20110,9 @@ RelativeDates = (function() { INTERVAL: $.MINUTE / 2, init: function() { var ref; - if (((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Relative Post Dates'] && !Conf['Relative Date Title'] || g.VIEW === 'index' && Conf['JSON Index'] && g.BOARD.ID !== 'f') { + if (((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Relative Post Dates'] && !Conf['Relative Date Title'] || Index.enabled) { this.flush(); - $.on(d, 'visibilitychange ThreadUpdate', this.flush); + $.on(d, 'visibilitychange PostsInserted', this.flush); } if (Conf['Relative Post Dates']) { return Callbacks.Post.push({ @@ -15941,6 +20123,9 @@ RelativeDates = (function() { }, node: function() { var dateEl; + if (!this.info.date) { + return; + } dateEl = this.nodes.date; if (Conf['Relative Date Title']) { $.on(dateEl, 'mouseover', (function(_this) { @@ -15956,14 +20141,22 @@ RelativeDates = (function() { dateEl.title = dateEl.textContent; return RelativeDates.update(this); }, - relative: function(diff, now, date) { + relative: function(diff, now, date, abbrev) { var days, months, number, rounded, unit, years; - unit = (number = diff / $.DAY) >= 1 ? (years = now.getYear() - date.getYear(), months = now.getMonth() - date.getMonth(), days = now.getDate() - date.getDate(), years > 1 ? (number = years - (months < 0 || months === 0 && days < 0), 'year') : years === 1 && (months > 0 || months === 0 && days >= 0) ? (number = years, 'year') : (months = months + 12 * years) > 1 ? (number = months - (days < 0), 'month') : months === 1 && days >= 0 ? (number = months, 'month') : 'day') : (number = diff / $.HOUR) >= 1 ? 'hour' : (number = diff / $.MINUTE) >= 1 ? 'minute' : (number = Math.max(0, diff) / $.SECOND, 'second'); + unit = (number = diff / $.DAY) >= 1 ? (years = now.getFullYear() - date.getFullYear(), months = now.getMonth() - date.getMonth(), days = now.getDate() - date.getDate(), years > 1 ? (number = years - (months < 0 || months === 0 && days < 0), 'year') : years === 1 && (months > 0 || months === 0 && days >= 0) ? (number = years, 'year') : (months = months + 12 * years) > 1 ? (number = months - (days < 0), 'month') : months === 1 && days >= 0 ? (number = months, 'month') : 'day') : (number = diff / $.HOUR) >= 1 ? 'hour' : (number = diff / $.MINUTE) >= 1 ? 'minute' : (number = Math.max(0, diff) / $.SECOND, 'second'); rounded = Math.round(number); - if (rounded !== 1) { - unit += 's'; + if (abbrev) { + unit = unit === 'month' ? 'mo' : unit[0]; + } else { + if (rounded !== 1) { + unit += 's'; + } + } + if (abbrev) { + return "" + rounded + unit; + } else { + return rounded + " " + unit + " ago"; } - return rounded + " " + unit + " ago"; }, stale: [], flush: function() { @@ -15989,12 +20182,18 @@ RelativeDates = (function() { return post.nodes.date.title = RelativeDates.relative(diff, now, date); }, update: function(data, now) { - var date, diff, i, isPost, len, ref, relative, singlePost; + var abbrev, date, diff, i, isPost, len, ref, relative, singlePost; isPost = data instanceof Post; - date = isPost ? data.info.date : new Date(+data.dataset.utc); + if (isPost) { + date = data.info.date; + abbrev = false; + } else { + date = new Date(+data.dataset.utc); + abbrev = !!data.dataset.abbrev; + } now || (now = new Date()); diff = now - date; - relative = RelativeDates.relative(diff, now, date); + relative = RelativeDates.relative(diff, now, date, abbrev); if (isPost) { ref = [data].concat(data.clones); for (i = 0, len = ref.length; i < len; i++) { @@ -16015,7 +20214,10 @@ RelativeDates = (function() { if (indexOf.call(RelativeDates.stale, data) >= 0) { return; } - if (data instanceof Post && !g.posts[data.fullID]) { + if (data instanceof Post && !g.posts.get(data.fullID)) { + return; + } + if (data instanceof Element && !doc.contains(data)) { return; } return RelativeDates.stale.push(data); @@ -16042,10 +20244,6 @@ RemoveSpoilers = (function() { name: 'Reveal Spoilers', cb: this.node }); - Callbacks.CatalogThread.push({ - name: 'Reveal Spoilers', - cb: this.node - }); if (g.VIEW === 'archive') { return $.ready(function() { return RemoveSpoilers.unspoiler($.id('arc-list')); @@ -16057,7 +20255,7 @@ RemoveSpoilers = (function() { }, unspoiler: function(el) { var i, len, span, spoiler, spoilers; - spoilers = $$('s', el); + spoilers = $$(g.SITE.selectors.spoiler, el); for (i = 0, len = spoilers.length; i < len; i++) { spoiler = spoilers[i]; span = $.el('span', { @@ -16088,18 +20286,18 @@ Report = (function() { }, ready: function() { $.addStyle(CSS.report); - if (!Conf['Use Recaptcha v1 in Reports'] && !Conf['Force Noscript Captcha'] && Main.jsEnabled) { - return new MutationObserver(function() { - Report.fit('iframe[src^="https://www.google.com/recaptcha/api2/frame"]'); - return Report.fit('body'); - }).observe(d.body, { - childList: true, - attributes: true, - subtree: true - }); - } else { - return Report.fit('body'); + if (Conf['Archive Report']) { + Report.archive(); } + new MutationObserver(function() { + Report.fit('iframe[src^="https://www.google.com/recaptcha/api2/frame"]'); + return Report.fit('body'); + }).observe(d.body, { + childList: true, + attributes: true, + subtree: true + }); + return Report.fit('body'); }, fit: function(selector) { var dy, el; @@ -16110,6 +20308,109 @@ Report = (function() { if (dy > 0) { return window.resizeBy(0, dy); } + }, + archive: function() { + var enabled, fieldset, form, match, message, reason, submit, types, urls; + if (!(urls = Redirect.report(g.BOARD.ID)).length) { + return; + } + form = $('form'); + types = $.id('reportTypes'); + message = $('h3'); + fieldset = $.el('fieldset', { + id: 'archive-report', + hidden: true + }, {innerHTML: ""}); + enabled = $('#archive-report-enabled', fieldset); + reason = $('#archive-report-reason', fieldset); + submit = $('#archive-report-submit', fieldset); + $.on(enabled, 'change', function() { + return reason.disabled = !this.checked; + }); + if (form && types) { + fieldset.hidden = !$('[value="31"]', types).checked; + $.on(types, 'change', function(e) { + fieldset.hidden = e.target.value !== '31'; + return Report.fit('body'); + }); + $.after(types, fieldset); + Report.fit('body'); + $.one(form, 'submit', function(e) { + if (!fieldset.hidden && enabled.checked) { + e.preventDefault(); + return Report.archiveSubmit(urls, reason.value, (function(_this) { + return function(results) { + _this.action = '#archiveresults=' + encodeURIComponent(JSON.stringify(results)); + return _this.submit(); + }; + })(this)); + } + }); + } else if (message) { + fieldset.hidden = /Report submitted!/.test(message.textContent); + $.on(enabled, 'change', function() { + return submit.hidden = !this.checked; + }); + $.after(message, fieldset); + $.on(submit, 'click', function() { + return Report.archiveSubmit(urls, reason.value, Report.archiveResults); + }); + } + if ((match = location.hash.match(/^#archiveresults=(.*)$/))) { + try { + return Report.archiveResults(JSON.parse(decodeURIComponent(match[1]))); + } catch (error) {} + } + }, + archiveSubmit: function(urls, reason, cb) { + var fn, form, i, len, name, ref, results, url; + form = $.formData({ + board: g.BOARD.ID, + num: Report.postID, + reason: reason + }); + results = []; + fn = function(name, url) { + return $.ajax(url, { + onloadend: function() { + results.push([ + name, this.response || { + error: '' + } + ]); + if (results.length === urls.length) { + return cb(results); + } + }, + form: form + }); + }; + for (i = 0, len = urls.length; i < len; i++) { + ref = urls[i], name = ref[0], url = ref[1]; + fn(name, url); + } + }, + archiveResults: function(results) { + var fieldset, i, len, line, name, ref, response; + fieldset = $.id('archive-report'); + for (i = 0, len = results.length; i < len; i++) { + ref = results[i], name = ref[0], response = ref[1]; + line = $.el('h3', { + className: 'archive-report-response' + }); + if ('success' in response) { + $.addClass(line, 'archive-report-success'); + line.textContent = name + ": " + response.success; + } else { + $.addClass(line, 'archive-report-error'); + line.textContent = name + ": " + (response.error || 'Error reporting post.'); + } + if (fieldset) { + $.before(fieldset, line); + } else { + $.add(d.body, line); + } + } } }; @@ -16158,7 +20459,7 @@ Time = (function() { Time = { init: function() { var ref; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Time Formatting'])) { + if (!(((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Time Formatting'])) { return; } return Callbacks.Post.push({ @@ -16167,14 +20468,16 @@ Time = (function() { }); }, node: function() { - if (this.isClone) { + var textContent; + if (!this.info.date || this.isClone) { return; } - return this.nodes.date.textContent = Time.format(Conf['time'], this.info.date); + textContent = this.nodes.date.textContent; + return this.nodes.date.textContent = textContent.match(/^\s*/)[0] + Time.format(Conf['time'], this.info.date) + textContent.match(/\s*$/)[0]; }, format: function(formatString, date) { return formatString.replace(/%(.)/g, function(s, c) { - if (c in Time.formatters) { + if ($.hasOwn(Time.formatters, c)) { return Time.formatters[c].call(date); } else { return s; @@ -16183,6 +20486,30 @@ Time = (function() { }, day: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], month: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + localeFormat: function(date, options, defaultValue) { + if (Conf['timeLocale']) { + try { + return Intl.DateTimeFormat(Conf['timeLocale'], options).format(date); + } catch (error) {} + } + return defaultValue; + }, + localeFormatPart: function(date, options, part, defaultValue) { + var parts; + if (Conf['timeLocale']) { + try { + parts = Intl.DateTimeFormat(Conf['timeLocale'], options).formatToParts(date); + return parts.map(function(x) { + if (x.type === part) { + return x.value; + } else { + return ''; + } + }).join(''); + } catch (error) {} + } + return defaultValue; + }, zeroPad: function(n) { if (n < 10) { return "0" + n; @@ -16192,16 +20519,24 @@ Time = (function() { }, formatters: { a: function() { - return Time.day[this.getDay()].slice(0, 3); + return Time.localeFormat(this, { + weekday: 'short' + }, Time.day[this.getDay()].slice(0, 3)); }, A: function() { - return Time.day[this.getDay()]; + return Time.localeFormat(this, { + weekday: 'long' + }, Time.day[this.getDay()]); }, b: function() { - return Time.month[this.getMonth()].slice(0, 3); + return Time.localeFormat(this, { + month: 'short' + }, Time.month[this.getMonth()].slice(0, 3)); }, B: function() { - return Time.month[this.getMonth()]; + return Time.localeFormat(this, { + month: 'long' + }, Time.month[this.getMonth()]); }, d: function() { return Time.zeroPad(this.getDate()); @@ -16228,18 +20563,13 @@ Time = (function() { return Time.zeroPad(this.getMinutes()); }, p: function() { - if (this.getHours() < 12) { - return 'AM'; - } else { - return 'PM'; - } + return Time.localeFormatPart(this, { + hour: 'numeric', + hour12: true + }, 'dayperiod', (this.getHours() < 12 ? 'AM' : 'PM')); }, P: function() { - if (this.getHours() < 12) { - return 'am'; - } else { - return 'pm'; - } + return Time.formatters.p.call(this).toLowerCase(); }, S: function() { return Time.zeroPad(this.getSeconds()); @@ -16260,6 +20590,61 @@ Time = (function() { }).call(this); +Tinyboard = (function() { + var Tinyboard; + + Tinyboard = { + init: function() { + if (g.SITE.software !== 'tinyboard') { + return; + } + if (g.VIEW === 'thread') { + return Main.ready(function() { + return $.global(function() { + var base, boardID, form, originalNoko, ref, ref1, ref2, threadID; + ref = document.currentScript.dataset, boardID = ref.boardID, threadID = ref.threadID; + threadID = +threadID; + form = document.querySelector('form[name="post"]'); + window.$(document).ajaxComplete(function(event, request, settings) { + var detail, noko, postID, redirect, ref1, ref2; + if (settings.url !== form.action) { + return; + } + if (!(postID = +((ref1 = request.responseJSON) != null ? ref1.id : void 0))) { + return; + } + detail = { + boardID: boardID, + threadID: threadID, + postID: postID + }; + try { + ref2 = request.responseJSON, redirect = ref2.redirect, noko = ref2.noko; + if (redirect && (typeof originalNoko !== "undefined" && originalNoko !== null) && !originalNoko && !noko) { + detail.redirect = redirect; + } + } catch (error) {} + event = new CustomEvent('QRPostSuccessful', { + bubbles: true, + detail: detail + }); + return document.dispatchEvent(event); + }); + originalNoko = (ref1 = window.tb_settings) != null ? (ref2 = ref1.ajax) != null ? ref2.always_noko_replies : void 0 : void 0; + return ((base = (window.tb_settings || (window.tb_settings = {}))).ajax || (base.ajax = {})).always_noko_replies = true; + }, { + boardID: g.BOARD.ID, + threadID: g.THREADID + }); + }); + } + } + }; + + return Tinyboard; + +}).call(this); + Favicon = (function() { var Favicon; @@ -16269,24 +20654,35 @@ Favicon = (function() { return d.head && (Favicon.el = $('link[rel="shortcut icon"]', d.head)); }), Favicon.initAsap); }, + set: function(status) { + Favicon.status = status; + if (Favicon.el) { + Favicon.el.href = Favicon[status]; + return $.add(d.head, Favicon.el); + } + }, initAsap: function() { var href; Favicon.el.type = 'image/x-icon'; href = Favicon.el.href; - Favicon.SFW = /ws\.ico$/.test(href); + Favicon.isSFW = /ws\.ico$/.test(href); Favicon["default"] = href; - return Favicon["switch"](); + Favicon["switch"](); + if (Favicon.status) { + return Favicon.set(Favicon.status); + } }, "switch": function() { var f, i, items, t; items = { ferongr: ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///9zBQC/AADpDAP/gID/q6voCwJJTwpOAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxUlEQVR42q1TOwrCQBB9s0FRtJI0WoqFtSLYegoP4gVSeJsUHsHSI3iFeIqRXXgwrhlXwYHHhLwPTB7B36abBCV+0pA4DUBQUNZYQptGtW3jtoKyxgoe0yrBCoyZfL/5ioQ3URZOXW9I341l3oo+NXEZiW4CEuIzvPECopED4OaZ3RNmeAm4u+a8Jr5f17VyVoL8fr8qcltzwlyyj2iqcgPOQ9ExkHAITgD75bYBe0A5S4H/P9htuWMF3QXoQpwaKeT+lnsC6JE5I6aq6fEAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///8AcH4AtswA2PJ55fKi6fIA1/FtpPADAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxElEQVQ4y2NgoBq4/vE/HJOsBiRQUIfA2AzBqQYqUfn00/9FLz+BaQxDCKqBmX7jExijKEDSDJPHrnnbGQhGV4RmOFwdVkNwhQMheYwQxhaIi7b9Z9A3gWAQm2BUoQOgRhgA8o7j1ozLC4LCyAZcx6kZI5qg4kLKqggDFFWxJySsUQVzlb4pwgAJaTRvokcVNgOqOv8zcHBCsL07DgNg8YsczzA5MxtUL+DMD8g0slxI/H8GQ/P/DJKyeKIRpglXZsIiBwBhP5O+VbI/JgAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///8oeQBJ3ABV/wHM/7Lu/+ZU/gAqUP3dAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAx0lEQVQ4y2NgoBYI+cfwH4ZJVgMS0KhEYGyG4FQDkzjzf9P/d/+fgWl0QwiqgSkI/c8IxsgKkDXD5LFq9rwDweiK0A2HqcNqCK5wICSPEcLYAtH+AMN/IXMIBrEJRie6OEgjDAC5x3FqxuUFNiEUA67j1IweTTBxBQ1puAG86jgSEraogskJWSBcwCGF5k30qMJmgMFEhv/MXBAs5oLDAFj8IsczTE7UEeECbhU8+QGZRpaTi2b4L2zF8J9TGk80wjThykzY5AAW/2O1C2mIbgAAAABJRU5ErkJggg=='], - 'xat-': ['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'], + 'xat-': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAG1BMVEX+AACLkZFub2yfaF3zZGIAAAD/AAD/iYr/zs8IPcF6AAAABXRSTlMAeprJ7xzg6IEAAABZSURBVAjXY2DABKGBSkqioQwMrGmpxsZhaQEMDGFpIa5pqSCRtPDSNJBIaGh5eShQDYOye0V7iREKAyQFYoiCFAcyILQDGcGmEEZYkGoqiMHKysAQEICwGwAAjBmBqhYlagAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAPFBMVEUAAACEgoBva2ilamDxcG7IaWYgFBNOSEf//f0PDQwBAAA7LCwAAAD/AAD+hIX+m5z+zc5HAADPAAAGAADl032uAAAADHRSTlMAzNv0/vz+6v3+7ALrmfyXAAAAaUlEQVQY042PyxKAIAhFAc1eV7T6/3/N8VXOtAgWwBm4ANEPA8AswpySXHvvYZLlpBNrh9pDtcSqAQ1BUTVIjNUQY5icmwfglmXNgE0d6QBF9GigrU0A9LoM53U1kFzk6SBQuWfD/vHqDUCpBmVKTTM4AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAIVBMVEUAAACRjop4dXVpZ2tdcI9dfKdisfMAAAAumMN9xv+s2/+PADT2AAAAB3RSTlMAepGdv83v3HIc4QAAAFxJREFUCNdjYMAE5YXKRuLlDAzsHe2uIRUdBQwMFR1l6R3tIJGOyukdIJHy8lkry4FqGEwzV62aFozMUAFJOQEZ4iDFhQwI7UBGaTiEUVFs3g5isLMzMBQUIOwGAJRlIu9hk08QAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEUAAACAgYVlc4ljsu4AAAAAAAAAAAAumMODyP6b1P6e1f/g8v89msgSIiwNFxwbPU3tQYj5AAAABnRSTlMAxej+9VTmD9ciAAAAZElEQVQI12NgwARpiUKKYmkMDGzlZUpK6eUJDAzp5clm5WUgkfKMtnKQSFpa54o0oBoGJYvZO88+gjJu7wMyhIBS2SCGGFDxaxADpP32NjAjSe0bSFd6epIaWISNjYEhJRVhNwAGlyJpYtcvcAAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAHlBMVEUfJSCRi5Frbm9dn19082KR/30AAABmzDOq/5vZ/9Gt/vt2AAAABnRSTlMAe5rJ7/4vxEp4AAAAWUlEQVQI12NgwARpiUpKYmkMDGzlZcbG6eUJDAzp5Slu5WUgkfLUsHKQSFpaRGsaUA2DsmvnjBAjFAZICsQQAylOZEBoBzKSzSCM9CS1MhCDjY2BISEBYTcAtgAcKSK2vuIAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAM1BMVEUAAACBj39tfm1qj2RepFlu2VQAAQAAAAAAAABmyzOX/oSr/pus/pzk/98PGgtatC4CBAI1ENblAAAACHRSTlMA09/p9v77ig0SBcQAAABnSURBVBjTjY9LDsAgCEQRsR2xWu9/2hK/adJFYQG8wABEPwyAYzNnSatjjPAiviWLhPCqI1R7HBrQdCmGBrEETTmnUAq/QMm5dODHyAQOXXR1zLUGsIEI7lonMGfeHQTq9xw4P159AIxSBSC53km7AAAAAElFTkSuQmCC'], Mayhem: ['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'], - '4chanJS': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAD/AABnZ2f///8nFk05AAAAAXRSTlMAQObYZgAAAEFJREFUeNqNjgEKACAMAjvX/98cAkkxgmSgO8Bt/Ai4ApJ6KKhzF3OiEMDASrGB/QWgPEHsUpN+Ng9xAETMYhDrWmeHAMcmvycWAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAD/AAD///9nZ2f77Y6hAAAAAXRSTlMAQObYZgAAAEBJREFUeF6NjQEKACAMAnfW/98cAxFiBIngOsTqR8B1IGkeG9p5i7XabgAGZNigXgA8aoCUxvzWAIcBItGiSEwdccYA3BuRAWkAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAAul8NnZ2f////82iC9AAAAAXRSTlMAQObYZgAAAEFJREFUeNqNjgEKACAMAjvX/98cAkkxgmSgO8Bt/Ai4ApJ6KKhzF3OiEMDASrGB/QWgPEHsUpN+Ng9xAETMYhDrWmeHAMcmvycWAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAAul8P///9nZ2cgIeMlAAAAAXRSTlMAQObYZgAAAEBJREFUeF6NjQEKACAMAnfW/98cAxFiBIngOsTqR8B1IGkeG9p5i7XabgAGZNigXgA8aoCUxvzWAIcBItGiSEwdccYA3BuRAWkAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAElBMVEUBAAAAAABmzDNlyjJnZ2f///+6o7dfAAAAAXRSTlMAQObYZgAAAERJREFUeF6NjkEKADEIA51o///lJZfQxUsHITogWi8AvwZJuxmYa25xDooBLEwOWFTYAsYVhdorLZt9Ng9xCUTCUCQ2H3F4ANrZ2WNiAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAElBMVEUBAAAAAABmzDP///9lyjJnZ2cIHys9AAAAAXRSTlMAQObYZgAAAENJREFUeF6NjUEKwEAMAjNm9/9fLkEslFwqgjoEUn8EfAqSdrkwzj6ieyyTkQEVGWRvANfO1iEX620AjgBEwqR4Y+sBeGAA6d+vQ4IAAAAASUVORK5CYII='], + '4chanJS': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAD/AABnZ2f///8nFk05AAAAAXRSTlMAQObYZgAAAEFJREFUeNqNjgEKACAMAjvX/98cAkkxgmSgO8Bt/Ai4ApJ6KKhzF3OiEMDASrGB/QWgPEHsUpN+Ng9xAETMYhDrWmeHAMcmvycWAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAD1BMVEUAAAAAAAD/AABmZmYA/wBD99DBAAAAAXRSTlMAQObYZgAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAE9JREFUCNdljcsRACEIQ5MOiNKAdGAJ9N/Uiu7nsMzABHgB4B8ygFoZA2hhVWavhhGeURPJU9q45+17hGbfGxa82Ndex3hEM44SJGD2/b4AzDgGlHbl388AAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAAul8NnZ2f////82iC9AAAAAXRSTlMAQObYZgAAAEFJREFUeNqNjgEKACAMAjvX/98cAkkxgmSgO8Bt/Ai4ApJ6KKhzF3OiEMDASrGB/QWgPEHsUpN+Ng9xAETMYhDrWmeHAMcmvycWAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAD1BMVEUAAAAAAAAul8NnZ2f/AAD7B+mqAAAAAXRSTlMAQObYZgAAAAlwSFlzAAALEgAACxIB0t1+/AAAAE9JREFUCNdljcsRACEIQ5MOiNKAdGAJ9N/Uiu7nsMzABHgB4B8ygFoZA2hhVWavhhGeURPJU9q45+17hGbfGxa82Ndex3hEM44SJGD2/b4AzDgGlHbl388AAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAElBMVEUBAAAAAABmzDNlyjJnZ2f///+6o7dfAAAAAXRSTlMAQObYZgAAAERJREFUeF6NjkEKADEIA51o///lJZfQxUsHITogWi8AvwZJuxmYa25xDooBLEwOWFTYAsYVhdorLZt9Ng9xCUTCUCQ2H3F4ANrZ2WNiAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAD1BMVEUAAAAAAABmzDNmZmb/AAC8/wCMAAAAAXRSTlMAQObYZgAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAE9JREFUCNdljcsRACEIQ5MOiNKAdGAJ9N/Uiu7nsMzABHgB4B8ygFoZA2hhVWavhhGeURPJU9q45+17hGbfGxa82Ndex3hEM44SJGD2/b4AzDgGlHbl388AAAAASUVORK5CYII='], Original: ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEX/////AAD///8AAABBZmS3AAAAAXRSTlMAQObYZgAAAExJREFUeF4tyrENgDAMAMFXKuQswQLBG3mOlBnFS1gwDfIYLpEivvjq2MlqjmYvYg5jWEzCwtDSQlwcXKCVLrpFbvLvvSf9uZJ2HusDtJAY7Tkn1oYAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAhElEQVR42q1RwQnAMAjMu5M4guAKXa4j5dUROo5tipSDcrFChUONd0di2m/hEGVOHDyIPufgwAFASDkpoSzmBrkJ2UMyR9LsJ3rvrqo3Rt1YMIMhhNnOxLMnoMFBxHyJAr2IOBFzA8U+6pLBdmEJTA0aMVjpDd6Loks0s5HZNwYx8tfZCZ0kll7ORffZAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEX///8ul8P///8AAACaqgkzAAAAAXRSTlMAQObYZgAAAExJREFUeF4tyrENgDAMAMFXKuQswQLBG3mOlBnFS1gwDfIYLpEivvjq2MlqjmYvYg5jWEzCwtDSQlwcXKCVLrpFbvLvvSf9uZJ2HusDtJAY7Tkn1oYAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAALVBMVEUAAAAAAAAAAAAAAAABBQcHFx4KISoNLToaVW4oKCgul8M4ODg7OzvBwcH///8uS/CdAAAAA3RSTlMAx9dmesIgAAAAV0lEQVR42m2NWw6AIBAD1eILZO5/XI0UAgm7H9tOsu0yGWAQSOoFijHOxOANGqm/LczpOaXs4gISrPZ+gc2+hO5w2xdwgOjBFUIF+sEJrhUl9JFr+badFwR+BfqlmGUJAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEX///9mzDP///8AAACT0n1lAAAAAXRSTlMAQObYZgAAAExJREFUeF4tyrENgDAMAMFXKuQswQLBG3mOlBnFS1gwDfIYLpEivvjq2MlqjmYvYg5jWEzCwtDSQlwcXKCVLrpFbvLvvSf9uZJ2HusDtJAY7Tkn1oYAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAALVBMVEUAAAAAAAAAAAAAAAAECAIQIAgWLAsePA8oKCg4ODg6dB07OztmzDPBwcH///+rsf3XAAAAA3RSTlMAx9dmesIgAAAAV0lEQVR42m2NWw6AIBAD1eIDhbn/cTVSCCTsfmw7ybbLZIBBIKkXKKU0E4M3aKT+tjCn5xiziwuIsNr7BTb7ErrDZV/AAaIHdwgV6AcnuFaU0Eeu5dt2XiUyBjCQ2bIrAAAAAElFTkSuQmCC'], - 'Metro': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAC/AABrZQDiAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAAHAAAdAAApAAAsAAA4AABsAACQAAC/AAD///9SVhtjAAAAA3RSTlMAPse+s4iwAAAAM0lEQVQIW2NggAGuVasWgDBpDDAQUoSaob0Jao73lgVojOitUEazBZRRvR3KmJa5AO4KAGBtLuMAuhIIAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAAA1/GhpCidAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAAACAkAISUALzQAMTcAQEcAeokAorYA1/H///8BrzTFAAAAA3RSTlMAPse+s4iwAAAAM0lEQVQIW2NggAGuVasWgDBpDDAQUoSaob0Jao73lgVojOitUEazBZRRvR3KmJa5AO4KAGBtLuMAuhIIAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAABV/wErM5hwAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAADCgANKAASOAATOwAZTAAwkQBAwQBV/wH////+Fmy4AAAAA3RSTlMAPse+s4iwAAAAM0lEQVQIW2NggAGuVasWgDBpDDAQUoSaob0Jao73lgVojOitUEazBZRRvR3KmJa5AO4KAGBtLuMAuhIIAAAAAElFTkSuQmCC'] - }[Conf['favicon']]; + 'Metro': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAC/AABrZQDiAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAC/AAD///8dAAApAABsAAAHAAA4AACQAAAsAABMCpCvAAAAA3RSTlMAPse+s4iwAAAAMklEQVQI12NggAFmY2MDECaNAQZCilAzVJyg5oS4GqAxUtygjIp2KGOKJ5SxepcB3BUAcdYRqxAtgFoAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAAA1/GhpCidAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAAA1/H///8AISUALzQAeokACAkAQEcAorYAMTcE9WFNAAAAA3RSTlMAPse+s4iwAAAAMklEQVQI12NggAFmY2MDECaNAQZCilAzVJyg5oS4GqAxUtygjIp2KGOKJ5SxepcB3BUAcdYRqxAtgFoAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAABV/wErM5hwAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAABV/wH///8NKAASOAAwkQADCgAZTABAwQATOwC5e3VGAAAAA3RSTlMAPse+s4iwAAAAMklEQVQI12NggAFmY2MDECaNAQZCilAzVJyg5oS4GqAxUtygjIp2KGOKJ5SxepcB3BUAcdYRqxAtgFoAAAAASUVORK5CYII='] + }; + items = $.getOwn(items, Conf['favicon']); f = Favicon; t = 'data:image/png;base64,'; i = 0; @@ -16297,7 +20693,7 @@ Favicon = (function() { return f.update(); }, update: function() { - if (this.SFW) { + if (this.isSFW) { this.unread = this.unreadSFW; return this.unreadY = this.unreadSFWY; } else { @@ -16305,6 +20701,8 @@ Favicon = (function() { return this.unreadY = this.unreadNSFWY; } }, + SFW: '//s.4cdn.org/image/favicon-ws.ico', + NSFW: '//s.4cdn.org/image/favicon.ico', dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==', logo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAgMAAAC+UIlYAAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAGlJREFUWMPtlkEKADEIA/tJP9lXLttQto2yHxgDHozTi0ToGK2WKZZ+HAQQMZc+xBwI4EZ+wAC2IfPuSIDOZJrSZQEAX9eVJhhwIuUYAnQe8rhAEMAZlTI2MID9f5Clyh0JeE1V1ZEAvB4qDfwuJTSGRAAAAABJRU5ErkJggg==' }; @@ -16318,7 +20716,7 @@ MarkNewIPs = (function() { MarkNewIPs = { init: function() { - if (g.VIEW !== 'thread' || !Conf['Mark New IPs']) { + if (!(g.SITE.software === 'yotsuba' && g.VIEW === 'thread' && Conf['Mark New IPs'])) { return; } return Callbacks.Thread.push({ @@ -16342,13 +20740,13 @@ MarkNewIPs = (function() { i = MarkNewIPs.ipCount; for (j = 0, len = newPosts.length; j < len; j++) { fullID = newPosts[j]; - MarkNewIPs.markNew(g.posts[fullID], ++i); + MarkNewIPs.markNew(g.posts.get(fullID), ++i); } break; case -deletedPosts.length: for (k = 0, len1 = newPosts.length; k < len1; k++) { fullID = newPosts[k]; - MarkNewIPs.markOld(g.posts[fullID]); + MarkNewIPs.markOld(g.posts.get(fullID)); } } MarkNewIPs.ipCount = ipCount; @@ -16384,7 +20782,6 @@ ReplyPruning = (function() { if (!(g.VIEW === 'thread' && Conf['Reply Pruning'])) { return; } - this.active = !(Conf['Quote Threading'] && Conf['Thread Quotes']); this.container = $.frag(); this.summary = $.el('span', { hidden: true, @@ -16397,17 +20794,16 @@ ReplyPruning = (function() { return $.event('change', null, _this.inputs.enabled); }; })(this)); - label = UI.checkbox('Prune Replies', 'Show Last', this.active); + label = UI.checkbox('Prune Replies', 'Show Last', Conf['Prune All Threads']); el = $.el('span', { title: 'Maximum number of replies to show.' - }, { - innerHTML: " " - }); + }, {innerHTML: " "}); $.prepend(el, label); this.inputs = { enabled: label.firstElementChild, replies: el.lastElementChild }; + this.setEnabled.call(this.inputs.enabled); $.on(this.inputs.enabled, 'change', this.setEnabled); $.on(this.inputs.replies, 'change', $.cb.value); Header.menu.addEntry({ @@ -16442,6 +20838,12 @@ ReplyPruning = (function() { node: function() { var ref; ReplyPruning.thread = this; + if (this.isSticky) { + ReplyPruning.active = ReplyPruning.inputs.enabled.checked = true; + if (QuoteThreading.input) { + Conf['Thread Quotes'] = QuoteThreading.input.checked = false; + } + } this.posts.forEach(function(post) { if (post.isReply) { ReplyPruning.total++; @@ -16450,7 +20852,7 @@ ReplyPruning = (function() { } } }); - if (ReplyPruning.active && /^#p\d+$/.test(location.hash) && (0 <= (ref = this.posts.keys.indexOf(location.hash.slice(2))) && ref < 1 + Math.max(ReplyPruning.total - +Conf["Max Replies"], 0))) { + if (ReplyPruning.active && /^#p\d+$/.test(location.hash) && (1 <= (ref = this.posts.keys.indexOf(location.hash.slice(2))) && ref < 1 + Math.max(ReplyPruning.total - +Conf["Max Replies"], 0))) { ReplyPruning.active = ReplyPruning.inputs.enabled.checked = false; } $.after(this.OP.nodes.root, ReplyPruning.summary); @@ -16469,21 +20871,24 @@ ReplyPruning = (function() { for (i = 0, len = ref.length; i < len; i++) { fullID = ref[i]; ReplyPruning.total++; - if (g.posts[fullID].file) { + if (g.posts.get(fullID).file) { ReplyPruning.totalFiles++; } } }, update: function() { - var boardTop, frag, hidden1, hidden2, oldPos, post, posts; + var boardTop, frag, hidden1, hidden2, node, oldPos, post, posts; hidden1 = ReplyPruning.hidden; hidden2 = ReplyPruning.active ? Math.max(ReplyPruning.total - +Conf["Max Replies"], 0) : 0; oldPos = d.body.clientHeight - window.scrollY; posts = ReplyPruning.thread.posts; if (ReplyPruning.hidden < hidden2) { while (ReplyPruning.hidden < hidden2 && ReplyPruning.position < posts.keys.length) { - post = posts[posts.keys[ReplyPruning.position++]]; + post = posts.get(posts.keys[ReplyPruning.position++]); if (post.isReply && !post.isFetchedQuote) { + while ((node = ReplyPruning.summary.nextSibling) && node !== post.nodes.root) { + $.add(ReplyPruning.container, node); + } $.add(ReplyPruning.container, post.nodes.root); ReplyPruning.hidden++; if (post.file) { @@ -16494,8 +20899,11 @@ ReplyPruning = (function() { } else if (ReplyPruning.hidden > hidden2) { frag = $.frag(); while (ReplyPruning.hidden > hidden2 && ReplyPruning.position > 0) { - post = posts[posts.keys[--ReplyPruning.position]]; + post = posts.get(posts.keys[--ReplyPruning.position]); if (post.isReply && !post.isFetchedQuote) { + while ((node = ReplyPruning.container.lastChild) && node !== post.nodes.root) { + $.prepend(frag, node); + } $.prepend(frag, post.nodes.root); ReplyPruning.hidden--; if (post.file) { @@ -16504,9 +20912,9 @@ ReplyPruning = (function() { } } $.after(ReplyPruning.summary, frag); - $.event('PostsInserted'); + $.event('PostsInserted', null, ReplyPruning.summary.parentNode); } - ReplyPruning.summary.textContent = ReplyPruning.active ? Build.summaryText('+', ReplyPruning.hidden, ReplyPruning.hiddenFiles) : Build.summaryText('-', ReplyPruning.total, ReplyPruning.totalFiles); + ReplyPruning.summary.textContent = ReplyPruning.active ? g.SITE.Build.summaryText('+', ReplyPruning.hidden, ReplyPruning.hiddenFiles) : g.SITE.Build.summaryText('-', ReplyPruning.total, ReplyPruning.totalFiles); ReplyPruning.summary.hidden = ReplyPruning.total <= +Conf["Max Replies"]; if (hidden1 !== hidden2 && (boardTop = Header.getTopOf($('.board'))) < 0) { return window.scrollBy(0, Math.max(d.body.clientHeight - oldPos, window.scrollY + boardTop) - window.scrollY); @@ -16522,20 +20930,24 @@ ThreadStats = (function() { var ThreadStats; ThreadStats = { + postCount: 0, + fileCount: 0, + postIndex: 0, init: function() { - var sc, statsHTML, statsTitle; + var base, sc, statsHTML, statsTitle; if (g.VIEW !== 'thread' || !Conf['Thread Stats']) { return; } - statsHTML = { - innerHTML: "? / ?" + ((Conf["IP Count in Stats"]) ? " / ?" : "") + ((Conf["Page Count in Stats"]) ? " / ?" : "") - }; + if (Conf['Page Count in Stats']) { + this[(typeof (base = g.SITE).isPrunedByAge === "function" ? base.isPrunedByAge(g.BOARD) : void 0) ? 'showPurgePos' : 'showPage'] = true; + } + statsHTML = {innerHTML: "? / ?" + ((Conf["IP Count in Stats"] && g.SITE.hasIPCount) ? " / ?" : "") + ((Conf["Page Count in Stats"]) ? " / ?" : "")}; statsTitle = 'Posts / Files'; - if (Conf['IP Count in Stats']) { + if (Conf['IP Count in Stats'] && g.SITE.hasIPCount) { statsTitle += ' / IPs'; } if (Conf['Page Count in Stats']) { - statsTitle += (g.BOARD.ID === 'f' ? ' / Purge Position' : ' / Page'); + statsTitle += (this.showPurgePos ? ' / Purge Position' : ' / Page'); } if (Conf['Updater and Stats in Header']) { this.dialog = sc = $.el('span', { @@ -16545,9 +20957,7 @@ ThreadStats = (function() { $.extend(sc, statsHTML); Header.addShortcut('stats', sc, 200); } else { - this.dialog = sc = UI.dialog('thread-stats', 'bottom: 0px; right: 0px;', { - innerHTML: "
          " + (statsHTML).innerHTML + "
          " - }); + this.dialog = sc = UI.dialog('thread-stats', {innerHTML: "
          " + (statsHTML).innerHTML + "
          "}); $.addClass(doc, 'float'); $.ready(function() { return $.add(d.body, sc); @@ -16566,50 +20976,64 @@ ThreadStats = (function() { }); }, node: function() { - var fileCount, postCount; - postCount = 0; - fileCount = 0; - this.posts.forEach(function(post) { - postCount++; - if (post.file) { - fileCount++; - } - if (ThreadStats.pageCountEl) { - return ThreadStats.lastPost = post.info.date; - } - }); ThreadStats.thread = this; + ThreadStats.count(); + ThreadStats.update(); ThreadStats.fetchPage(); - ThreadStats.update(postCount, fileCount, this.ipCount); + $.on(d, 'PostsInserted', function() { + return $.queueTask(ThreadStats.onPostsInserted); + }); return $.on(d, 'ThreadUpdate', ThreadStats.onUpdate); }, + count: function() { + var i, j, n, post, posts, ref, ref1; + posts = ThreadStats.thread.posts; + n = posts.keys.length; + for (i = j = ref = ThreadStats.postIndex, ref1 = n; j < ref1; i = j += 1) { + post = posts.get(posts.keys[i]); + if (!post.isFetchedQuote) { + ThreadStats.postCount++; + ThreadStats.fileCount += post.files.length; + } + } + return ThreadStats.postIndex = n; + }, onUpdate: function(e) { - var fileCount, ipCount, newPosts, postCount, ref, ref1; + var fileCount, postCount, ref; if (e.detail[404]) { return; } - ref = e.detail, postCount = ref.postCount, fileCount = ref.fileCount, ipCount = ref.ipCount, newPosts = ref.newPosts; - ThreadStats.update(postCount, fileCount, ipCount); - if (!ThreadStats.pageCountEl) { - return; + ref = e.detail, postCount = ref.postCount, fileCount = ref.fileCount; + $.extend(ThreadStats, { + postCount: postCount, + fileCount: fileCount + }); + ThreadStats.postIndex = ThreadStats.thread.posts.keys.length; + ThreadStats.update(); + if (ThreadStats.showPage && ThreadStats.pageCountEl.textContent !== '1') { + return ThreadStats.fetchPage(); } - if (newPosts.length) { - ThreadStats.lastPost = g.posts[newPosts[newPosts.length - 1]].info.date; + }, + onPostsInserted: function() { + if (!(ThreadStats.thread.posts.keys.length > ThreadStats.postIndex)) { + return; } - if (g.BOARD.ID !== 'f' && ((ref1 = ThreadStats.pageCountEl) != null ? ref1.textContent : void 0) !== '1') { + ThreadStats.count(); + ThreadStats.update(); + if (ThreadStats.showPage && ThreadStats.pageCountEl.textContent !== '1') { return ThreadStats.fetchPage(); } }, - update: function(postCount, fileCount, ipCount) { - var fileCountEl, ipCountEl, postCountEl, thread; + update: function() { + var fileCountEl, ipCountEl, postCountEl, ref, thread; thread = ThreadStats.thread, postCountEl = ThreadStats.postCountEl, fileCountEl = ThreadStats.fileCountEl, ipCountEl = ThreadStats.ipCountEl; - postCountEl.textContent = postCount; - fileCountEl.textContent = fileCount; - if ((ipCount != null) && ipCountEl) { - ipCountEl.textContent = ipCount; + postCountEl.textContent = ThreadStats.postCount; + fileCountEl.textContent = ThreadStats.fileCount; + if (ipCountEl != null) { + ipCountEl.textContent = (ref = thread.ipCount) != null ? ref : '?'; } - (thread.postLimit && !thread.isSticky ? $.addClass : $.rmClass)(postCountEl, 'warning'); - return (thread.fileLimit && !thread.isSticky ? $.addClass : $.rmClass)(fileCountEl, 'warning'); + postCountEl.classList.toggle('warning', thread.postLimit && !thread.isSticky); + return fileCountEl.classList.toggle('warning', thread.fileLimit && !thread.isSticky); }, fetchPage: function() { if (!ThreadStats.pageCountEl) { @@ -16622,40 +21046,47 @@ ThreadStats = (function() { return; } ThreadStats.timeout = setTimeout(ThreadStats.fetchPage, 2 * $.MINUTE); - return $.ajax("//a.4cdn.org/" + ThreadStats.thread.board + "/threads.json", { - onload: ThreadStats.onThreadsLoad - }, { - whenModified: 'ThreadStats' - }); + return $.whenModified(g.SITE.urls.threadsListJSON(ThreadStats.thread), 'ThreadStats', ThreadStats.onThreadsLoad); }, onThreadsLoad: function() { - var i, j, k, len, len1, len2, page, purgePos, ref, ref1, ref2, thread; + var i, j, k, l, len, len1, len2, len3, len4, m, nThreads, o, page, pageNum, purgePos, ref, ref1, ref2, ref3, ref4, thread; if (this.status === 200) { - ref = this.response; - for (i = 0, len = ref.length; i < len; i++) { - page = ref[i]; - if (g.BOARD.ID === 'f') { - purgePos = 1; + if (ThreadStats.showPurgePos) { + purgePos = 1; + ref = this.response; + for (j = 0, len = ref.length; j < len; j++) { + page = ref[j]; ref1 = page.threads; - for (j = 0, len1 = ref1.length; j < len1; j++) { - thread = ref1[j]; + for (k = 0, len1 = ref1.length; k < len1; k++) { + thread = ref1[k]; if (thread.no < ThreadStats.thread.ID) { purgePos++; } } - ThreadStats.pageCountEl.textContent = purgePos; - } else { - ref2 = page.threads; - for (k = 0, len2 = ref2.length; k < len2; k++) { - thread = ref2[k]; - if (!(thread.no === ThreadStats.thread.ID)) { - continue; + } + ThreadStats.pageCountEl.textContent = purgePos; + return ThreadStats.pageCountEl.classList.toggle('warning', purgePos === 1); + } else { + i = nThreads = 0; + ref2 = this.response; + for (l = 0, len2 = ref2.length; l < len2; l++) { + page = ref2[l]; + nThreads += page.threads.length; + } + ref3 = this.response; + for (pageNum = m = 0, len3 = ref3.length; m < len3; pageNum = ++m) { + page = ref3[pageNum]; + ref4 = page.threads; + for (o = 0, len4 = ref4.length; o < len4; o++) { + thread = ref4[o]; + if (thread.no === ThreadStats.thread.ID) { + ThreadStats.pageCountEl.textContent = pageNum + 1; + ThreadStats.pageCountEl.classList.toggle('warning', i >= nThreads - this.response[0].threads.length); + ThreadStats.lastPageUpdate = new Date(thread.last_modified * $.SECOND); + ThreadStats.retry(); + return; } - ThreadStats.pageCountEl.textContent = page.page; - (page.page === this.response.length ? $.addClass : $.rmClass)(ThreadStats.pageCountEl, 'warning'); - ThreadStats.lastPageUpdate = new Date(thread.last_modified * $.SECOND); - ThreadStats.retry(); - return; + i++; } } } @@ -16664,11 +21095,11 @@ ThreadStats = (function() { } }, retry: function() { - var ref; - if (g.BOARD.ID !== 'f' && ThreadStats.lastPost > ThreadStats.lastPageUpdate && ((ref = ThreadStats.pageCountEl) != null ? ref.textContent : void 0) !== '1') { - clearTimeout(ThreadStats.timeout); - return ThreadStats.timeout = setTimeout(ThreadStats.fetchPage, 5 * $.SECOND); + if (!(ThreadStats.showPage && ThreadStats.pageCountEl.textContent !== '1' && !g.SITE.threadModTimeIgnoresSage && ThreadStats.thread.posts.get(ThreadStats.thread.lastPost).info.date > ThreadStats.lastPageUpdate)) { + return; } + clearTimeout(ThreadStats.timeout); + return ThreadStats.timeout = setTimeout(ThreadStats.fetchPage, 5 * $.SECOND); } }; @@ -16686,6 +21117,7 @@ ThreadUpdater = (function() { if (g.VIEW !== 'thread' || !Conf['Thread Updater']) { return; } + this.enabled = true; this.audio = $.el('audio'); if ($.engine !== 'gecko') { this.audio.src = this.beep; @@ -16694,14 +21126,10 @@ ThreadUpdater = (function() { this.dialog = sc = $.el('span', { id: 'updater' }); - $.extend(sc, { - innerHTML: "" - }); + $.extend(sc, {innerHTML: ""}); Header.addShortcut('updater', sc, 100); } else { - this.dialog = sc = UI.dialog('updater', 'bottom: 0px; left: 0px;', { - innerHTML: "
          " - }); + this.dialog = sc = UI.dialog('updater', {innerHTML: "
          "}); $.addClass(doc, 'float'); $.ready(function() { return $.add(d.body, sc); @@ -16715,9 +21143,7 @@ ThreadUpdater = (function() { updateLink = $.el('span', { className: 'brackets-wrap updatelink' }); - $.extend(updateLink, { - innerHTML: "Update" - }); + $.extend(updateLink, {innerHTML: "Update"}); Main.ready(function() { var navLinksBot; if ((navLinksBot = $('.navLinksBot'))) { @@ -16743,9 +21169,7 @@ ThreadUpdater = (function() { el: el }); } - this.settings = $.el('span', { - innerHTML: "Interval" - }); + this.settings = $.el('span', {innerHTML: "Interval"}); $.on(this.settings, 'click', this.intervalShortcut); subEntries.push({ el: this.settings @@ -16764,7 +21188,7 @@ ThreadUpdater = (function() { }, node: function() { ThreadUpdater.thread = this; - ThreadUpdater.root = this.OP.nodes.root.parentNode; + ThreadUpdater.root = this.nodes.root; ThreadUpdater.outdateCount = 0; ThreadUpdater.postIDs = []; ThreadUpdater.fileIDs = []; @@ -16835,11 +21259,12 @@ ThreadUpdater = (function() { } }, load: function() { - var req; - req = ThreadUpdater.req; - switch (req.status) { + if (this !== ThreadUpdater.req) { + return; + } + switch (this.status) { case 200: - ThreadUpdater.parse(req); + ThreadUpdater.parse(this); if (ThreadUpdater.thread.isArchived) { return ThreadUpdater.kill(); } else { @@ -16847,7 +21272,9 @@ ThreadUpdater = (function() { } break; case 404: - return $.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/catalog.json", { + return $.ajax(g.SITE.urls.catalogJSON({ + boardID: ThreadUpdater.thread.board.ID + }), { onloadend: function() { var confirmed, i, k, len, len1, page, ref, ref1, thread; if (this.status === 200) { @@ -16870,12 +21297,12 @@ ThreadUpdater = (function() { if (confirmed) { return ThreadUpdater.kill(); } else { - return ThreadUpdater.error(req); + return ThreadUpdater.error(this); } } }); default: - return ThreadUpdater.error(req); + return ThreadUpdater.error(this); } } }, @@ -16953,17 +21380,18 @@ ThreadUpdater = (function() { return ThreadUpdater.seconds--; }, update: function() { - var ref; + var oldReq; clearTimeout(ThreadUpdater.timeoutID); ThreadUpdater.set('timer', '...', 'loading'); - if ((ref = ThreadUpdater.req) != null) { - ref.abort(); - } - return ThreadUpdater.req = $.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/thread/" + ThreadUpdater.thread + ".json", { - onloadend: ThreadUpdater.cb.load, + if ((oldReq = ThreadUpdater.req)) { + delete ThreadUpdater.req; + oldReq.abort(); + } + return ThreadUpdater.req = $.whenModified(g.SITE.urls.threadJSON({ + boardID: ThreadUpdater.thread.board.ID, + threadID: ThreadUpdater.thread.ID + }), 'ThreadUpdater', ThreadUpdater.cb.load, { timeout: $.MINUTE - }, { - whenModified: 'ThreadUpdater' }); }, updateThreadStatus: function(type, status) { @@ -16985,10 +21413,10 @@ ThreadUpdater = (function() { thread = ThreadUpdater.thread; board = thread.board; ref = ThreadUpdater.postIDs, lastPost = ref[ref.length - 1]; - if (postObjects[postObjects.length - 1].no < lastPost && new Date(req.getResponseHeader('Last-Modified')) - thread.posts[lastPost].info.date < 30 * $.SECOND) { + if (postObjects[postObjects.length - 1].no < lastPost && new Date(req.getResponseHeader('Last-Modified')) - thread.posts.get(lastPost).info.date < 30 * $.SECOND) { return; } - Build.spoilerRange[board] = OP.custom_spoiler; + g.SITE.Build.spoilerRange[board] = OP.custom_spoiler; thread.setStatus('Archived', !!OP.archived); ThreadUpdater.updateThreadStatus('Sticky', !!OP.sticky); ThreadUpdater.updateThreadStatus('Closed', !!OP.closed); @@ -17011,12 +21439,12 @@ ThreadUpdater = (function() { if (ID <= lastPost) { continue; } - if ((post = thread.posts[ID]) && !post.isFetchedQuote) { + if ((post = thread.posts.get(ID)) && !post.isFetchedQuote) { post.resurrect(); continue; } newPosts.push(board + "." + ID); - node = Build.postFromObject(postObject, board.ID); + node = g.SITE.Build.postFromObject(postObject, board.ID); posts.push(new Post(node, thread, board)); if (ThreadUpdater.postID === ID) { delete ThreadUpdater.postID; @@ -17029,7 +21457,7 @@ ThreadUpdater = (function() { if (!(indexOf.call(index, ID) < 0)) { continue; } - thread.posts[ID].kill(); + thread.posts.get(ID).kill(); deletedPosts.push(board + "." + ID); } ThreadUpdater.postIDs = index; @@ -17040,7 +21468,7 @@ ThreadUpdater = (function() { if (!(!(indexOf.call(files, ID) >= 0 || (ref3 = board + "." + ID, indexOf.call(deletedPosts, ref3) >= 0)))) { continue; } - thread.posts[ID].kill(true); + thread.posts.get(ID).kill(true); deletedFiles.push(board + "." + ID); } ThreadUpdater.fileIDs = files; @@ -17071,7 +21499,7 @@ ThreadUpdater = (function() { $.add(ThreadUpdater.root, post.nodes.root); } } - $.event('PostsInserted'); + $.event('PostsInserted', null, ThreadUpdater.root); if (scroll) { if (Conf['Bottom Scroll']) { window.scrollTo(0, d.body.clientHeight); @@ -17106,11 +21534,12 @@ ThreadUpdater = (function() { ThreadWatcher = (function() { var ThreadWatcher, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, slice = [].slice; ThreadWatcher = { init: function() { - var sc; + var ref, sc; if (!(this.enabled = Conf['Thread Watcher'])) { return; } @@ -17119,26 +21548,26 @@ ThreadWatcher = (function() { textContent: 'Watcher', title: 'Thread Watcher', href: 'javascript:;', - className: 'disabled fa fa-eye' + className: 'fa fa-eye' }); this.db = new DataBoard('watchedThreads', this.refresh, true); - this.dialog = UI.dialog('thread-watcher', 'top: 50px; left: 0px;', { - innerHTML: "
          Thread Watcher ×
          " - }); + this.dbLM = new DataBoard('watcherLastModified', null, true); + this.dialog = UI.dialog('thread-watcher', {innerHTML: "
          Thread Watcher ×
          "}); this.status = $('#watcher-status', this.dialog); this.list = this.dialog.lastElementChild; this.refreshButton = $('.refresh', this.dialog); this.closeButton = $('.move > .close', this.dialog); - this.unreaddb = Unread.db || new DataBoard('lastReadPosts'); + this.unreaddb = Unread.db || UnreadIndex.db || new DataBoard('lastReadPosts'); this.unreadEnabled = Conf['Remember Last Read Post']; $.on(d, 'QRPostSuccessful', this.cb.post); $.on(sc, 'click', this.toggleWatcher); $.on(this.refreshButton, 'click', this.buttonFetchAll); $.on(this.closeButton, 'click', this.toggleWatcher); - $.on(d, '4chanXInitFinished', this.ready); + this.menu.addHeaderMenuEntry(); + $.onExists(doc, 'body', this.addDialog); switch (g.VIEW) { case 'index': - $.on(d, 'IndexRefresh', this.cb.onIndexRefresh); + $.on(d, 'IndexUpdate', this.cb.onIndexUpdate); break; case 'thread': $.on(d, 'ThreadUpdate', this.cb.onThreadRefresh); @@ -17146,20 +21575,22 @@ ThreadWatcher = (function() { if (Conf['Fixed Thread Watcher']) { $.addClass(doc, 'fixed-watcher'); } - if (Conf['Toggleable Thread Watcher']) { + if (!Conf['Persistent Thread Watcher']) { + $.addClass(ThreadWatcher.shortcut, 'disabled'); this.dialog.hidden = true; - Header.addShortcut('watcher', sc, 510); - $.addClass(doc, 'toggleable-watcher'); } + Header.addShortcut('watcher', sc, 510); + ThreadWatcher.initLastModified(); ThreadWatcher.fetchAuto(); - if (g.VIEW === 'index' && Conf['JSON Index'] && Conf['Menu'] && g.BOARD.ID !== 'f') { + $.on(window, 'visibilitychange focus', function() { + return $.queueTask(ThreadWatcher.fetchAuto); + }); + if (Conf['Menu'] && Index.enabled) { Menu.menu.addEntry({ el: $.el('a', { href: 'javascript:;', className: 'has-shortcut-text' - }, { - innerHTML: "Alt+click" - }), + }, {innerHTML: "Alt+click"}), order: 6, open: function(arg) { var thread; @@ -17173,13 +21604,16 @@ ThreadWatcher = (function() { } this.cb = function() { $.event('CloseMenu'); - return ThreadWatcher.toggle(thread); + return ThreadWatcher.toggle(thread, true); }; $.on(this.el, 'click', this.cb); return true; } }); } + if ((ref = g.VIEW) !== 'index' && ref !== 'thread') { + return; + } Callbacks.Post.push({ name: 'Thread Watcher', cb: this.node @@ -17191,65 +21625,78 @@ ThreadWatcher = (function() { }, isWatched: function(thread) { var ref; - return (ref = ThreadWatcher.db) != null ? ref.get({ + return !!((ref = ThreadWatcher.db) != null ? ref.get({ boardID: thread.board.ID, threadID: thread.ID - }) : void 0; + }) : void 0); + }, + isWatchedRaw: function(boardID, threadID) { + var ref; + return !!((ref = ThreadWatcher.db) != null ? ref.get({ + boardID: boardID, + threadID: threadID + }) : void 0); + }, + setToggler: function(toggler, isWatched) { + toggler.classList.toggle('watched', isWatched); + return toggler.title = (isWatched ? 'Unwatch' : 'Watch') + " Thread"; }, node: function() { - var toggler; + var boardID, data, siteID, threadID, toggler; if (this.isReply) { return; } if (this.isClone) { - toggler = $('.watch-thread-link', this.nodes.post); + toggler = $('.watch-thread-link', this.nodes.info); } else { toggler = $.el('a', { href: 'javascript:;', className: 'watch-thread-link' }); - $.before($('input', this.nodes.post), toggler); + $.before($('input', this.nodes.info), toggler); + } + siteID = g.SITE.ID; + boardID = this.board.ID; + threadID = this.thread.ID; + data = ThreadWatcher.db.get({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }); + ThreadWatcher.setToggler(toggler, !!data); + $.on(toggler, 'click', ThreadWatcher.cb.toggle); + if (data && (data.excerpt == null)) { + return $.queueTask((function(_this) { + return function() { + return ThreadWatcher.update(siteID, boardID, threadID, { + excerpt: Get.threadExcerpt(_this.thread) + }); + }; + })(this)); } - return $.on(toggler, 'click', ThreadWatcher.cb.toggle); }, catalogNode: function() { if (ThreadWatcher.isWatched(this.thread)) { $.addClass(this.nodes.root, 'watched'); } - $.on(this.nodes.thumb.parentNode, 'click', (function(_this) { + return $.on(this.nodes.root, 'mousedown click', (function(_this) { return function(e) { if (!(e.button === 0 && e.altKey)) { return; } - ThreadWatcher.toggle(_this.thread); + if (e.type === 'click') { + ThreadWatcher.toggle(_this.thread, true); + } return e.preventDefault(); }; })(this)); - return $.on(this.nodes.thumb.parentNode, 'mousedown', function(e) { - if (e.button === 0 && e.altKey) { - return e.preventDefault(); - } - }); }, - ready: function() { - $.off(d, '4chanXInitFinished', ThreadWatcher.ready); + addDialog: function() { if (!Main.isThisPageLegit()) { return; } - ThreadWatcher.refresh(); - $.add(d.body, ThreadWatcher.dialog); - if (!Conf['Auto Watch']) { - return; - } - return $.get('AutoWatch', 0, function(arg) { - var AutoWatch, thread; - AutoWatch = arg.AutoWatch; - if (!(thread = g.BOARD.threads[AutoWatch])) { - return; - } - ThreadWatcher.add(thread); - return $["delete"]('AutoWatch'); - }); + ThreadWatcher.build(); + return $.prepend(d.body, ThreadWatcher.dialog); }, toggleWatcher: function() { $.toggleClass(ThreadWatcher.shortcut, 'disabled'); @@ -17257,101 +21704,153 @@ ThreadWatcher = (function() { }, cb: { openAll: function() { - var a, i, len, ref; + var a, j, len1, ref; if ($.hasClass(this, 'disabled')) { return; } - ref = $$('a[title]', ThreadWatcher.list); - for (i = 0, len = ref.length; i < len; i++) { - a = ref[i]; + ref = $$('a.watcher-link', ThreadWatcher.list); + for (j = 0, len1 = ref.length; j < len1; j++) { + a = ref[j]; + $.open(a.href); + } + return $.event('CloseMenu'); + }, + openUnread: function() { + var a, j, len1, ref; + if ($.hasClass(this, 'disabled')) { + return; + } + ref = $$('.replies-unread > a.watcher-link', ThreadWatcher.list); + for (j = 0, len1 = ref.length; j < len1; j++) { + a = ref[j]; + $.open(a.href); + } + return $.event('CloseMenu'); + }, + openDeads: function() { + var a, j, len1, ref; + if ($.hasClass(this, 'disabled')) { + return; + } + ref = $$('.dead-thread > a.watcher-link', ThreadWatcher.list); + for (j = 0, len1 = ref.length; j < len1; j++) { + a = ref[j]; $.open(a.href); } return $.event('CloseMenu'); }, + clear: function() { + var boardID, j, len1, ref, ref1, siteID, threadID; + if (!confirm("Delete ALL threads from watcher?")) { + return; + } + ref = ThreadWatcher.getAll(); + for (j = 0, len1 = ref.length; j < len1; j++) { + ref1 = ref[j], siteID = ref1.siteID, boardID = ref1.boardID, threadID = ref1.threadID; + ThreadWatcher.db["delete"]({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }); + } + ThreadWatcher.refresh(true); + return $.event('CloseMenu'); + }, pruneDeads: function() { - var boardID, data, i, len, ref, ref1, threadID; + var boardID, data, j, len1, ref, ref1, siteID, threadID; if ($.hasClass(this, 'disabled')) { return; } - ThreadWatcher.db.forceSync(); ref = ThreadWatcher.getAll(); - for (i = 0, len = ref.length; i < len; i++) { - ref1 = ref[i], boardID = ref1.boardID, threadID = ref1.threadID, data = ref1.data; - if (!data.isDead) { - continue; + for (j = 0, len1 = ref.length; j < len1; j++) { + ref1 = ref[j], siteID = ref1.siteID, boardID = ref1.boardID, threadID = ref1.threadID, data = ref1.data; + if (data.isDead) { + ThreadWatcher.db["delete"]({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }); + } + } + ThreadWatcher.refresh(true); + return $.event('CloseMenu'); + }, + dismiss: function() { + var boardID, data, j, len1, ref, ref1, siteID, threadID; + ref = ThreadWatcher.getAll(); + for (j = 0, len1 = ref.length; j < len1; j++) { + ref1 = ref[j], siteID = ref1.siteID, boardID = ref1.boardID, threadID = ref1.threadID, data = ref1.data; + if (data.quotingYou) { + ThreadWatcher.update(siteID, boardID, threadID, { + dismiss: data.quotingYou || 0 + }); } - delete ThreadWatcher.db.data.boards[boardID][threadID]; - ThreadWatcher.db.deleteIfEmpty({ - boardID: boardID - }); } - ThreadWatcher.db.save(); - ThreadWatcher.refresh(); return $.event('CloseMenu'); }, toggle: function() { var thread; thread = Get.postFromNode(this).thread; - Index.followedThreadID = thread.ID; - ThreadWatcher.toggle(thread); - return delete Index.followedThreadID; + return ThreadWatcher.toggle(thread, true); }, rm: function() { - var boardID, ref, threadID; + var boardID, ref, siteID, threadID; + siteID = this.parentNode.dataset.siteID; ref = this.parentNode.dataset.fullID.split('.'), boardID = ref[0], threadID = ref[1]; - return ThreadWatcher.rm(boardID, +threadID); + return ThreadWatcher.rm(siteID, boardID, +threadID, void 0, true); }, post: function(e) { - var boardID, postID, ref, threadID; + var boardID, cb, postID, ref, threadID; ref = e.detail, boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID; + cb = PostRedirect.delay(); if (postID === threadID) { if (Conf['Auto Watch']) { - return $.set('AutoWatch', threadID); + return ThreadWatcher.addRaw(boardID, threadID, {}, cb, true); } } else if (Conf['Auto Watch Reply']) { - return ThreadWatcher.add(g.threads[boardID + '.' + threadID]); + return ThreadWatcher.add(g.threads.get(boardID + '.' + threadID) || new Thread(threadID, g.boards[boardID] || new Board(boardID)), cb, true); } }, - onIndexRefresh: function() { - var boardID, data, db, ref, threadID; + onIndexUpdate: function(e) { + var boardID, data, db, nKilled, ref, ref1, siteID, threadID; db = ThreadWatcher.db; + siteID = g.SITE.ID; boardID = g.BOARD.ID; - db.forceSync(); - ref = db.data.boards[boardID]; + nKilled = 0; + ref = db.data[siteID].boards[boardID]; for (threadID in ref) { data = ref[threadID]; - if (!(data != null ? data.isDead : void 0) && !(threadID in g.BOARD.threads)) { - if (Conf['Auto Prune'] || !(data && typeof data === 'object')) { - db["delete"]({ - boardID: boardID, - threadID: threadID - }); - } else { - if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) { - ThreadWatcher.fetchStatus({ - boardID: boardID, - threadID: threadID, - data: data - }); - } - data.isDead = true; - db.set({ - boardID: boardID, - threadID: threadID, - val: data - }); - } + if (!(!(data != null ? data.isDead : void 0) && (ref1 = boardID + "." + threadID, indexOf.call(e.detail.threads, ref1) < 0))) { + continue; + } + if (!e.detail.threads.some(function(fullID) { + return +fullID.split('.')[1] > threadID; + })) { + continue; + } + if (Conf['Auto Prune'] || !(data && typeof data === 'object')) { + db["delete"]({ + boardID: boardID, + threadID: threadID + }); + nKilled++; + } else { + ThreadWatcher.fetchStatus({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + data: data + }); } } - return ThreadWatcher.refresh(); + if (nKilled) { + return ThreadWatcher.refresh(); + } }, onThreadRefresh: function(e) { var thread; - thread = g.threads[e.detail.threadID]; - if (!(e.detail[404] && ThreadWatcher.db.get({ - boardID: thread.board.ID, - threadID: thread.ID - }))) { + thread = g.threads.get(e.detail.threadID); + if (!(e.detail[404] && ThreadWatcher.isWatched(thread))) { return; } return ThreadWatcher.add(thread); @@ -17359,6 +21858,38 @@ ThreadWatcher = (function() { }, requests: [], fetched: 0, + fetch: function(url, arg, args, cb) { + var ajax, force, onloadend, ref, req, siteID; + siteID = arg.siteID, force = arg.force; + if (ThreadWatcher.requests.length === 0) { + ThreadWatcher.status.textContent = '...'; + $.addClass(ThreadWatcher.refreshButton, 'fa-spin'); + } + onloadend = function() { + if (this.finished) { + return; + } + this.finished = true; + ThreadWatcher.fetched++; + if (ThreadWatcher.fetched === ThreadWatcher.requests.length) { + ThreadWatcher.clearRequests(); + } else { + ThreadWatcher.status.textContent = (Math.round(ThreadWatcher.fetched / ThreadWatcher.requests.length * 100)) + "%"; + } + return cb.apply(this, args); + }; + ajax = siteID === g.SITE.ID ? $.ajax : CrossOrigin.ajax; + if (force) { + if ((ref = $.lastModified.ThreadWatcher) != null) { + delete ref[url]; + } + } + req = $.whenModified(url, 'ThreadWatcher', onloadend, { + timeout: $.MINUTE, + ajax: ajax + }); + return ThreadWatcher.requests.push(req); + }, clearRequests: function() { ThreadWatcher.requests = []; ThreadWatcher.fetched = 0; @@ -17366,196 +21897,402 @@ ThreadWatcher = (function() { return $.rmClass(ThreadWatcher.refreshButton, 'fa-spin'); }, abort: function() { - var i, len, ref, req; + var j, len1, ref, req; + delete ThreadWatcher.syncing; ref = ThreadWatcher.requests; - for (i = 0, len = ref.length; i < len; i++) { - req = ref[i]; - if (req.readyState !== 4) { - req.abort(); + for (j = 0, len1 = ref.length; j < len1; j++) { + req = ref[j]; + if (!(!req.finished)) { + continue; } + req.finished = true; + req.abort(); } return ThreadWatcher.clearRequests(); }, + initLastModified: function() { + var base, boardID, boards, data, date, lm, ref, ref1, siteID, url; + lm = ((base = $.lastModified)['ThreadWatcher'] || (base['ThreadWatcher'] = $.dict())); + ref = ThreadWatcher.dbLM.data; + for (siteID in ref) { + boards = ref[siteID]; + ref1 = boards.boards; + for (boardID in ref1) { + data = ref1[boardID]; + if (ThreadWatcher.db.get({ + siteID: siteID, + boardID: boardID + })) { + for (url in data) { + date = data[url]; + lm[url] = date; + } + } else { + ThreadWatcher.dbLM["delete"]({ + siteID: siteID, + boardID: boardID + }); + } + } + } + }, fetchAuto: function() { - var db, interval, now; + var db, interval, now, ref; clearTimeout(ThreadWatcher.timeout); if (!Conf['Auto Update Thread Watcher']) { return; } db = ThreadWatcher.db; - interval = ThreadWatcher.unreadEnabled && Conf['Show Unread Count'] ? 5 * $.MINUTE : 2 * $.HOUR; + interval = Conf['Show Page'] || (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) ? 5 * $.MINUTE : 2 * $.HOUR; now = Date.now(); - if (now >= (db.data.lastChecked || 0) + interval) { - db.data.lastChecked = now; - ThreadWatcher.fetchAllStatus(); - db.save(); + if (!((now - interval < (ref = db.data.lastChecked || 0) && ref <= now) || d.hidden || !d.hasFocus())) { + ThreadWatcher.fetchAllStatus(interval); } return ThreadWatcher.timeout = setTimeout(ThreadWatcher.fetchAuto, interval); }, buttonFetchAll: function() { - if (ThreadWatcher.requests.length) { + if (ThreadWatcher.syncing || ThreadWatcher.requests.length) { return ThreadWatcher.abort(); } else { return ThreadWatcher.fetchAllStatus(); } }, - fetchAllStatus: function() { - var i, len, ref, thread, threads; - ThreadWatcher.db.forceSync(); - ThreadWatcher.unreaddb.forceSync(); - if ((ref = QuoteYou.db) != null) { - ref.forceSync(); + fetchAllStatus: function(interval) { + var dbi, dbs, j, len1, n, results; + if (interval == null) { + interval = 0; + } + ThreadWatcher.status.textContent = '...'; + $.addClass(ThreadWatcher.refreshButton, 'fa-spin'); + ThreadWatcher.syncing = true; + dbs = [ThreadWatcher.db, ThreadWatcher.unreaddb, QuoteYou.db].filter(function(x) { + return x; + }); + n = 0; + results = []; + for (j = 0, len1 = dbs.length; j < len1; j++) { + dbi = dbs[j]; + results.push(dbi.forceSync(function() { + var board, boards, db, deep, k, len2, now, ref, ref1; + if ((++n) === dbs.length) { + if (!ThreadWatcher.syncing) { + return; + } + delete ThreadWatcher.syncing; + if (!((0 <= (ref = Date.now() - (ThreadWatcher.db.data.lastChecked || 0)) && ref < interval))) { + db = ThreadWatcher.db; + now = Date.now(); + deep = !((now - 2 * $.HOUR < (ref1 = db.data.lastChecked2 || 0) && ref1 <= now)); + boards = ThreadWatcher.getAll(true); + for (k = 0, len2 = boards.length; k < len2; k++) { + board = boards[k]; + ThreadWatcher.fetchBoard(board, deep); + } + db.setLastChecked(); + if (deep) { + db.setLastChecked('lastChecked2'); + } + } + if (ThreadWatcher.fetched === ThreadWatcher.requests.length) { + return ThreadWatcher.clearRequests(); + } + } + })); } - if (!(threads = ThreadWatcher.getAll()).length) { + return results; + }, + fetchBoard: function(board, deep) { + var base, boardID, data, force, j, len1, ref, site, siteID, thread, url, urlF; + if (!board.some(function(thread) { + return !thread.data.isDead; + })) { return; } - for (i = 0, len = threads.length; i < len; i++) { - thread = threads[i]; - ThreadWatcher.fetchStatus(thread); + force = false; + for (j = 0, len1 = board.length; j < len1; j++) { + thread = board[j]; + data = thread.data; + if (!data.isDead && data.last !== -1) { + if (Conf['Show Page'] && (data.page == null)) { + force = true; + } + if (data.modified == null) { + force = thread.force = true; + } + } } - }, - fetchStatus: function(thread, force) { - var boardID, data, req, threadID; - boardID = thread.boardID, threadID = thread.threadID, data = thread.data; - if (data.isDead && !force) { + ref = board[0], siteID = ref.siteID, boardID = ref.boardID; + site = g.sites[siteID]; + if (!site) { return; } - if (ThreadWatcher.requests.length === 0) { - ThreadWatcher.status.textContent = '...'; - $.addClass(ThreadWatcher.refreshButton, 'fa-spin'); + urlF = deep && site.threadModTimeIgnoresSage ? 'catalogJSON' : 'threadsListJSON'; + url = typeof (base = site.urls)[urlF] === "function" ? base[urlF]({ + siteID: siteID, + boardID: boardID + }) : void 0; + if (!url) { + return; } - req = $.ajax("//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", { - onloadend: function() { - return ThreadWatcher.parseStatus.call(this, thread); - }, - timeout: $.MINUTE - }, { - whenModified: force ? false : 'ThreadWatcher' + return ThreadWatcher.fetch(url, { + siteID: siteID, + force: force + }, [board, url], ThreadWatcher.parseBoard); + }, + parseBoard: function(board, url) { + var base, boardID, data, i, index, item, j, k, l, lastPage, len1, len2, len3, len4, lmDate, m, modified, nThreads, oldest, page, pageLength, ref, ref1, ref2, ref3, ref4, replies, siteID, thread, threadID, threads; + if (this.status !== 200) { + return; + } + ref = board[0], siteID = ref.siteID, boardID = ref.boardID; + lmDate = this.getResponseHeader('Last-Modified'); + ThreadWatcher.dbLM.extend({ + siteID: siteID, + boardID: boardID, + val: $.item(url, lmDate) }); - return ThreadWatcher.requests.push(req); + threads = $.dict(); + pageLength = 0; + nThreads = 0; + oldest = null; + try { + pageLength = ((ref1 = this.response[0]) != null ? ref1.threads.length : void 0) || 0; + ref2 = this.response; + for (i = j = 0, len1 = ref2.length; j < len1; i = ++j) { + page = ref2[i]; + ref3 = page.threads; + for (k = 0, len2 = ref3.length; k < len2; k++) { + item = ref3[k]; + threads[item.no] = { + page: i + 1, + index: nThreads, + modified: item.last_modified, + replies: item.replies + }; + nThreads++; + if ((oldest == null) || item.no < oldest) { + oldest = item.no; + } + } + } + } catch (error) { + for (l = 0, len3 = board.length; l < len3; l++) { + thread = board[l]; + ThreadWatcher.fetchStatus(thread); + } + } + for (m = 0, len4 = board.length; m < len4; m++) { + thread = board[m]; + threadID = thread.threadID, data = thread.data; + if (threads[threadID]) { + ref4 = threads[threadID], page = ref4.page, index = ref4.index, modified = ref4.modified, replies = ref4.replies; + if (Conf['Show Page']) { + lastPage = (typeof (base = g.sites[siteID]).isPrunedByAge === "function" ? base.isPrunedByAge({ + siteID: siteID, + boardID: boardID + }) : void 0) ? threadID === oldest : index >= nThreads - pageLength; + ThreadWatcher.update(siteID, boardID, threadID, { + page: page, + lastPage: lastPage + }); + } + if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) { + if (modified !== data.modified || ((replies != null) && replies !== data.replies)) { + (thread.newData || (thread.newData = {})).modified = modified; + ThreadWatcher.fetchStatus(thread); + } + } + } else { + ThreadWatcher.fetchStatus(thread); + } + } }, - parseStatus: function(arg) { - var boardID, data, i, isDead, lastReadPost, len, match, postObj, quotesYou, quotingYou, ref, ref1, regexp, threadID, unread; - boardID = arg.boardID, threadID = arg.threadID, data = arg.data; - ThreadWatcher.fetched++; - if (ThreadWatcher.fetched === ThreadWatcher.requests.length) { - ThreadWatcher.clearRequests(); - } else { - ThreadWatcher.status.textContent = (Math.round(ThreadWatcher.fetched / ThreadWatcher.requests.length * 100)) + "%"; + fetchStatus: function(thread) { + var base, boardID, data, force, ref, siteID, threadID, url; + siteID = thread.siteID, boardID = thread.boardID, threadID = thread.threadID, data = thread.data, force = thread.force; + url = (ref = g.sites[siteID]) != null ? typeof (base = ref.urls).threadJSON === "function" ? base.threadJSON({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }) : void 0 : void 0; + if (!url) { + return; + } + if (data.isDead && !force) { + return; + } + if (data.last === -1) { + return; } + return ThreadWatcher.fetch(url, { + siteID: siteID, + force: force + }, [thread], ThreadWatcher.parseStatus); + }, + parseStatus: function(thread, isArchiveURL) { + var archiveURL, base, boardID, data, force, isArchived, isDead, j, last, lastReadPost, len1, match, newData, postObj, quotesYou, quotingYou, ref, ref1, ref2, ref3, regexp, replies, site, siteID, threadID, unread, youOP; + siteID = thread.siteID, boardID = thread.boardID, threadID = thread.threadID, data = thread.data, newData = thread.newData, force = thread.force; + site = g.sites[siteID]; if (this.status === 200 && this.response) { - isDead = !!this.response.posts[0].archived; + last = this.response.posts[this.response.posts.length - 1].no; + replies = this.response.posts.length - 1; + isDead = isArchived = !!(this.response.posts[0].archived || isArchiveURL); if (isDead && Conf['Auto Prune']) { - ThreadWatcher.db["delete"]({ - boardID: boardID, - threadID: threadID - }); - ThreadWatcher.refresh(); + ThreadWatcher.rm(siteID, boardID, threadID); + return; + } + if (last === data.last && isDead === data.isDead && isArchived === data.isArchived) { return; } lastReadPost = ThreadWatcher.unreaddb.get({ + siteID: siteID, boardID: boardID, threadID: threadID, defaultValue: 0 }); - unread = quotingYou = 0; - ref = this.response.posts; - for (i = 0, len = ref.length; i < len; i++) { - postObj = ref[i]; - if (!(postObj.no > lastReadPost)) { + unread = data.unread || 0; + quotingYou = data.quotingYou || 0; + youOP = !!((ref = QuoteYou.db) != null ? ref.get({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + postID: threadID + }) : void 0); + ref1 = this.response.posts; + for (j = 0, len1 = ref1.length; j < len1; j++) { + postObj = ref1[j]; + if (!(postObj.no > (data.last || 0) && postObj.no > lastReadPost)) { continue; } - if ((ref1 = QuoteYou.db) != null ? ref1.get({ + if ((ref2 = QuoteYou.db) != null ? ref2.get({ + siteID: siteID, boardID: boardID, threadID: threadID, postID: postObj.no }) : void 0) { continue; } - unread++; - if (!(QuoteYou.db && postObj.com)) { - continue; - } quotesYou = false; - regexp = /]*\bhref="(?:\/([^\/]+)\/thread\/)?(\d+)?(?:#p(\d+))?"/g; - while (match = regexp.exec(postObj.com)) { - if (QuoteYou.db.get({ - boardID: match[1] || boardID, - threadID: match[2] || threadID, - postID: match[3] || match[2] || threadID - })) { - quotesYou = true; - break; + if (!Conf['Require OP Quote Link'] && youOP) { + quotesYou = true; + } else if (QuoteYou.db && postObj.com) { + regexp = site.regexp.quotelinkHTML; + regexp.lastIndex = 0; + while ((match = regexp.exec(postObj.com))) { + if (QuoteYou.db.get({ + siteID: siteID, + boardID: match[1] ? encodeURIComponent(match[1]) : boardID, + threadID: match[2] || threadID, + postID: match[3] || match[2] || threadID + })) { + quotesYou = true; + break; + } } } - if (quotesYou && !Filter.isHidden(Build.parseJSON(postObj, boardID))) { - quotingYou++; + if (!unread || (!quotingYou && quotesYou)) { + if (Filter.isHidden(site.Build.parseJSON(postObj, { + siteID: siteID, + boardID: boardID + }))) { + continue; + } } - } - if (isDead !== data.isDead || unread !== data.unread || quotingYou !== data.quotingYou) { - data.isDead = isDead; - data.unread = unread; - data.quotingYou = quotingYou; - ThreadWatcher.db.set({ - boardID: boardID, - threadID: threadID, - val: data - }); - return ThreadWatcher.refresh(); - } + unread++; + if (quotesYou) { + quotingYou = postObj.no; + } + } + newData || (newData = {}); + $.extend(newData, { + last: last, + replies: replies, + isDead: isDead, + isArchived: isArchived, + unread: unread, + quotingYou: quotingYou + }); + return ThreadWatcher.update(siteID, boardID, threadID, newData); } else if (this.status === 404) { - if (Conf['Auto Prune']) { - ThreadWatcher.db["delete"]({ - boardID: boardID, - threadID: threadID + archiveURL = (ref3 = g.sites[siteID]) != null ? typeof (base = ref3.urls).archivedThreadJSON === "function" ? base.archivedThreadJSON({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }) : void 0 : void 0; + if (!isArchiveURL && archiveURL) { + return ThreadWatcher.fetch(archiveURL, { + siteID: siteID, + force: force + }, [thread, true], ThreadWatcher.parseStatus); + } else if (site.mayLackJSON && (data.last == null)) { + return ThreadWatcher.update(siteID, boardID, threadID, { + last: -1 }); } else { - data.isDead = true; - delete data.unread; - delete data.quotingYou; - ThreadWatcher.db.set({ - boardID: boardID, - threadID: threadID, - val: data + return ThreadWatcher.update(siteID, boardID, threadID, { + isDead: true }); } - return ThreadWatcher.refresh(); } }, - getAll: function() { - var all, boardID, data, ref, threadID, threads; + getAll: function(groupByBoard) { + var all, boardID, boards, cont, data, ref, ref1, siteID, threadID, threads; all = []; - ref = ThreadWatcher.db.data.boards; - for (boardID in ref) { - threads = ref[boardID]; - if (Conf['Current Board'] && boardID !== g.BOARD.ID) { - continue; - } - for (threadID in threads) { - data = threads[threadID]; - if (data && typeof data === 'object') { - all.push({ - boardID: boardID, - threadID: threadID, - data: data - }); + ref = ThreadWatcher.db.data; + for (siteID in ref) { + boards = ref[siteID]; + ref1 = boards.boards; + for (boardID in ref1) { + threads = ref1[boardID]; + if (Conf['Current Board'] && (siteID !== g.SITE.ID || boardID !== g.BOARD.ID)) { + continue; + } + if (groupByBoard) { + all.push((cont = [])); + } + for (threadID in threads) { + data = threads[threadID]; + if (data && typeof data === 'object') { + (groupByBoard ? cont : all).push({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + data: data + }); + } } } } return all; }, - makeLine: function(boardID, threadID, data) { - var count, div, fullID, link, title, x; + makeLine: function(siteID, boardID, threadID, data) { + var count, div, excerpt, fullID, isArchived, link, page, ref, title, x; x = $.el('a', { className: 'fa fa-times', href: 'javascript:;' }); $.on(x, 'click', ThreadWatcher.cb.rm); + excerpt = data.excerpt, isArchived = data.isArchived; + excerpt || (excerpt = "/" + boardID + "/ - No." + threadID); + if (Conf['Show Site Prefix']) { + excerpt = ThreadWatcher.prefixes[siteID] + excerpt; + } link = $.el('a', { - href: "/" + boardID + "/thread/" + threadID, - title: data.excerpt, + href: ((ref = g.sites[siteID]) != null ? ref.urls.thread({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }, isArchived) : void 0) || '', + title: excerpt, className: 'watcher-link' }); + if (Conf['Show Page'] && (data.page != null)) { + page = $.el('span', { + textContent: "[" + data.page + "]", + className: 'watcher-page' + }); + $.add(link, page); + } if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count'] && (data.unread != null)) { count = $.el('span', { textContent: "(" + data.unread + ")", @@ -17564,19 +22301,28 @@ ThreadWatcher = (function() { $.add(link, count); } title = $.el('span', { - textContent: data.excerpt, + textContent: excerpt, className: 'watcher-title' }); $.add(link, title); div = $.el('div'); fullID = boardID + "." + threadID; div.dataset.fullID = fullID; + div.dataset.siteID = siteID; if (g.VIEW === 'thread' && fullID === (g.BOARD + "." + g.THREADID)) { $.addClass(div, 'current'); } if (data.isDead) { $.addClass(div, 'dead-thread'); } + if (Conf['Show Page']) { + if (data.lastPage) { + $.addClass(div, 'last-page'); + } + if (data.page != null) { + div.dataset.page = data.page; + } + } if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) { if (data.unread === 0) { $.addClass(div, 'replies-read'); @@ -17584,75 +22330,122 @@ ThreadWatcher = (function() { if (data.unread) { $.addClass(div, 'replies-unread'); } - if (data.quotingYou) { + if ((data.quotingYou || 0) > (data.dismiss || 0)) { $.addClass(div, 'replies-quoting-you'); } } $.add(div, [x, $.tn(' '), link]); return div; }, - refresh: function() { - var boardID, data, i, j, len, len1, list, nodes, ref, ref1, ref2, refresher, threadID; + setPrefixes: function(threads) { + var conflicts, conflicts2, j, k, len, len1, len2, prefix, prefixes, siteID, siteID2; + prefixes = $.dict(); + for (j = 0, len1 = threads.length; j < len1; j++) { + siteID = threads[j].siteID; + if (siteID in prefixes) { + continue; + } + len = 0; + prefix = ''; + conflicts = Object.keys(prefixes); + while (conflicts.length > 0) { + len++; + prefix = siteID.slice(0, len); + conflicts2 = []; + for (k = 0, len2 = conflicts.length; k < len2; k++) { + siteID2 = conflicts[k]; + if (siteID2.slice(0, len) === prefix) { + conflicts2.push(siteID2); + } else if (prefixes[siteID2].length < len) { + prefixes[siteID2] = siteID2.slice(0, len); + } + } + conflicts = conflicts2; + } + prefixes[siteID] = prefix; + } + return ThreadWatcher.prefixes = prefixes; + }, + build: function() { + var boardID, data, j, len1, list, nodes, ref, siteID, thread, threadID, threads; nodes = []; - ref = ThreadWatcher.getAll(); - for (i = 0, len = ref.length; i < len; i++) { - ref1 = ref[i], boardID = ref1.boardID, threadID = ref1.threadID, data = ref1.data; - nodes.push(ThreadWatcher.makeLine(boardID, threadID, data)); + threads = ThreadWatcher.getAll(); + ThreadWatcher.setPrefixes(threads); + for (j = 0, len1 = threads.length; j < len1; j++) { + ref = threads[j], siteID = ref.siteID, boardID = ref.boardID, threadID = ref.threadID, data = ref.data; + if ((data.excerpt == null) && siteID === g.SITE.ID && (thread = g.threads.get(boardID + "." + threadID)) && thread.OP) { + ThreadWatcher.db.extend({ + boardID: boardID, + threadID: threadID, + val: { + excerpt: Get.threadExcerpt(thread) + } + }); + } + nodes.push(ThreadWatcher.makeLine(siteID, boardID, threadID, data)); } list = ThreadWatcher.list; $.rmAll(list); $.add(list, nodes); + return ThreadWatcher.refreshIcon(); + }, + refresh: function(manual) { + ThreadWatcher.build(); g.threads.forEach(function(thread) { - var helper, j, len1, post, ref2, toggler; - helper = ThreadWatcher.isWatched(thread) ? ['addClass', 'Unwatch'] : ['rmClass', 'Watch']; + var isWatched, j, len1, post, ref, toggler; + isWatched = ThreadWatcher.isWatched(thread); if (thread.OP) { - ref2 = [thread.OP].concat(slice.call(thread.OP.clones)); - for (j = 0, len1 = ref2.length; j < len1; j++) { - post = ref2[j]; - toggler = $('.watch-thread-link', post.nodes.post); - $[helper[0]](toggler, 'watched'); - toggler.title = helper[1] + " Thread"; + ref = [thread.OP].concat(slice.call(thread.OP.clones)); + for (j = 0, len1 = ref.length; j < len1; j++) { + post = ref[j]; + if ((toggler = $('.watch-thread-link', post.nodes.info))) { + ThreadWatcher.setToggler(toggler, isWatched); + } } } if (thread.catalogView) { - return $[helper[0]](thread.catalogView.nodes.root, 'watched'); + return thread.catalogView.nodes.root.classList.toggle('watched', isWatched); } }); - ThreadWatcher.refreshIcon(); - ref2 = ThreadWatcher.menu.refreshers; - for (j = 0, len1 = ref2.length; j < len1; j++) { - refresher = ref2[j]; - refresher(); - } - if (Index.nodes && Conf['Pin Watched Threads']) { - Index.sort(); - return Index.buildIndex(); + if (Conf['Pin Watched Threads']) { + return $.event('SortIndex', { + deferred: !(manual && Conf['Index Mode'] === 'catalog') + }); } }, refreshIcon: function() { - var className, i, len, ref; + var className, j, len1, ref; ref = ['replies-unread', 'replies-quoting-you']; - for (i = 0, len = ref.length; i < len; i++) { - className = ref[i]; + for (j = 0, len1 = ref.length; j < len1; j++) { + className = ref[j]; ThreadWatcher.shortcut.classList.toggle(className, !!$("." + className, ThreadWatcher.dialog)); } }, - update: function(boardID, threadID, newData) { - var data, key, line, n, newLine, ref, val; + update: function(siteID, boardID, threadID, newData) { + var data, j, key, len1, line, n, newLine, ref, ref1, val; if (!(data = (ref = ThreadWatcher.db) != null ? ref.get({ + siteID: siteID, boardID: boardID, threadID: threadID }) : void 0)) { return; } if (newData.isDead && Conf['Auto Prune']) { - ThreadWatcher.db["delete"]({ - boardID: boardID, - threadID: threadID - }); - ThreadWatcher.refresh(); + ThreadWatcher.rm(siteID, boardID, threadID); return; } + if (newData.isDead || newData.last === -1) { + ref1 = ['isArchived', 'page', 'lastPage', 'unread', 'quotingyou']; + for (j = 0, len1 = ref1.length; j < len1; j++) { + key = ref1[j]; + if (!(key in newData)) { + newData[key] = void 0; + } + } + } + if ((newData.last != null) && newData.last < data.last) { + newData.modified = void 0; + } n = 0; for (key in newData) { val = newData[key]; @@ -17663,21 +22456,14 @@ ThreadWatcher = (function() { if (!n) { return; } - ThreadWatcher.db.forceSync(); - if (!(data = ThreadWatcher.db.get({ - boardID: boardID, - threadID: threadID - }))) { - return; - } - $.extend(data, newData); - ThreadWatcher.db.set({ + ThreadWatcher.db.extend({ + siteID: siteID, boardID: boardID, threadID: threadID, - val: data + val: newData }); - if (line = $("#watched-threads > [data-full-i-d='" + boardID + "." + threadID + "']", ThreadWatcher.dialog)) { - newLine = ThreadWatcher.makeLine(boardID, threadID, data); + if ((line = $("#watched-threads > [data-site-i-d='" + siteID + "'][data-full-i-d='" + boardID + "." + threadID + "']", ThreadWatcher.dialog))) { + newLine = ThreadWatcher.makeLine(siteID, boardID, threadID, data); $.replace(line, newLine); return ThreadWatcher.refreshIcon(); } else { @@ -17699,34 +22485,40 @@ ThreadWatcher = (function() { }); return cb(); } - if (data.isDead && !((data.unread != null) || (data.quotingYou != null))) { + if (data.isDead && !((data.isArchived != null) || (data.page != null) || (data.lastPage != null) || (data.unread != null) || (data.quotingYou != null))) { return cb(); } - data.isDead = true; - delete data.unread; - delete data.quotingYou; - return ThreadWatcher.db.set({ + return ThreadWatcher.db.extend({ boardID: boardID, threadID: threadID, - val: data + val: { + isDead: true, + isArchived: void 0, + page: void 0, + lastPage: void 0, + unread: void 0, + quotingYou: void 0 + } }, cb); }, - toggle: function(thread) { - var boardID, threadID; + toggle: function(thread, manual) { + var boardID, siteID, threadID; + siteID = g.SITE.ID; boardID = thread.board.ID; threadID = thread.ID; if (ThreadWatcher.db.get({ boardID: boardID, threadID: threadID })) { - return ThreadWatcher.rm(boardID, threadID); + return ThreadWatcher.rm(siteID, boardID, threadID, void 0, manual); } else { - return ThreadWatcher.add(thread); + return ThreadWatcher.add(thread, void 0, manual); } }, - add: function(thread) { - var boardID, data, threadID; + add: function(thread, cb, manual) { + var boardID, data, siteID, threadID; data = {}; + siteID = g.SITE.ID; boardID = thread.board.ID; threadID = thread.ID; if (thread.isDead) { @@ -17734,35 +22526,54 @@ ThreadWatcher = (function() { boardID: boardID, threadID: threadID })) { - ThreadWatcher.rm(boardID, threadID); + ThreadWatcher.rm(siteID, boardID, threadID, cb); return; } data.isDead = true; } - data.excerpt = Get.threadExcerpt(thread); - ThreadWatcher.db.set({ + if (thread.OP) { + data.excerpt = Get.threadExcerpt(thread); + } + return ThreadWatcher.addRaw(boardID, threadID, data, cb, manual); + }, + addRaw: function(boardID, threadID, data, cb, manual) { + var oldData, thread; + oldData = ThreadWatcher.db.get({ boardID: boardID, threadID: threadID, - val: data + defaultValue: $.dict() }); - ThreadWatcher.refresh(); - if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) { - return ThreadWatcher.fetchStatus({ - boardID: boardID, - threadID: threadID, - data: data - }, true); + delete oldData.last; + delete oldData.modified; + $.extend(oldData, data); + ThreadWatcher.db.set({ + boardID: boardID, + threadID: threadID, + val: oldData + }, cb); + ThreadWatcher.refresh(manual); + thread = { + siteID: g.SITE.ID, + boardID: boardID, + threadID: threadID, + data: data, + force: true + }; + if (Conf['Show Page'] && !data.isDead) { + return ThreadWatcher.fetchBoard([thread]); + } else if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) { + return ThreadWatcher.fetchStatus(thread); } }, - rm: function(boardID, threadID) { + rm: function(siteID, boardID, threadID, cb, manual) { ThreadWatcher.db["delete"]({ + siteID: siteID, boardID: boardID, threadID: threadID - }); - return ThreadWatcher.refresh(); + }, cb); + return ThreadWatcher.refresh(manual); }, menu: { - refreshers: [], init: function() { var menu; if (!Conf['Thread Watcher']) { @@ -17772,7 +22583,6 @@ ThreadWatcher = (function() { $.on($('.menu-button', ThreadWatcher.dialog), 'click', function(e) { return menu.toggle(e, this, ThreadWatcher); }); - this.addHeaderMenuEntry(); return this.addMenuEntries(); }, addHeaderMenuEntry: function() { @@ -17785,73 +22595,97 @@ ThreadWatcher = (function() { }); Header.menu.addEntry({ el: entryEl, - order: 60 - }); - $.on(entryEl, 'click', function() { - return ThreadWatcher.toggle(g.threads[g.BOARD + "." + g.THREADID]); + order: 60, + open: function() { + var addClass, ref, rmClass, text; + ref = !!ThreadWatcher.db.get({ + boardID: g.BOARD.ID, + threadID: g.THREADID + }) ? ['unwatch-thread', 'watch-thread', 'Unwatch thread'] : ['watch-thread', 'unwatch-thread', 'Watch thread'], addClass = ref[0], rmClass = ref[1], text = ref[2]; + $.addClass(entryEl, addClass); + $.rmClass(entryEl, rmClass); + entryEl.textContent = text; + return true; + } }); - return this.refreshers.push(function() { - var addClass, ref, rmClass, text; - ref = $('.current', ThreadWatcher.list) ? ['unwatch-thread', 'watch-thread', 'Unwatch thread'] : ['watch-thread', 'unwatch-thread', 'Watch thread'], addClass = ref[0], rmClass = ref[1], text = ref[2]; - $.addClass(entryEl, addClass); - $.rmClass(entryEl, rmClass); - return entryEl.textContent = text; + return $.on(entryEl, 'click', function() { + return ThreadWatcher.toggle(g.threads.get(g.BOARD + "." + g.THREADID), true); }); }, addMenuEntries: function() { - var cb, conf, entries, entry, i, len, name, ref, ref1, refresh, subEntries; + var cb, conf, entries, entry, j, len1, name, open, ref, ref1, text, title; entries = []; entries.push({ + text: 'Open all threads', cb: ThreadWatcher.cb.openAll, - entry: { - el: $.el('a', { - textContent: 'Open all threads' - }) - }, - refresh: function() { - return (ThreadWatcher.list.firstElementChild ? $.rmClass : $.addClass)(this.el, 'disabled'); + open: function() { + this.el.classList.toggle('disabled', !ThreadWatcher.list.firstElementChild); + return true; } }); entries.push({ - cb: ThreadWatcher.cb.pruneDeads, - entry: { - el: $.el('a', { - textContent: 'Prune dead threads' - }) - }, - refresh: function() { - return ($('.dead-thread', ThreadWatcher.list) ? $.rmClass : $.addClass)(this.el, 'disabled'); + text: 'Open unread threads', + cb: ThreadWatcher.cb.openUnread, + open: function() { + this.el.classList.toggle('disabled', !$('.replies-unread', ThreadWatcher.list)); + return true; + } + }); + entries.push({ + text: 'Open dead threads', + cb: ThreadWatcher.cb.openDeads, + open: function() { + this.el.classList.toggle('disabled', !$('.dead-thread', ThreadWatcher.list)); + return true; } }); - subEntries = []; - ref = Config.threadWatcher; - for (name in ref) { - conf = ref[name]; - subEntries.push(this.createSubEntry(name, conf[1])); - } entries.push({ - entry: { - el: $.el('span', { - textContent: 'Settings' - }), - subEntries: subEntries + text: 'Clear all threads', + cb: ThreadWatcher.cb.clear, + open: function() { + this.el.classList.toggle('disabled', !ThreadWatcher.list.firstElementChild); + return true; } }); - for (i = 0, len = entries.length; i < len; i++) { - ref1 = entries[i], entry = ref1.entry, cb = ref1.cb, refresh = ref1.refresh; - if (entry.el.nodeName === 'A') { - entry.el.href = 'javascript:;'; + entries.push({ + text: 'Prune dead threads', + cb: ThreadWatcher.cb.pruneDeads, + open: function() { + this.el.classList.toggle('disabled', !$('.dead-thread', ThreadWatcher.list)); + return true; } - if (cb) { - $.on(entry.el, 'click', cb); + }); + entries.push({ + text: 'Dismiss posts quoting you', + title: 'Unhighlight the thread watcher icon and threads until there are new replies quoting you.', + cb: ThreadWatcher.cb.dismiss, + open: function() { + this.el.classList.toggle('disabled', !$.hasClass(ThreadWatcher.shortcut, 'replies-quoting-you')); + return true; } - if (refresh) { - this.refreshers.push(refresh.bind(entry)); + }); + for (j = 0, len1 = entries.length; j < len1; j++) { + ref = entries[j], text = ref.text, title = ref.title, cb = ref.cb, open = ref.open; + entry = { + el: $.el('a', { + textContent: text, + href: 'javascript:;' + }) + }; + if (title) { + entry.el.title = title; } + $.on(entry.el, 'click', cb); + entry.open = open.bind(entry); this.menu.addEntry(entry); } + ref1 = Config.threadWatcher; + for (name in ref1) { + conf = ref1[name]; + this.addCheckbox(name, conf[1]); + } }, - createSubEntry: function(name, desc) { + addCheckbox: function(name, desc) { var entry, input; entry = { type: 'thread watcher', @@ -17865,13 +22699,15 @@ ThreadWatcher = (function() { entry.el.title += '\n[Remember Last Read Post is disabled.]'; } $.on(input, 'change', $.cb.checked); - if (name === 'Current Board' || name === 'Show Unread Count') { - $.on(input, 'change', ThreadWatcher.refresh); - } - if (name === 'Show Unread Count' || name === 'Auto Update Thread Watcher') { + $.on(input, 'change', function() { + if (name === 'Current Board' || name === 'Show Page' || name === 'Show Unread Count' || name === 'Show Site Prefix') { + return ThreadWatcher.refresh(); + } + }); + if (name === 'Show Page' || name === 'Show Unread Count' || name === 'Auto Update Thread Watcher') { $.on(input, 'change', ThreadWatcher.fetchAuto); } - return entry; + return this.menu.addEntry(entry); } } }; @@ -17895,7 +22731,8 @@ Unread = (function() { this.db = new DataBoard('lastReadPosts', this.sync); } this.hr = $.el('hr', { - id: 'unread-line' + id: 'unread-line', + className: 'unread-line' }); this.posts = new Set(); this.postsQuotingYou = new Set(); @@ -17911,7 +22748,7 @@ Unread = (function() { }); }, node: function() { - var ID, j, len, ref, ref1; + var ID, j, len, ref, ref1, resetLink; Unread.thread = this; Unread.title = d.title; Unread.lastReadPost = ((ref = Unread.db) != null ? ref.get({ @@ -17927,7 +22764,22 @@ Unread = (function() { } } $.one(d, '4chanXInitFinished', Unread.ready); - return $.on(d, 'ThreadUpdate', Unread.onUpdate); + $.on(d, 'PostsInserted', Unread.onUpdate); + $.on(d, 'ThreadUpdate', function(e) { + if (e.detail[404]) { + return Unread.update(); + } + }); + resetLink = $.el('a', { + href: 'javascript:;', + className: 'unread-reset', + textContent: 'Mark all unread' + }); + $.on(resetLink, 'click', Unread.reset); + return Header.menu.addEntry({ + el: resetLink, + order: 70 + }); }, ready: function() { if (Conf['Remember Last Read Post'] && Conf['Scroll to Last Read Post']) { @@ -17949,22 +22801,46 @@ Unread = (function() { } }, scroll: function() { - var hash, position, ref, root; + var bottom, hash, position; if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) { return; } - ReplyPruning.showIfHidden((ref = Unread.position) != null ? ref.data.nodes.root.id : void 0); position = Unread.positionPrev(); while (position) { - root = position.data.nodes.root; - if (!root.getBoundingClientRect().height) { + bottom = position.data.nodes.bottom; + if (!bottom.getBoundingClientRect().height) { position = position.prev; } else { - Header.scrollToIfNeeded(root, true); + Header.scrollToIfNeeded(bottom, true); break; } } }, + reset: function() { + if (Unread.lastReadPost == null) { + return; + } + Unread.posts = new Set(); + Unread.postsQuotingYou = new Set(); + Unread.order = new RandomAccessList(); + Unread.position = null; + Unread.lastReadPost = 0; + Unread.readCount = 0; + Unread.thread.posts.forEach(function(post) { + return Unread.addPost.call(post); + }); + $.forceSync('Remember Last Read Post'); + if (Conf['Remember Last Read Post'] && (!Unread.thread.isDead || Unread.thread.isArchived)) { + Unread.db.set({ + boardID: Unread.thread.board.ID, + threadID: Unread.thread.ID, + val: 0 + }); + } + Unread.updatePosition(); + Unread.setLine(); + return Unread.update(); + }, sync: function() { var ID, i, j, lastReadPost, postIDs, ref, ref1; if (Unread.lastReadPost == null) { @@ -17982,7 +22858,7 @@ Unread = (function() { postIDs = Unread.thread.posts.keys; for (i = j = ref = Unread.readCount, ref1 = postIDs.length; j < ref1; i = j += 1) { ID = +postIDs[i]; - if (!Unread.thread.posts[ID].isFetchedQuote) { + if (!Unread.thread.posts.get(ID).isFetchedQuote) { if (ID > Unread.lastReadPost) { break; } @@ -17996,19 +22872,14 @@ Unread = (function() { return Unread.update(); }, addPost: function() { - var ref; if (this.isFetchedQuote || this.isClone) { return; - } - Unread.order.push(this); - if (this.ID <= Unread.lastReadPost || this.isHidden || ((ref = QuoteYou.db) != null ? ref.get({ - boardID: this.board.ID, - threadID: this.thread.ID, - postID: this.ID - }) : void 0)) { + } + Unread.order.push(this); + if (this.ID <= Unread.lastReadPost || this.isHidden || QuoteYou.isYou(this)) { return; } - Unread.posts.add(this.ID); + Unread.posts.add((Unread.posts.last = this.ID)); Unread.addPostQuotingYou(this); return Unread.position != null ? Unread.position : Unread.position = Unread.order[this.ID]; }, @@ -18020,22 +22891,25 @@ Unread = (function() { if (!((ref1 = QuoteYou.db) != null ? ref1.get(Get.postDataFromLink(quotelink)) : void 0)) { continue; } - Unread.postsQuotingYou.add(post.ID); + Unread.postsQuotingYou.add((Unread.postsQuotingYou.last = post.ID)); Unread.openNotification(post); return; } }, - openNotification: function(post) { + openNotification: function(post, predicate) { var notif; + if (predicate == null) { + predicate = ' replied to you'; + } if (!Header.areNotificationsEnabled) { return; } - notif = new Notification(post.info.nameBlock + " replied to you", { - body: post.info.commentDisplay, + notif = new Notification("" + post.info.nameBlock + predicate, { + body: post.commentDisplay(), icon: Favicon.logo }); notif.onclick = function() { - Header.scrollToIfNeeded(post.nodes.root, true); + Header.scrollToIfNeeded(post.nodes.bottom, true); return window.focus(); }; return notif.onshow = function() { @@ -18044,12 +22918,12 @@ Unread = (function() { }, 7 * $.SECOND); }; }, - onUpdate: function(e) { - if (!e.detail[404]) { + onUpdate: function() { + return $.queueTask(function() { Unread.setLine(); Unread.read(); - } - return Unread.update(); + return Unread.update(); + }); }, readSinglePost: function(post) { var ID; @@ -18064,7 +22938,7 @@ Unread = (function() { return Unread.update(); }, read: $.debounce(100, function(e) { - var ID, count, data, ref, ref1, root; + var ID, bottom, count, data, ref; if (!Unread.posts.size && Unread.readCount !== Unread.thread.posts.keys.length) { Unread.saveLastReadPost(); } @@ -18074,20 +22948,13 @@ Unread = (function() { count = 0; while (Unread.position) { ref = Unread.position, ID = ref.ID, data = ref.data; - root = data.nodes.root; - if (!(!root.getBoundingClientRect().height || Header.getBottomOf(root) > -1)) { + bottom = data.nodes.bottom; + if (!(!bottom.getBoundingClientRect().height || Header.getBottomOf(bottom) > -1)) { break; } count++; Unread.posts["delete"](ID); Unread.postsQuotingYou["delete"](ID); - if ((ref1 = QuoteYou.db) != null ? ref1.get({ - boardID: data.board.ID, - threadID: data.thread.ID, - postID: ID - }) : void 0) { - QuoteYou.lastRead = root; - } Unread.position = Unread.position.next; } if (!count) { @@ -18113,7 +22980,7 @@ Unread = (function() { postIDs = Unread.thread.posts.keys; for (i = j = ref = Unread.readCount, ref1 = postIDs.length; j < ref1; i = j += 1) { ID = +postIDs[i]; - if (!Unread.thread.posts[ID].isFetchedQuote) { + if (!Unread.thread.posts.get(ID).isFetchedQuote) { if (Unread.posts.has(ID)) { break; } @@ -18124,7 +22991,6 @@ Unread = (function() { if (Unread.thread.isDead && !Unread.thread.isArchived) { return; } - Unread.db.forceSync(); return Unread.db.set({ boardID: Unread.thread.board.ID, threadID: Unread.thread.ID, @@ -18132,12 +22998,20 @@ Unread = (function() { }); }), setLine: function(force) { + var node, oldPosition, ref; if (!Conf['Unread Line']) { return; } if (Unread.hr.hidden || d.hidden || (force === true)) { + oldPosition = Unread.linePosition; if ((Unread.linePosition = Unread.positionPrev())) { - $.after(Unread.linePosition.data.nodes.root, Unread.hr); + if (Unread.linePosition !== oldPosition) { + node = Unread.linePosition.data.nodes.bottom; + if (((ref = node.nextSibling) != null ? ref.tagName : void 0) === 'BR') { + node = node.nextSibling; + } + $.after(node, Unread.hr); + } } else { $.rm(Unread.hr); } @@ -18154,211 +23028,331 @@ Unread = (function() { titleDead = Unread.thread.isDead ? Unread.title.replace('-', (Unread.thread.isArchived ? '- Archived -' : '- 404 -')) : Unread.title; d.title = "" + titleQuotingYou + titleCount + titleDead; } + Unread.saveThreadWatcherCount(); + if (Conf['Unread Favicon'] && g.SITE.software === 'yotsuba') { + isDead = Unread.thread.isDead; + return Favicon.set((countQuotingYou ? (isDead ? 'unreadDeadY' : 'unreadY') : count ? (isDead ? 'unreadDead' : 'unread') : (isDead ? 'dead' : 'default'))); + } + }, + saveThreadWatcherCount: $.debounce(2 * $.SECOND, function() { + var i, j, posts, quotingYou, ref; $.forceSync('Remember Last Read Post'); if (Conf['Remember Last Read Post'] && (!Unread.thread.isDead || Unread.thread.isArchived)) { - ThreadWatcher.update(Unread.thread.board.ID, Unread.thread.ID, { + quotingYou = !Conf['Require OP Quote Link'] && QuoteYou.isYou(Unread.thread.OP) ? Unread.posts : Unread.postsQuotingYou; + if (!quotingYou.size) { + quotingYou.last = 0; + } else if (!quotingYou.has(quotingYou.last)) { + quotingYou.last = 0; + posts = Unread.thread.posts.keys; + for (i = j = ref = posts.length - 1; j >= 0; i = j += -1) { + if (quotingYou.has(+posts[i])) { + quotingYou.last = posts[i]; + break; + } + } + } + return ThreadWatcher.update(g.SITE.ID, Unread.thread.board.ID, Unread.thread.ID, { + last: Unread.thread.lastPost, isDead: Unread.thread.isDead, - unread: count, - quotingYou: countQuotingYou + isArchived: Unread.thread.isArchived, + unread: Unread.posts.size, + quotingYou: quotingYou.last || 0 }); } - if (Conf['Unread Favicon']) { - isDead = Unread.thread.isDead; - Favicon.el.href = countQuotingYou ? Favicon[isDead ? 'unreadDeadY' : 'unreadY'] : count ? Favicon[isDead ? 'unreadDead' : 'unread'] : Favicon[isDead ? 'dead' : 'default']; - return $.add(d.head, Favicon.el); + }) + }; + + return Unread; + +}).call(this); + +UnreadIndex = (function() { + var UnreadIndex; + + UnreadIndex = { + lastReadPost: $.dict(), + hr: $.dict(), + markReadLink: $.dict(), + init: function() { + if (!(g.VIEW === 'index' && Conf['Remember Last Read Post'] && Conf['Unread Line in Index'])) { + return; + } + this.enabled = true; + this.db = new DataBoard('lastReadPosts', this.sync); + Callbacks.Thread.push({ + name: 'Unread Line in Index', + cb: this.node + }); + $.on(d, 'IndexRefreshInternal', this.onIndexRefresh); + return $.on(d, 'PostsInserted PostsRemoved', this.onPostsInserted); + }, + node: function() { + UnreadIndex.lastReadPost[this.fullID] = UnreadIndex.db.get({ + boardID: this.board.ID, + threadID: this.ID + }) || 0; + if (!Index.enabled) { + return UnreadIndex.update(this); + } + }, + onIndexRefresh: function(e) { + var i, len, ref, results, thread, threadID; + if (e.detail.isCatalog) { + return; + } + ref = e.detail.threadIDs; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + threadID = ref[i]; + thread = g.threads.get(threadID); + results.push(UnreadIndex.update(thread)); + } + return results; + }, + onPostsInserted: function(e) { + var ref, ref1, thread, wasVisible; + if (e.target === Index.root) { + return; + } + thread = Get.threadFromNode(e.target); + if (!thread || thread.nodes.root !== e.target) { + return; + } + wasVisible = !!((ref = UnreadIndex.hr[thread.fullID]) != null ? ref.parentNode : void 0); + UnreadIndex.update(thread); + if (Conf['Scroll to Last Read Post'] && e.type === 'PostsInserted' && !wasVisible && !!((ref1 = UnreadIndex.hr[thread.fullID]) != null ? ref1.parentNode : void 0)) { + return Header.scrollToIfNeeded(UnreadIndex.hr[thread.fullID], true); + } + }, + sync: function() { + return g.threads.forEach(function(thread) { + var lastReadPost, ref; + lastReadPost = UnreadIndex.db.get({ + boardID: thread.board.ID, + threadID: thread.ID + }) || 0; + if (lastReadPost !== UnreadIndex.lastReadPost[thread.fullID]) { + UnreadIndex.lastReadPost[thread.fullID] = lastReadPost; + if ((ref = thread.nodes.root) != null ? ref.parentNode : void 0) { + return UnreadIndex.update(thread); + } + } + }); + }, + update: function(thread) { + var divider, firstUnread, hasUnread, hr, lastReadPost, link, repliesRead, repliesShown; + lastReadPost = UnreadIndex.lastReadPost[thread.fullID]; + repliesShown = 0; + repliesRead = 0; + firstUnread = null; + thread.posts.forEach(function(post) { + if (post.isReply && thread.nodes.root.contains(post.nodes.root)) { + repliesShown++; + if (post.ID <= lastReadPost) { + return repliesRead++; + } else if ((!firstUnread || post.ID < firstUnread.ID) && !post.isHidden && !QuoteYou.isYou(post)) { + return firstUnread = post; + } + } + }); + hr = UnreadIndex.hr[thread.fullID]; + if (firstUnread && (repliesRead || (lastReadPost === thread.OP.ID && (!$(g.SITE.selectors.summary, thread.nodes.root) || thread.ID in ExpandThread.statuses)))) { + if (!hr) { + hr = UnreadIndex.hr[thread.fullID] = $.el('hr', { + className: 'unread-line' + }); + } + $.before(firstUnread.nodes.root, hr); + } else { + $.rm(hr); + } + hasUnread = repliesShown ? firstUnread || !repliesRead : Index.enabled ? thread.lastPost > lastReadPost : thread.OP.ID > lastReadPost; + thread.nodes.root.classList.toggle('unread-thread', hasUnread); + link = UnreadIndex.markReadLink[thread.fullID]; + if (!link) { + link = UnreadIndex.markReadLink[thread.fullID] = $.el('a', { + className: 'unread-mark-read brackets-wrap', + href: 'javascript:;', + textContent: 'Mark Read' + }); + $.on(link, 'click', UnreadIndex.markRead); + } + if ((divider = $(g.SITE.selectors.threadDivider, thread.nodes.root))) { + return $.before(divider, link); + } else { + return $.add(thread.nodes.root, link); } + }, + markRead: function() { + var thread; + thread = Get.threadFromNode(this); + UnreadIndex.lastReadPost[thread.fullID] = thread.lastPost; + UnreadIndex.db.set({ + boardID: thread.board.ID, + threadID: thread.ID, + val: thread.lastPost + }); + $.rm(UnreadIndex.hr[thread.fullID]); + thread.nodes.root.classList.remove('unread-thread'); + return ThreadWatcher.update(g.SITE.ID, thread.board.ID, thread.ID, { + last: thread.lastPost, + unread: 0, + quotingYou: 0 + }); } }; - return Unread; + return UnreadIndex; }).call(this); Captcha = {}; (function() { - Captcha.fixes = { - imageKeys: '789456123uiojklm'.split('').concat(['Comma', 'Period']), - imageKeys16: '7890uiopjkl'.split('').concat(['Semicolon', 'm', 'Comma', 'Period', 'Slash']), - css: '.rc-imageselect-target > div:focus, .rc-image-tile-target:focus {\n outline: 2px solid #4a90e2;\n}\n.rc-imageselect-target td:focus {\n box-shadow: inset 0 0 0 2px #4a90e2;\n outline: none;\n}\n.rc-button-default:focus {\n box-shadow: inset 0 0 0 2px #0063d6;\n}', - cssNoscript: '.fbc-payload-imageselect {\n position: relative;\n}\n.fbc-payload-imageselect > label {\n position: absolute;\n display: block;\n height: 93.3px;\n width: 93.3px;\n}\nlabel[data-row="0"] {top: 0px;}\nlabel[data-row="1"] {top: 93.3px;}\nlabel[data-row="2"] {top: 186.6px;}\nlabel[data-col="0"] {left: 0px;}\nlabel[data-col="1"] {left: 93.3px;}\nlabel[data-col="2"] {left: 186.6px;}\n.fbc-payload-imageselect > input:focus + label {\n outline: 2px solid #4a90e2;\n}\n.fbc-button-verify input:focus {\n box-shadow: inset 0 0 0 2px #0063d6;\n}\nbody.focus .fbc {\n box-shadow: inset 0 0 0 2px #4a90e2;\n}', + Captcha.cache = { init: function() { - switch (location.pathname.split('/')[3]) { - case 'anchor': - return this.initMain(); - case 'frame': - return this.initPopup(); - case 'fallback': - return this.initNoscript(); - } - }, - initMain: function() { - var a, j, len, ref; - $.onExists(d.body, '#recaptcha-anchor', function(checkbox) { - var focus; - focus = function() { - var ref; - if (d.hasFocus() && ((ref = d.activeElement) === d.documentElement || ref === d.body)) { - return checkbox.focus(); - } + $.on(d, 'SaveCaptcha', (function(_this) { + return function(e) { + return _this.saveAPI(e.detail); }; - focus(); - return $.on(window, 'focus', function() { - return $.queueTask(focus); - }); - }); - ref = $$('.rc-anchor-pt a'); - for (j = 0, len = ref.length; j < len; j++) { - a = ref[j]; - a.tabIndex = -1; - } + })(this)); + return $.on(d, 'NoCaptcha', (function(_this) { + return function(e) { + return _this.noCaptcha(e.detail); + }; + })(this)); }, - initPopup: function() { - $.addStyle(this.css); - this.fixImages(); - new MutationObserver((function(_this) { + captchas: [], + getCount: function() { + return this.captchas.length; + }, + neededRaw: function() { + return !(this.haveCookie() || this.captchas.length || QR.req || this.submitCB) && (QR.posts.length > 1 || Conf['Auto-load captcha'] || !QR.posts[0].isOnlyQuotes() || QR.posts[0].file); + }, + needed: function() { + return this.neededRaw() && $.event('LoadCaptcha'); + }, + prerequest: function() { + if (!Conf['Prerequest Captcha']) { + return; + } + return $.queueTask((function(_this) { return function() { - return _this.fixImages(); + var isReply; + if (!_this.prerequested && _this.neededRaw() && !$.event('LoadCaptcha') && !QR.captcha.occupied() && QR.cooldown.seconds <= 60 && QR.selected === QR.posts[QR.posts.length - 1] && !QR.selected.isOnlyQuotes()) { + isReply = QR.selected.thread !== 'new'; + if (!$.event('RequestCaptcha', { + isReply: isReply + })) { + _this.prerequested = true; + _this.submitCB = function(captcha) { + if (captcha) { + return _this.save(captcha); + } + }; + return _this.updateCount(); + } + } }; - })(this)).observe(d.body, { - childList: true, - subtree: true - }); - return $.on(d, 'keydown', this.keybinds.bind(this)); + })(this)); }, - initNoscript: function() { - var data, ref, token; - this.noscript = true; - data = (token = (ref = $('.fbc-verification-token > textarea')) != null ? ref.value : void 0) ? { - token: token - } : { - working: true - }; - new Connection(window.parent, '*').send(data); - d.body.classList.toggle('focus', d.hasFocus()); - $.on(window, 'focus blur', function() { - return d.body.classList.toggle('focus', d.hasFocus()); - }); - this.images = $$('.fbc-payload-imageselect > input'); - this.width = 3; - if (this.images.length !== 9) { - return; + haveCookie: function() { + return /\b_ct=/.test(d.cookie) && QR.posts[0].thread !== 'new'; + }, + getOne: function() { + var captcha; + delete this.prerequested; + this.clear(); + if ((captcha = this.captchas.shift())) { + this.count(); + return captcha; + } else { + return null; } - $.addStyle(this.cssNoscript); - this.addLabels(); - $.on(d, 'keydown', this.keybinds.bind(this)); - return $.on($('.fbc-imageselect-challenge > form'), 'submit', this.checkForm.bind(this)); }, - fixImages: function() { - var img, j, len, ref; - this.images = $$('.rc-image-tile-target'); - if (!this.images.length) { - this.images = $$('.rc-imageselect-target > div, .rc-imageselect-target td'); + request: function(isReply) { + if (!this.submitCB) { + if ($.event('RequestCaptcha', { + isReply: isReply + })) { + return; + } } - this.width = $$('.rc-imageselect-target tr:first-of-type td').length || Math.round(Math.sqrt(this.images.length)); - ref = this.images; - for (j = 0, len = ref.length; j < len; j++) { - img = ref[j]; - img.tabIndex = 0; + return (function(_this) { + return function(cb) { + _this.submitCB = cb; + return _this.updateCount(); + }; + })(this); + }, + abort: function() { + if (this.submitCB) { + delete this.submitCB; + $.event('AbortCaptcha'); + return this.updateCount(); } - if (this.images.length === 9) { - return this.addTooltips(this.images); + }, + saveAPI: function(captcha) { + var cb; + if ((cb = this.submitCB)) { + delete this.submitCB; + cb(captcha); + return this.updateCount(); } else { - return this.addTooltips16(this.images); + return this.save(captcha); } }, - addLabels: function() { - var checkbox, i, imageSelect, label, labels; - imageSelect = $('.fbc-payload-imageselect'); - labels = (function() { - var j, len, ref, results; - ref = this.images; - results = []; - for (i = j = 0, len = ref.length; j < len; i = ++j) { - checkbox = ref[i]; - checkbox.id = "checkbox-" + i; - label = $.el('label', { - htmlFor: checkbox.id - }); - label.dataset.row = Math.floor(i / 3); - label.dataset.col = i % 3; - $.after(checkbox, label); - results.push(label); + noCaptcha: function(detail) { + var cb; + if ((cb = this.submitCB)) { + if (!this.haveCookie() || (detail != null ? detail.error : void 0)) { + QR.error((detail != null ? detail.error : void 0) || 'Failed to retrieve captcha.'); + QR.captcha.setup(d.activeElement === QR.nodes.status); } - return results; - }).call(this); - return this.addTooltips(labels); - }, - addTooltips: function(nodes) { - var i, j, len, node; - for (i = j = 0, len = nodes.length; j < len; i = ++j) { - node = nodes[i]; - node.title = this.imageKeys[i] + " or " + (this.imageKeys[i + 9][0].toUpperCase()) + this.imageKeys[i + 9].slice(1); + delete this.submitCB; + cb(); + return this.updateCount(); } }, - addTooltips16: function(nodes) { - var i, j, key, len, node, ref; - ref = this.imageKeys16; - for (i = j = 0, len = ref.length; j < len; i = ++j) { - key = ref[i]; - if (i % 4 < this.width && (node = nodes[nodes.length - (4 - Math.floor(i / 4)) * this.width + (i % 4)])) { - node.title = "" + (key[0].toUpperCase()) + key.slice(1); - } + save: function(captcha) { + var cb; + if ((cb = this.submitCB)) { + this.abort(); + cb(captcha); + return; } + this.captchas.push(captcha); + this.captchas.sort(function(a, b) { + return a.timeout - b.timeout; + }); + return this.count(); }, - checkForm: function(e) { - var checkbox, j, len, n, ref; - n = 0; - ref = this.images; - for (j = 0, len = ref.length; j < len; j++) { - checkbox = ref[j]; - if (checkbox.checked) { - n++; + clear: function() { + var captcha, i, j, len, now, ref; + if (this.captchas.length) { + now = Date.now(); + ref = this.captchas; + for (i = j = 0, len = ref.length; j < len; i = ++j) { + captcha = ref[i]; + if (captcha.timeout > now) { + break; + } + } + if (i) { + this.captchas = this.captchas.slice(i); + return this.count(); } } - if (n === 0) { - return e.preventDefault(); - } - }, - keybinds: function(e) { - var dx, i, img, key, last, n, reload, verify, w, x; - if (!(this.images && doc.contains(this.images[0]))) { - return; - } - n = this.images.length; - w = this.width; - last = n + w - 1; - reload = $('#recaptcha-reload-button, .fbc-button-reload'); - verify = $('#recaptcha-verify-button, .fbc-button-verify > input'); - x = this.images.indexOf(d.activeElement); - if (x < 0) { - x = d.activeElement === verify ? last : n; - } - key = Keybinds.keyCode(e); - if (!this.noscript && key === 'Space' && x < n) { - this.images[x].click(); - } else if (n === 9 && (i = this.imageKeys.indexOf(key)) >= 0) { - this.images[i % 9].click(); - verify.focus(); - } else if (n !== 9 && (i = this.imageKeys16.indexOf(key)) >= 0 && i % 4 < w && (img = this.images[n - (4 - Math.floor(i / 4)) * w + (i % 4)])) { - img.click(); - verify.focus(); - } else if (dx = { - 'Up': n, - 'Down': w, - 'Left': last, - 'Right': 1 - }[key]) { - x = (x + dx) % (n + w); - if ((n < x && x < last)) { - x = dx === last ? n : last; - } - (this.images[x] || (x === n ? reload : void 0) || (x === last ? verify : void 0)).focus(); - } else { - return; + }, + count: function() { + clearTimeout(this.timer); + if (this.captchas.length) { + this.timer = setTimeout(this.clear.bind(this), this.captchas[0].timeout - Date.now()); } - e.preventDefault(); - return e.stopPropagation(); + return this.updateCount(); + }, + updateCount: function() { + return $.event('CaptchaCount', this.captchas.length); } }; @@ -18367,39 +23361,27 @@ Captcha = {}; (function() { Captcha.replace = { init: function() { - if (!(d.cookie.indexOf('pass_enabled=1') < 0)) { - return; - } - if (location.hostname === 'sys.4chan.org' && /[?&]altc\b/.test(location.search) && Main.jsEnabled) { - $.onExists(doc, 'script[src="//www.google.com/recaptcha/api/js/recaptcha_ajax.js"]', function() { - $.global(function() { - return window.el.onload = null; - }); - return Captcha.v1.create(); - }); - return; - } - if (((Conf['Use Recaptcha v1'] && location.hostname === 'boards.4chan.org') || (Conf['Use Recaptcha v1 in Reports'] && location.hostname === 'sys.4chan.org')) && Main.jsEnabled) { - $.ready(Captcha.replace.v1); + var ref; + if (!(g.SITE.software === 'yotsuba' && d.cookie.indexOf('pass_enabled=1') < 0)) { return; } if (Conf['Force Noscript Captcha'] && Main.jsEnabled) { $.ready(Captcha.replace.noscript); return; } - if (Conf['captchaLanguage'].trim() || Conf['Captcha Fixes']) { - if (location.hostname === 'boards.4chan.org') { + if (Conf['captchaLanguage'].trim()) { + if ((ref = location.hostname) === 'boards.4chan.org' || ref === 'boards.4channel.org') { return $.onExists(doc, '#captchaFormPart', function(node) { - return $.onExists(node, 'iframe', Captcha.replace.iframe); + return $.onExists(node, 'iframe[src^="https://www.google.com/recaptcha/"]', Captcha.replace.iframe); }); } else { - return $.onExists(doc, 'iframe', Captcha.replace.iframe); + return $.onExists(doc, 'iframe[src^="https://www.google.com/recaptcha/"]', Captcha.replace.iframe); } } }, noscript: function() { var insert, noscript, original, span, toggle; - if (!((original = $('#g-recaptcha, #captchaContainerAlt')) && (noscript = $('noscript')))) { + if (!((original = $('#g-recaptcha')) && (noscript = $('noscript', original.parentNode)))) { return; } span = $.el('span', { @@ -18409,7 +23391,7 @@ Captcha = {}; $.rm(original); insert = function() { span.innerHTML = noscript.textContent; - return Captcha.replace.iframe($('iframe', span)); + return Captcha.replace.iframe($('iframe[src^="https://www.google.com/recaptcha/"]', span)); }; if ((toggle = $('#togglePostFormLink a, #form-link'))) { return $.on(toggle, 'click', insert); @@ -18417,25 +23399,6 @@ Captcha = {}; return insert(); } }, - v1: function() { - var form, link; - if (!$.id('g-recaptcha')) { - return; - } - Captcha.v1.replace(); - if ((link = $.id('form-link'))) { - return $.on(link, 'click', function() { - return Captcha.v1.create(); - }); - } else if (location.hostname === 'boards.4chan.org') { - form = $.id('postForm'); - return form.addEventListener('focus', (function() { - return Captcha.v1.create(); - }), true); - } else { - return Captcha.v1.create(); - } - }, iframe: function(iframe) { var lang, src; if ((lang = Conf['captchaLanguage'].trim())) { @@ -18444,385 +23407,129 @@ Captcha = {}; iframe.src = src; } } - return Captcha.replace.autocopy(iframe); - }, - autocopy: function(iframe) { - if (!(Conf['Captcha Fixes'] && /^https:\/\/www\.google\.com\/recaptcha\/api\/fallback\?/.test(iframe.src))) { - return; - } - return new Connection(iframe, 'https://www.google.com', { - working: function() { - var ref, ref1; - if ((ref = $.id('qr')) != null ? ref.contains(iframe) : void 0) { - return (ref1 = $('#qr .captcha-container textarea')) != null ? ref1.parentNode.hidden = true : void 0; - } - }, - token: function(token) { - var node, textarea; - node = iframe; - while ((node = node.parentNode)) { - if ((textarea = $('textarea', node))) { - break; - } - } - textarea.value = token; - return $.event('input', null, textarea); - } - }); } }; }).call(this); (function() { - Captcha.v1 = { - blank: "data:image/svg+xml,", + Captcha.t = { init: function() { - var imgContainer, input; - if (d.cookie.indexOf('pass_enabled=1') >= 0) { - return; - } - if (!(this.isEnabled = !!$('#g-recaptcha, #captchaContainerAlt'))) { - return; - } - imgContainer = $.el('div', { - className: 'captcha-img', - title: 'Reload reCAPTCHA' - }); - $.extend(imgContainer, { - innerHTML: "" - }); - input = $.el('input', { - className: 'captcha-input field', - title: 'Verification', - autocomplete: 'off', - spellcheck: false - }); - this.nodes = { - img: imgContainer.firstChild, - input: input - }; - $.on(input, 'blur', QR.focusout); - $.on(input, 'focus', QR.focusin); - $.on(input, 'keydown', QR.captcha.keydown.bind(QR.captcha)); - $.on(this.nodes.img.parentNode, 'click', QR.captcha.reload.bind(QR.captcha)); - $.addClass(QR.nodes.el, 'has-captcha', 'captcha-v1'); - $.after(QR.nodes.com.parentNode, [imgContainer, input]); - this.captchas = []; - $.get('captchas', [], function(arg) { - var captchas; - captchas = arg.captchas; - QR.captcha.sync(captchas); - return QR.captcha.clear(); - }); - $.sync('captchas', this.sync); - this.replace(); - this.beforeSetup(); - if (Conf['Auto-load captcha']) { - this.setup(); - } - new MutationObserver(this.afterSetup).observe($.id('captchaContainerAlt'), { - childList: true - }); - return this.afterSetup(); - }, - replace: function() { - var container, old; - if (this.script) { - return; - } - if (!(this.script = $('script[src="//www.google.com/recaptcha/api/js/recaptcha_ajax.js"]', d.head))) { - this.script = $.el('script', { - src: '//www.google.com/recaptcha/api/js/recaptcha_ajax.js' - }); - $.add(d.head, this.script); - } - if (old = $.id('g-recaptcha')) { - container = $.el('div', { - id: 'captchaContainerAlt' - }); - return $.replace(old, container); - } - }, - create: function() { - var cont, lang; - cont = $.id('captchaContainerAlt'); - if (this.occupied) { - return; - } - this.occupied = true; - if ((lang = Conf['captchaLanguage'].trim())) { - cont.dataset.lang = lang; - } - $.onExists(cont, '#recaptcha_image', function(image) { - return $.on(image, 'click', function() { - if ($.id('recaptcha_challenge_image')) { - return $.global(function() { - return window.Recaptcha.reload(); - }); - } - }); - }); - $.onExists(cont, '#recaptcha_response_field', function(field) { - $.on(field, 'keydown', function(e) { - if (e.keyCode === 8 && !field.value) { - return $.global(function() { - return window.Recaptcha.reload(); - }); - } - }); - if (location.hostname === 'sys.4chan.org') { - return field.focus(); - } - }); - return $.global(function() { - var container, options, script; - container = document.getElementById('captchaContainerAlt'); - options = { - theme: 'clean', - tabindex: { - "boards.4chan.org": 5 - }[location.hostname], - lang: container.dataset.lang - }; - if (window.Recaptcha) { - return window.Recaptcha.create('6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc', container, options); - } else { - script = document.head.querySelector('script[src="//www.google.com/recaptcha/api/js/recaptcha_ajax.js"]'); - return script.addEventListener('load', function() { - return window.Recaptcha.create('6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc', container, options); - }, false); - } - }); - }, - cb: { - focus: function() { - return QR.captcha.setup(false, true); - } - }, - beforeSetup: function() { - var img, input, ref; - ref = this.nodes, img = ref.img, input = ref.input; - img.parentNode.hidden = true; - img.src = this.blank; - input.value = ''; - input.placeholder = 'Focus to load reCAPTCHA'; - this.count(); - return $.on(input, 'focus click', this.cb.focus); - }, - needed: function() { - var captchaCount, postsCount; - captchaCount = this.captchas.length; - if (QR.req) { - captchaCount++; - } - postsCount = QR.posts.length; - if (postsCount === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) { - postsCount = 0; - } - return captchaCount < postsCount; - }, - onNewPost: function() {}, - onPostChange: function() {}, - setup: function(focus, force) { - if (!(this.isEnabled && (force || this.needed()))) { - return; - } - this.create(); - if (focus) { - $.addClass(QR.nodes.el, 'focus'); - return this.nodes.input.focus(); - } - }, - afterSetup: function() { - var challenge, img, input, ref, setLifetime; - if (!(challenge = $.id('recaptcha_challenge_field_holder'))) { - return; - } - if (challenge === QR.captcha.nodes.challenge) { - return; - } - setLifetime = function(e) { - return QR.captcha.lifetime = e.detail; - }; - $.on(window, 'captcha:timeout', setLifetime); - $.global(function() { - return window.dispatchEvent(new CustomEvent('captcha:timeout', { - detail: window.RecaptchaState.timeout - })); - }); - $.off(window, 'captcha:timeout', setLifetime); - ref = QR.captcha.nodes, img = ref.img, input = ref.input; - img.parentNode.hidden = false; - input.placeholder = 'Verification'; - QR.captcha.count(); - $.off(input, 'focus click', QR.captcha.cb.focus); - QR.captcha.nodes.challenge = challenge; - new MutationObserver(QR.captcha.load.bind(QR.captcha)).observe(challenge, { - childList: true, - subtree: true, - attributes: true - }); - QR.captcha.load(); - if (QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight) { - QR.nodes.el.style.top = null; - return QR.nodes.el.style.bottom = '0px'; + var root; + if (d.cookie.indexOf('pass_enabled=1') >= 0) { + return; } - }, - destroy: function() { - if (!this.script) { + if (!(this.isEnabled = !!$('#t-root') || !$.id('postForm'))) { return; } - $.global(function() { - return window.Recaptcha.destroy(); + root = $.el('div', { + className: 'captcha-root' }); - delete this.occupied; - if (this.nodes) { - return this.beforeSetup(); - } - }, - sync: function(captchas) { - if (captchas == null) { - captchas = []; - } - QR.captcha.captchas = captchas; - return QR.captcha.count(); + this.nodes = { + root: root + }; + $.addClass(QR.nodes.el, 'has-captcha', 'captcha-t'); + return $.after(QR.nodes.com.parentNode, root); }, - getOne: function() { - var captcha, challenge, response, timeout; - this.clear(); - if (captcha = this.captchas.shift()) { - this.count(); - $.set('captchas', this.captchas); - return captcha; + moreNeeded: function() {}, + getThread: function() { + var boardID, threadID; + boardID = g.BOARD.ID; + if (QR.posts[0].thread === 'new') { + threadID = '0'; } else { - challenge = this.nodes.img.alt; - timeout = this.timeout; - if (/\S/.test(response = this.nodes.input.value)) { - this.destroy(); - return { - challenge: challenge, - response: response, - timeout: timeout - }; - } else { - return null; - } - } - }, - save: function() { - var response; - if (!/\S/.test(response = this.nodes.input.value)) { - return; + threadID = '' + QR.posts[0].thread; } - this.nodes.input.value = ''; - this.captchas.push({ - challenge: this.nodes.img.alt, - response: response, - timeout: this.timeout - }); - this.captchas.sort(function(a, b) { - return a.timeout - b.timeout; - }); - this.count(); - this.destroy(); - this.setup(false, true); - return $.set('captchas', this.captchas); + return { + boardID: boardID, + threadID: threadID + }; }, - clear: function() { - var captcha, i, j, len, now, ref; - if (!this.captchas.length) { + setup: function(focus) { + if (!this.isEnabled) { return; } - $.forceSync('captchas'); - now = Date.now(); - ref = this.captchas; - for (i = j = 0, len = ref.length; j < len; i = ++j) { - captcha = ref[i]; - if (captcha.timeout > now) { - break; - } + if (!this.nodes.container) { + this.nodes.container = $.el('div', { + className: 'captcha-container' + }); + $.prepend(this.nodes.root, this.nodes.container); + Captcha.t.currentThread = Captcha.t.getThread(); + $.global(function() { + var el; + el = document.querySelector('#qr .captcha-container'); + window.TCaptcha.init(el, this.boardID, +this.threadID); + return window.TCaptcha.setErrorCb(function(err) { + return window.dispatchEvent(new CustomEvent('CreateNotification', { + detail: { + type: 'warning', + content: '' + err + } + })); + }); + }, Captcha.t.currentThread); } - if (!i) { - return; + if (focus) { + return $('#t-resp').focus(); } - this.captchas = this.captchas.slice(i); - this.count(); - return $.set('captchas', this.captchas); }, - load: function() { - var challenge, challenge_image; - if ($('#captchaContainerAlt[class~="recaptcha_is_showing_audio"]')) { - this.nodes.img.src = this.blank; + destroy: function() { + if (!(this.isEnabled && this.nodes.container)) { return; } - if (!this.nodes.challenge.firstChild) { + $.global(function() { + return window.TCaptcha.destroy(); + }); + $.rm(this.nodes.container); + return delete this.nodes.container; + }, + updateThread: function() { + var boardID, newThread, ref, threadID; + if (!this.isEnabled) { return; } - if (!(challenge_image = $.id('recaptcha_challenge_image'))) { - return; + ref = Captcha.t.currentThread || {}, boardID = ref.boardID, threadID = ref.threadID; + newThread = Captcha.t.getThread(); + if (!(newThread.boardID === boardID && newThread.threadID === threadID)) { + Captcha.t.destroy(); + return Captcha.t.setup(); } - this.timeout = Date.now() + this.lifetime * $.SECOND - $.MINUTE; - challenge = this.nodes.challenge.firstChild.value; - this.nodes.img.alt = challenge; - this.nodes.img.src = challenge_image.src; - this.nodes.input.value = ''; - return this.clear(); }, - count: function() { - var count, placeholder; - count = this.captchas ? this.captchas.length : 0; - placeholder = this.nodes.input.placeholder.replace(/\ \(.*\)$/, ''); - placeholder += (function() { - switch (count) { - case 0: - if (placeholder === 'Verification') { - return ' (Shift + Enter to cache)'; - } else { - return ''; - } - break; - case 1: - return ' (1 cached captcha)'; - default: - return " (" + count + " cached captchas)"; + getOne: function() { + var el, i, key, len, ref, response; + response = {}; + if (this.nodes.container) { + ref = ['t-response', 't-challenge']; + for (i = 0, len = ref.length; i < len; i++) { + key = ref[i]; + response[key] = $("[name='" + key + "']", this.nodes.container).value; } - })(); - this.nodes.input.placeholder = placeholder; - this.nodes.input.alt = count; - clearTimeout(this.timer); - if (count) { - return this.timer = setTimeout(this.clear.bind(this), this.captchas[0].timeout - Date.now()); } - }, - reload: function(focus) { - $.global(function() { - if (window.Recaptcha.type === 'image') { - window.Recaptcha.reload(); - } else { - window.Recaptcha.switch_type('image'); - } - return window.Recaptcha.should_focus = false; - }); - if (focus) { - return this.nodes.input.focus(); + if (!response['t-response'] && !((el = $('#t-msg')) && /Verification not required/i.test(el.textContent))) { + response = null; } + return response; }, - keydown: function(e) { - if (e.keyCode === 8 && !this.nodes.input.value) { - this.reload(); - } else if (e.keyCode === 13 && e.shiftKey) { - this.save(); - } else { + setUsed: function() { + if (!this.isEnabled) { return; } - return e.preventDefault(); + if (this.nodes.container) { + return $.global(function() { + return window.TCaptcha.clearChallenge(); + }); + } + }, + occupied: function() { + return !!this.nodes.container; } }; }).call(this); (function() { + var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + Captcha.v2 = { lifetime: 2 * $.MINUTE, init: function() { @@ -18830,25 +23537,18 @@ Captcha = {}; if (d.cookie.indexOf('pass_enabled=1') >= 0) { return; } - if (!(this.isEnabled = !!$('#g-recaptcha, #captchaContainerAlt, #captcha-forced-noscript'))) { + if (!(this.isEnabled = !!$('#g-recaptcha, #captcha-forced-noscript') || !$.id('postForm'))) { return; } if ((this.noscript = Conf['Force Noscript Captcha'] || !Main.jsEnabled)) { $.addClass(QR.nodes.el, 'noscript-captcha'); } - this.captchas = []; - $.get('captchas', [], function(arg) { - var captchas; - captchas = arg.captchas; - return QR.captcha.sync(captchas); - }); - $.sync('captchas', this.sync.bind(this)); + Captcha.cache.init(); + $.on(d, 'CaptchaCount', this.count.bind(this)); root = $.el('div', { className: 'captcha-root' }); - $.extend(root, { - innerHTML: "
          " - }); + $.extend(root, {innerHTML: "
          "}); counter = $('.captcha-counter > a', root); this.nodes = { root: root, @@ -18877,7 +23577,7 @@ Captcha = {}; })(this)); }, timeouts: {}, - postsCount: 0, + prevNeeded: 0, noscriptURL: function() { var lang, url; url = 'https://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc'; @@ -18886,28 +23586,17 @@ Captcha = {}; } return url; }, - needed: function() { - var captchaCount; - captchaCount = this.captchas.length; - if (QR.req) { - captchaCount++; - } - this.postsCount = QR.posts.length; - if (this.postsCount === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) { - this.postsCount = 0; - } - return captchaCount < this.postsCount; - }, - onNewPost: function() { - return this.setup(); - }, - onPostChange: function() { - if (this.postsCount === 0) { - this.setup(); - } - if (QR.posts.length === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) { - return this.postsCount = 0; - } + moreNeeded: function() { + return $.queueTask((function(_this) { + return function() { + var needed; + needed = Captcha.cache.needed(); + if (needed && !_this.prevNeeded) { + _this.setup(QR.cooldown.auto && d.activeElement === QR.nodes.status); + } + return _this.prevNeeded = needed; + }; + })(this)); }, toggle: function() { if (this.nodes.container && !this.timeouts.destroy) { @@ -18917,7 +23606,7 @@ Captcha = {}; } }, setup: function(focus, force) { - if (!(this.isEnabled && (this.needed() || force))) { + if (!(this.isEnabled && (Captcha.cache.needed() || force))) { return; } if (focus) { @@ -18933,7 +23622,7 @@ Captcha = {}; $.queueTask((function(_this) { return function() { var iframe; - if (_this.nodes.container && d.activeElement === _this.nodes.counter && (iframe = $('iframe', _this.nodes.container))) { + if (_this.nodes.container && d.activeElement === _this.nodes.counter && (iframe = $('iframe[src^="https://www.google.com/recaptcha/"]', _this.nodes.container))) { iframe.focus(); return QR.focus(); } @@ -18959,6 +23648,7 @@ Captcha = {}; var div, iframe, textarea; iframe = $.el('iframe', { id: 'qr-captcha-iframe', + scrolling: 'no', src: this.noscriptURL() }); div = $.el('div'); @@ -18968,14 +23658,14 @@ Captcha = {}; }, setupJS: function() { return $.global(function() { - var cbNative, render; + var cbNative, render, script; render = function() { var classList, container; classList = document.documentElement.classList; container = document.querySelector('#qr .captcha-container'); return container.dataset.widgetID = window.grecaptcha.render(container, { sitekey: '6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc', - theme: classList.contains('tomorrow') || classList.contains('dark-captcha') ? 'dark' : 'light', + theme: classList.contains('tomorrow') || classList.contains('spooky') || classList.contains('dark-captcha') ? 'dark' : 'light', callback: function(response) { return window.dispatchEvent(new CustomEvent('captcha:success', { detail: response @@ -18987,21 +23677,26 @@ Captcha = {}; return render(); } else { cbNative = window.onRecaptchaLoaded; - return window.onRecaptchaLoaded = function() { + window.onRecaptchaLoaded = function() { render(); return cbNative(); }; + if (!document.head.querySelector('script[src^="https://www.google.com/recaptcha/api.js"]')) { + script = document.createElement('script'); + script.src = 'https://www.google.com/recaptcha/api.js?onload=onRecaptchaLoaded&render=explicit'; + return document.head.appendChild(script); + } } }); }, afterSetup: function(mutations) { - var iframe, j, k, len, len1, mutation, node, ref, textarea; - for (j = 0, len = mutations.length; j < len; j++) { - mutation = mutations[j]; + var i, iframe, j, len, len1, mutation, node, ref, textarea; + for (i = 0, len = mutations.length; i < len; i++) { + mutation = mutations[i]; ref = mutation.addedNodes; - for (k = 0, len1 = ref.length; k < len1; k++) { - node = ref[k]; - if ((iframe = $.x('./descendant-or-self::iframe', node))) { + for (j = 0, len1 = ref.length; j < len1; j++) { + node = ref[j]; + if ((iframe = $.x('./descendant-or-self::iframe[starts-with(@src, "https://www.google.com/recaptcha/")]', node))) { this.setupIFrame(iframe); } if ((textarea = $.x('./descendant-or-self::textarea', node))) { @@ -19011,6 +23706,7 @@ Captcha = {}; } }, setupIFrame: function(iframe) { + var ref, ref1; if (!doc.contains(iframe)) { return; } @@ -19021,15 +23717,15 @@ Captcha = {}; if (d.activeElement === this.nodes.counter) { iframe.focus(); } - return $.global(function() { - var f; - f = document.querySelector('#qr iframe'); - return f.focus = f.blur = function() {}; - }); + if (((ref = $.engine) === 'blink' || ref === 'edge') && (ref1 = iframe.parentNode, indexOf.call($$('#qr .captcha-container > div > div:first-of-type'), ref1) >= 0)) { + return $.on(iframe.parentNode, 'scroll', function() { + return this.scrollTop = 0; + }); + } }, fixQRPosition: function() { if (QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight) { - QR.nodes.el.style.top = null; + QR.nodes.el.style.top = ''; return QR.nodes.el.style.bottom = '0px'; } }, @@ -19041,58 +23737,32 @@ Captcha = {}; })(this)); }, destroy: function() { - var garbage, i, ins, node, ref; if (!this.isEnabled) { return; } delete this.timeouts.destroy; $.rmClass(QR.nodes.el, 'captcha-open'); if (this.nodes.container) { + $.global(function() { + var container; + container = document.querySelector('#qr .captcha-container'); + return window.grecaptcha.reset(container.dataset.widgetID); + }); $.rm(this.nodes.container); + return delete this.nodes.container; } - delete this.nodes.container; - garbage = $.X('//iframe[starts-with(@src, "https://www.google.com/recaptcha/api2/frame")]/ancestor-or-self::*[parent::body]'); - i = 0; - while (node = garbage.snapshotItem(i++)) { - if (((ref = (ins = node.nextSibling)) != null ? ref.nodeName : void 0) === 'INS') { - $.rm(ins); - } - $.rm(node); - } - }, - sync: function(captchas) { - if (captchas == null) { - captchas = []; - } - this.captchas = captchas; - this.clear(); - return this.count(); }, - getOne: function() { - var captcha; - this.clear(); - if ((captcha = this.captchas.shift())) { - $.set('captchas', this.captchas); - this.count(); - return captcha; - } else { - return null; - } + getOne: function(isReply) { + return Captcha.cache.getOne(isReply); }, save: function(pasted, token) { var base, focus, ref; - $.forceSync('captchas'); - this.captchas.push({ + Captcha.cache.save({ response: token || $('textarea', this.nodes.container).value, timeout: Date.now() + this.lifetime }); - this.captchas.sort(function(a, b) { - return a.timeout - b.timeout; - }); - $.set('captchas', this.captchas); - this.count(); focus = ((ref = d.activeElement) != null ? ref.nodeName : void 0) === 'IFRAME' && /https?:\/\/www\.google\.com\/recaptcha\//.test(d.activeElement.src); - if (this.needed()) { + if (Captcha.cache.needed()) { if (focus) { if (QR.cooldown.auto || Conf['Post on Captcha Completion']) { this.nodes.counter.focus(); @@ -19117,34 +23787,12 @@ Captcha = {}; return QR.submit(); } }, - clear: function() { - var captcha, i, j, len, now, ref; - if (!this.captchas.length) { - return; - } - $.forceSync('captchas'); - now = Date.now(); - ref = this.captchas; - for (i = j = 0, len = ref.length; j < len; i = ++j) { - captcha = ref[i]; - if (captcha.timeout > now) { - break; - } - } - if (!i) { - return; - } - this.captchas = this.captchas.slice(i); - this.count(); - $.set('captchas', this.captchas); - return this.setup(d.activeElement === QR.nodes.status); - }, count: function() { - this.nodes.counter.textContent = "Captchas: " + this.captchas.length; - clearTimeout(this.timeouts.clear); - if (this.captchas.length) { - return this.timeouts.clear = setTimeout(this.clear.bind(this), this.captchas[0].timeout - Date.now()); - } + var count, loading; + count = Captcha.cache.getCount(); + loading = Captcha.cache.submitCB ? '...' : ''; + this.nodes.counter.textContent = "Captchas: " + count + loading; + return this.moreNeeded(); }, reload: function() { if ($('iframe[src^="https://www.google.com/recaptcha/api/fallback?"]', this.nodes.container)) { @@ -19157,6 +23805,9 @@ Captcha = {}; return window.grecaptcha.reset(container.dataset.widgetID); }); } + }, + occupied: function() { + return !!this.nodes.container && !this.timeouts.destroy; } }; @@ -19167,7 +23818,7 @@ PassLink = (function() { PassLink = { init: function() { - if (!Conf['Pass Link']) { + if (!(g.SITE.software === 'yotsuba' && Conf['Pass Link'])) { return; } return Main.ready(this.ready); @@ -19180,11 +23831,9 @@ PassLink = (function() { passLink = $.el('span', { className: 'brackets-wrap pass-link-container' }); - $.extend(passLink, { - innerHTML: "4chan Pass" - }); + $.extend(passLink, {innerHTML: "4chan Pass"}); $.on(passLink.firstElementChild, 'click', function() { - return window.open('//sys.4chan.org/auth', Date.now(), 'width=500,height=280,toolbar=0'); + return window.open("//sys." + (location.hostname.split('.')[1]) + ".org/auth", Date.now(), 'width=500,height=280,toolbar=0'); }); return $.before(styleSelector.previousSibling, [passLink, $.tn('\u00A0\u00A0')]); } @@ -19194,6 +23843,52 @@ PassLink = (function() { }).call(this); +PostRedirect = (function() { + var PostRedirect; + + PostRedirect = { + init: function() { + return $.on(d, 'QRPostSuccessful', (function(_this) { + return function(e) { + if (!e.detail.redirect) { + return; + } + _this.event = e; + _this.delays = 0; + return $.queueTask(function() { + if (e === _this.event && _this.delays === 0) { + return location.href = e.detail.redirect; + } + }); + }; + })(this)); + }, + delays: 0, + delay: function() { + var e; + if (!this.event) { + return null; + } + e = this.event; + this.delays++; + return (function(_this) { + return function() { + if (e !== _this.event) { + return; + } + _this.delays--; + if (_this.delays === 0) { + return location.href = e.detail.redirect; + } + }; + })(this); + } + }; + + return PostRedirect; + +}).call(this); + PostSuccessful = (function() { var PostSuccessful; @@ -19232,8 +23927,8 @@ QR = (function() { slice = [].slice; QR = { - mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/vnd.adobe.flash.movie', 'application/x-shockwave-flash', 'video/webm'], - validExtension: /\.(jpe?g|png|gif|pdf|swf|webm)$/i, + mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/vnd.adobe.flash.movie', 'application/x-shockwave-flash', 'video/webm', 'video/mp4'], + validExtension: /\.(jpe?g|png|gif|pdf|swf|webm|mp4)$/i, typeFromExtension: { 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', @@ -19241,7 +23936,8 @@ QR = (function() { 'gif': 'image/gif', 'pdf': 'application/pdf', 'swf': 'application/vnd.adobe.flash.movie', - 'webm': 'video/webm' + 'webm': 'video/webm', + 'mp4': 'video/mp4' }, extensionFromType: { 'image/jpeg': 'jpg', @@ -19250,20 +23946,18 @@ QR = (function() { 'application/pdf': 'pdf', 'application/vnd.adobe.flash.movie': 'swf', 'application/x-shockwave-flash': 'swf', - 'video/webm': 'webm' + 'video/webm': 'webm', + 'video/mp4': 'mp4' }, init: function() { - var sc, version; + var sc; if (!Conf['Quick Reply']) { return; } this.posts = []; - if (g.VIEW === 'archive') { - return; - } - version = Conf['Use Recaptcha v1'] && Main.jsEnabled ? 'v1' : 'v2'; - this.captcha = Captcha[version]; - $.on(d, '4chanXInitFinished', this.initReady); + $.on(d, '4chanXInitFinished', function() { + return BoardConfig.ready(QR.initReady); + }); Callbacks.Post.push({ name: 'Quick Reply', cb: this.node @@ -19288,30 +23982,43 @@ QR = (function() { return Header.addShortcut('qr', sc, 540); }, initReady: function() { - var link, linkBot, navLinksBot, origToggle; - $.off(d, '4chanXInitFinished', this.initReady); - QR.postingIsEnabled = !!$.id('postForm'); - if (!QR.postingIsEnabled) { - return; + var captchaVersion, config, link, linkBot, navLinksBot, origToggle, prop; + captchaVersion = $('#g-recaptcha, #captcha-forced-noscript') ? 'v2' : 't'; + QR.captcha = Captcha[captchaVersion]; + QR.postingIsEnabled = true; + config = g.BOARD.config; + prop = function(key, def) { + var ref; + return +((ref = config[key]) != null ? ref : def); + }; + QR.min_width = prop('min_image_width', 1); + QR.min_height = prop('min_image_height', 1); + QR.max_width = QR.max_height = 10000; + QR.max_size = prop('max_filesize', 4194304); + QR.max_size_video = prop('max_webm_filesize', QR.max_size); + QR.max_comment = prop('max_comment_chars', 2000); + QR.max_width_video = QR.max_height_video = 2048; + QR.max_duration_video = prop('max_webm_duration', 120); + QR.forcedAnon = !!config.forced_anon; + QR.spoiler = !!config.spoilers; + if ((origToggle = $.id('togglePostFormLink'))) { + link = $.el('h1', { + className: "qr-link-container" + }); + $.extend(link, {innerHTML: "" + ((g.VIEW === "thread") ? "Reply to Thread" : "Start a Thread") + ""}); + QR.link = link.firstElementChild; + $.on(link.firstChild, 'click', function() { + QR.open(); + return QR.nodes.com.focus(); + }); + $.before(origToggle, link); + origToggle.firstElementChild.textContent = 'Original Form'; } - link = $.el('h1', { - className: "qr-link-container" - }); - $.extend(link, { - innerHTML: "" + ((g.VIEW === "thread") ? "Reply to Thread" : "Start a Thread") + "" - }); - QR.link = link.firstElementChild; - $.on(link.firstChild, 'click', function() { - QR.open(); - return QR.nodes.com.focus(); - }); if (g.VIEW === 'thread') { linkBot = $.el('div', { className: "brackets-wrap qr-link-container-bottom" }); - $.extend(linkBot, { - innerHTML: "Reply to Thread" - }); + $.extend(linkBot, {innerHTML: "Reply to Thread"}); $.on(linkBot.firstElementChild, 'click', function() { QR.open(); return QR.nodes.com.focus(); @@ -19320,16 +24027,14 @@ QR = (function() { $.prepend(navLinksBot, linkBot); } } - origToggle = $.id('togglePostFormLink'); - $.before(origToggle, link); - origToggle.firstElementChild.textContent = 'Original Form'; $.on(d, 'QRGetFile', QR.getFile); + $.on(d, 'QRDrawFile', QR.drawFile); $.on(d, 'QRSetFile', QR.setFile); $.on(d, 'paste', QR.paste); $.on(d, 'dragover', QR.dragOver); $.on(d, 'drop', QR.dropFile); $.on(d, 'dragstart dragend', QR.drag); - $.on(d, 'IndexRefresh', QR.generatePostableThreadsList); + $.on(d, 'IndexRefreshInternal', QR.generatePostableThreadsList); $.on(d, 'ThreadUpdate', QR.statusCheck); if (!Conf['Persistent QR']) { return; @@ -19345,7 +24050,7 @@ QR = (function() { return; } thread = QR.posts[0].thread; - if (thread !== 'new' && g.threads[g.BOARD + "." + thread].isDead) { + if (thread !== 'new' && g.threads.get(g.BOARD + "." + thread).isDead) { return QR.abort(); } else { return QR.status(); @@ -19368,8 +24073,8 @@ QR = (function() { } else { try { QR.dialog(); - } catch (_error) { - err = _error; + } catch (error) { + err = error; delete QR.nodes; Main.handleErrors({ message: 'Quick Reply dialog creation crashed.', @@ -19388,7 +24093,7 @@ QR = (function() { } QR.nodes.el.hidden = true; QR.cleanNotifications(); - d.activeElement.blur(); + QR.blur(); $.rmClass(QR.nodes.el, 'dump'); $.addClass(QR.shortcut, 'disabled'); new QR.post(true); @@ -19417,7 +24122,7 @@ QR = (function() { }); }, hide: function() { - d.activeElement.blur(); + QR.blur(); $.addClass(QR.nodes.el, 'autohide'); return QR.nodes.autohide.checked = true; }, @@ -19432,6 +24137,11 @@ QR = (function() { return QR.unhide(); } }, + blur: function() { + if (QR.nodes.el.contains(d.activeElement)) { + return d.activeElement.blur(); + } + }, toggleSJIS: function(e) { e.preventDefault(); Conf['sjisPreview'] = !Conf['sjisPreview']; @@ -19449,6 +24159,16 @@ QR = (function() { texPreviewHide: function() { return $.rmClass(QR.nodes.el, 'tex-preview'); }, + addPost: function() { + var wasOpen; + wasOpen = QR.nodes && !QR.nodes.el.hidden; + QR.open(); + if (wasOpen) { + $.addClass(QR.nodes.el, 'dump'); + new QR.post(true); + } + return QR.nodes.com.focus(); + }, setCustomCooldown: function(enabled) { Conf['customCooldownEnabled'] = enabled; QR.cooldown.customCooldown = enabled; @@ -19456,7 +24176,7 @@ QR = (function() { }, toggleCustomCooldown: function() { var enabled; - enabled = $.hasClass(this, 'disabled'); + enabled = $.hasClass(QR.nodes.customCooldown, 'disabled'); QR.setCustomCooldown(enabled); return $.set('customCooldownEnabled', enabled); }, @@ -19496,6 +24216,9 @@ QR = (function() { } } }, + connectionError: function() { + return $.el('span', {innerHTML: "Connection error while posting. [More info]"}); + }, notifications: [], cleanNotifications: function() { var j, len, notification, ref; @@ -19512,7 +24235,7 @@ QR = (function() { return; } thread = QR.posts[0].thread; - if (thread !== 'new' && g.threads[g.BOARD + "." + thread].isDead) { + if (thread !== 'new' && g.threads.get(g.BOARD + "." + thread).isDead) { value = 'Dead'; disabled = true; QR.cooldown.auto = false; @@ -19533,7 +24256,7 @@ QR = (function() { } }, quote: function(e) { - var ancestor, caretPos, com, frag, insideCode, j, k, l, len, len1, len2, len3, len4, len5, n, node, o, post, q, range, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, sel, text, thread; + var ancestor, base, caretPos, com, frag, i, insideCode, j, k, l, len, len1, len2, len3, n, node, o, post, postRange, range, ref, ref1, ref2, ref3, ref4, ref5, ref6, root, sel, text, thread, wasOnlyQuotes; if (e != null) { e.preventDefault(); } @@ -19542,81 +24265,146 @@ QR = (function() { } sel = d.getSelection(); post = Get.postFromNode(this); + root = post.nodes.root; + postRange = new Range(); + postRange.selectNode(root); text = post.board.ID === g.BOARD.ID ? ">>" + post + "\n" : ">>>/" + post.board + "/" + post + "\n"; - if (sel.toString().trim() && post === Get.postFromNode(sel.anchorNode)) { - range = sel.getRangeAt(0); - frag = range.cloneContents(); - ancestor = range.commonAncestorContainer; - if ($.x('ancestor-or-self::*[self::s or contains(@class,"removed-spoiler")]', ancestor)) { - $.prepend(frag, $.tn('[spoiler]')); - $.add(frag, $.tn('[/spoiler]')); - } - if (insideCode = $.x('ancestor-or-self::pre[contains(@class,"prettyprint")]', ancestor)) { - $.prepend(frag, $.tn('[code]')); - $.add(frag, $.tn('[/code]')); - } - ref = $$((insideCode ? 'br' : '.prettyprint br'), frag); - for (j = 0, len = ref.length; j < len; j++) { - node = ref[j]; - $.replace(node, $.tn('\n')); - } - ref1 = $$('br', frag); - for (k = 0, len1 = ref1.length; k < len1; k++) { - node = ref1[k]; - if (node !== frag.lastChild) { - $.replace(node, $.tn('\n>')); + for (i = j = 0, ref = sel.rangeCount; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) { + try { + range = sel.getRangeAt(i); + if (range.compareBoundaryPoints(Range.START_TO_START, postRange) < 0) { + range.setStartBefore(root); } - } - ref2 = $$('s, .removed-spoiler', frag); - for (l = 0, len2 = ref2.length; l < len2; l++) { - node = ref2[l]; - $.replace(node, [$.tn('[spoiler]')].concat(slice.call(node.childNodes), [$.tn('[/spoiler]')])); - } - ref3 = $$('.prettyprint', frag); - for (n = 0, len3 = ref3.length; n < len3; n++) { - node = ref3[n]; - $.replace(node, [$.tn('[code]')].concat(slice.call(node.childNodes), [$.tn('[/code]')])); - } - ref4 = $$('.linkify[data-original]', frag); - for (o = 0, len4 = ref4.length; o < len4; o++) { - node = ref4[o]; - $.replace(node, $.tn(node.dataset.original)); - } - ref5 = $$('.embedder', frag); - for (q = 0, len5 = ref5.length; q < len5; q++) { - node = ref5[q]; - if (((ref6 = node.previousSibling) != null ? ref6.nodeValue : void 0) === ' ') { - $.rm(node.previousSibling); + if (range.compareBoundaryPoints(Range.END_TO_END, postRange) > 0) { + range.setEndAfter(root); } - $.rm(node); - } - text += ">" + (frag.textContent.trim()) + "\n"; + if (!range.toString().trim()) { + continue; + } + frag = range.cloneContents(); + ancestor = range.commonAncestorContainer; + if ($.x('ancestor-or-self::*[self::s or contains(@class,"removed-spoiler")]', ancestor)) { + $.prepend(frag, $.tn('[spoiler]')); + $.add(frag, $.tn('[/spoiler]')); + } + if (insideCode = $.x('ancestor-or-self::pre[contains(@class,"prettyprint")]', ancestor)) { + $.prepend(frag, $.tn('[code]')); + $.add(frag, $.tn('[/code]')); + } + ref1 = $$((insideCode ? 'br' : '.prettyprint br'), frag); + for (k = 0, len = ref1.length; k < len; k++) { + node = ref1[k]; + $.replace(node, $.tn('\n')); + } + ref2 = $$('br', frag); + for (l = 0, len1 = ref2.length; l < len1; l++) { + node = ref2[l]; + if (node !== frag.lastChild) { + $.replace(node, $.tn('\n>')); + } + } + if (typeof (base = g.SITE).insertTags === "function") { + base.insertTags(frag); + } + ref3 = $$('.linkify[data-original]', frag); + for (n = 0, len2 = ref3.length; n < len2; n++) { + node = ref3[n]; + $.replace(node, $.tn(node.dataset.original)); + } + ref4 = $$('.embedder', frag); + for (o = 0, len3 = ref4.length; o < len3; o++) { + node = ref4[o]; + if (((ref5 = node.previousSibling) != null ? ref5.nodeValue : void 0) === ' ') { + $.rm(node.previousSibling); + } + $.rm(node); + } + text += ">" + (frag.textContent.trim()) + "\n"; + } catch (error) {} } QR.openPost(); - ref7 = QR.nodes, com = ref7.com, thread = ref7.thread; + ref6 = QR.nodes, com = ref6.com, thread = ref6.thread; if (!com.value) { thread.value = Get.threadFromNode(this); } + wasOnlyQuotes = QR.selected.isOnlyQuotes(); caretPos = com.selectionStart; com.value = com.value.slice(0, caretPos) + text + com.value.slice(com.selectionEnd); range = caretPos + text.length; com.setSelectionRange(range, range); com.focus(); + if (wasOnlyQuotes) { + QR.selected.quotedText = com.value; + } QR.selected.save(com); return QR.selected.save(thread); }, characterCount: function() { - var count, counter; + var count, counter, splitPost; counter = QR.nodes.charCount; count = QR.nodes.com.value.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '_').length; counter.textContent = count; counter.hidden = count < QR.max_comment / 2; - return (count > QR.max_comment ? $.addClass : $.rmClass)(counter, 'warning'); + (count > QR.max_comment ? $.addClass : $.rmClass)(counter, 'warning'); + splitPost = QR.nodes.splitPost; + return splitPost.hidden = count < QR.max_comment; + }, + splitPost: function() { + var count, currentLength, currentPost, j, lastPostLength, len, line, newComment, post, ref, text; + count = QR.nodes.com.value.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '_').length; + text = QR.nodes.com.value; + if (count < QR.max_comment || QR.selected.isLocked) { + return; + } + lastPostLength = 0; + QR.selected.setComment(""); + ref = text.split("\n"); + for (j = 0, len = ref.length; j < len; j++) { + line = ref[j]; + currentLength = line.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '_').length + 1; + if ((currentLength + lastPostLength) > QR.max_comment) { + post = new QR.post(true); + post.setComment(line); + lastPostLength = currentLength; + } else { + currentPost = QR.selected; + newComment = [currentPost.com, line].filter(function(el) { + return el !== null; + }).join("\n"); + currentPost.setComment(newComment); + lastPostLength += currentLength; + } + } + return QR.nodes.el.classList.add('dump'); }, getFile: function() { var ref; return $.event('QRFile', (ref = QR.selected) != null ? ref.file : void 0); }, + drawFile: function(e) { + var el, file, isVideo, ref; + file = (ref = QR.selected) != null ? ref.file : void 0; + if (!(file && /^(image|video)\//.test(file.type))) { + return; + } + isVideo = /^video\//.test(file); + el = $.el((isVideo ? 'video' : 'img')); + $.on(el, 'error', function() { + return QR.openError(); + }); + $.on(el, (isVideo ? 'loadeddata' : 'load'), function() { + e.target.getContext('2d').drawImage(el, 0, 0); + URL.revokeObjectURL(el.src); + return $.event('QRImageDrawn', null, e.target); + }); + return el.src = URL.createObjectURL(file); + }, + openError: function() { + var div; + div = $.el('div'); + $.extend(div, {innerHTML: "Could not open file. [More info]"}); + return QR.error(div); + }, setFile: function(e) { var file, name, ref, source; ref = e.detail, file = ref.file, name = ref.name, source = ref.source; @@ -19648,30 +24436,34 @@ QR = (function() { return QR.handleFiles(e.dataTransfer.files); }, paste: function(e) { - var blob, files, item, j, len, ref; + var blob, file, file2, item, j, len, ref, score, score2, type; if (!e.clipboardData.items) { return; } - files = []; + file = null; + score = -1; ref = e.clipboardData.items; for (j = 0, len = ref.length; j < len; j++) { item = ref[j]; - if (!(item.kind === 'file')) { + if (!(item.kind === 'file' && (file2 = item.getAsFile()))) { continue; } - blob = item.getAsFile(); - blob.name = 'file'; - if (blob.type) { - blob.name += '.' + blob.type.split('/')[1]; + score2 = 2 * (file2.size <= QR.max_size) + (file2.type === 'image/png'); + if (score2 > score) { + file = file2; + score = score2; } - files.push(blob); } - if (!files.length) { - return; + if (file) { + type = file.type; + blob = new Blob([file], { + type: type + }); + blob.name = Conf['pastedname'] + "." + ($.getOwn(QR.extensionFromType, type) || 'jpg'); + QR.open(); + QR.handleFiles([blob]); + $.addClass(QR.nodes.el, 'dump'); } - QR.open(); - QR.handleFiles(files); - return $.addClass(QR.nodes.el, 'dump'); }, pasteFF: function() { var arr, blob, bstr, i, images, img, j, k, len, m, pasteArea, ref, src; @@ -19693,7 +24485,7 @@ QR = (function() { blob = new Blob([arr], { type: m[1] }); - blob.name = "file." + m[2]; + blob.name = Conf['pastedname'] + "." + m[2]; QR.handleFiles([blob]); } else if (/^https?:\/\//.test(src)) { QR.handleUrl(src); @@ -19701,18 +24493,22 @@ QR = (function() { } }, handleUrl: function(urlDefault) { - var url; - url = prompt('Enter a URL:', urlDefault); - if (url === null) { - return; - } - QR.nodes.fileButton.focus(); - return CrossOrigin.file(url, function(blob) { - if (blob && !/^text\//.test(blob.type)) { - return QR.handleFiles([blob]); - } else { - return QR.error("Can't load file."); + QR.open(); + QR.selected.preventAutoPost(); + return CrossOrigin.permission(function() { + var url; + url = prompt('Enter a URL:', urlDefault); + if (url === null) { + return; } + QR.nodes.fileButton.focus(); + return CrossOrigin.file(url, function(blob) { + if (blob && !/^text\//.test(blob.type)) { + return QR.handleFiles([blob]); + } else { + return QR.error("Can't load file."); + } + }); }); }, handleFiles: function(files) { @@ -19782,11 +24578,9 @@ QR = (function() { return (g.VIEW === 'thread' ? $.addClass : $.rmClass)(QR.nodes.el, 'reply-to-thread'); }, dialog: function() { - var dialog, event, i, items, m, match_max, match_min, name, node, nodes, ref, rules, save, scriptData, setNode; + var classList, config, dialog, event, i, items, name, node, nodes, save, setNode; QR.nodes = nodes = { - el: dialog = UI.dialog('qr', 'top: 50px; right: 0px;', { - innerHTML: "
          ×
          No selected file
          " - }) + el: dialog = UI.dialog('qr', {innerHTML: "
          ×
          No selected file
          "}) }; setNode = function(name, query) { return nodes[name] = $(query, dialog); @@ -19803,6 +24597,7 @@ QR = (function() { setNode('sub', '[data-name=sub]'); setNode('com', '[data-name=com]'); setNode('charCount', '#char-count'); + setNode('splitPost', '#split-post'); setNode('texPreview', '#tex-preview'); setNode('dumpList', '#dump-list'); setNode('addPost', '#add-post'); @@ -19822,33 +24617,14 @@ QR = (function() { setNode('status', '[type=submit]'); setNode('flashTag', '[name=filetag]'); setNode('fileInput', '[type=file]'); - rules = $('ul.rules').textContent.trim(); - match_min = rules.match(/.+smaller than (\d+)x(\d+).+/); - match_max = rules.match(/.+greater than (\d+)x(\d+).+/); - QR.min_width = +(match_min != null ? match_min[1] : void 0) || 1; - QR.min_height = +(match_min != null ? match_min[2] : void 0) || 1; - QR.max_width = +(match_max != null ? match_max[1] : void 0) || 10000; - QR.max_height = +(match_max != null ? match_max[2] : void 0) || 10000; - scriptData = Get.scriptData(); - QR.max_size = (m = scriptData.match(/\bmaxFilesize *= *(\d+)\b/)) ? +m[1] : 4194304; - QR.max_size_video = (m = scriptData.match(/\bmaxWebmFilesize *= *(\d+)\b/)) ? +m[1] : QR.max_size; - QR.max_comment = (m = scriptData.match(/\bcomlen *= *(\d+)\b/)) ? +m[1] : 2000; - QR.max_width_video = QR.max_height_video = 2048; - QR.max_duration_video = (ref = g.BOARD.ID) === 'gif' || ref === 'wsg' ? 300 : 120; - if (Conf['Show New Thread Option in Threads']) { - $.addClass(QR.nodes.el, 'show-new-thread-option'); - } - QR.forcedAnon = !!$('form[name="post"] input[name="name"][type="hidden"]'); - if (QR.forcedAnon) { - $.addClass(QR.nodes.el, 'forced-anon'); - } - QR.spoiler = !!$('.postForm input[name=spoiler]'); - if (QR.spoiler) { - $.addClass(QR.nodes.el, 'has-spoiler'); - } - if (g.BOARD.ID === 'jp' && Conf['sjisPreview']) { - $.addClass(QR.nodes.el, 'sjis-preview'); - } + config = g.BOARD.config; + classList = QR.nodes.el.classList; + classList.toggle('forced-anon', QR.forcedAnon); + classList.toggle('has-spoiler', QR.spoiler); + classList.toggle('has-sjis', !!config.sjis_tags); + classList.toggle('has-math', !!config.math_tags); + classList.toggle('sjis-preview', !!config.sjis_tags && Conf['sjisPreview']); + classList.toggle('show-new-thread-option', Conf['Show New Thread Option in Threads']); if (parseInt(Conf['customCooldown'], 10) > 0) { $.addClass(QR.nodes.fileSubmit, 'custom-cooldown'); $.get('customCooldownEnabled', Conf['customCooldownEnabled'], function(arg) { @@ -19858,12 +24634,15 @@ QR = (function() { return $.sync('customCooldownEnabled', QR.setCustomCooldown); }); } + QR.flagsInput(); $.on(nodes.autohide, 'change', QR.toggleHide); $.on(nodes.close, 'click', QR.close); + $.on(nodes.status, 'click', QR.submit); $.on(nodes.form, 'submit', QR.submit); $.on(nodes.sjisToggle, 'click', QR.toggleSJIS); $.on(nodes.texButton, 'mousedown', QR.texPreviewShow); $.on(nodes.texButton, 'mouseup', QR.texPreviewHide); + $.on(nodes.splitPost, 'click', QR.splitPost); $.on(nodes.addPost, 'click', function() { return new QR.post(true); }); @@ -19894,13 +24673,13 @@ QR = (function() { window.addEventListener('focus', QR.focus, true); window.addEventListener('blur', QR.focus, true); $.on(d, 'click', QR.focus); - if ($.engine === 'gecko') { + if ($.engine === 'gecko' && !window.DataTransferItemList) { nodes.pasteArea.hidden = false; - new MutationObserver(QR.pasteFF).observe(nodes.pasteArea, { - childList: true - }); } - items = ['thread', 'name', 'email', 'sub', 'com', 'filename']; + new MutationObserver(QR.pasteFF).observe(nodes.pasteArea, { + childList: true + }); + items = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'flag']; i = 0; save = function() { return QR.selected.save(this); @@ -19934,35 +24713,80 @@ QR = (function() { QR.oekaki.setup(); return $.event('QRDialogCreation', null, dialog); }, + flags: function() { + var addFlag, ref, select, textContent, value; + select = $.el('select', { + name: 'flag', + className: 'flagSelector' + }); + addFlag = function(value, textContent) { + return $.add(select, $.el('option', { + value: value, + textContent: textContent + })); + }; + addFlag('0', (g.BOARD.config.country_flags ? 'Geographic Location' : 'None')); + ref = g.BOARD.config.board_flags; + for (value in ref) { + textContent = ref[value]; + addFlag(value, textContent); + } + return select; + }, + flagsInput: function() { + var flag, nodes; + nodes = QR.nodes; + if (!nodes) { + return; + } + if (nodes.flag) { + $.rm(nodes.flag); + delete nodes.flag; + } + if (g.BOARD.config.board_flags) { + flag = QR.flags(); + flag.dataset.name = 'flag'; + flag.dataset["default"] = '0'; + nodes.flag = flag; + return $.add(nodes.form, flag); + } + }, submit: function(e) { - var captcha, cb, err, extra, filetag, formData, options, post, ref, textOnly, thread, threadID; + var captcha, cb, err, filetag, force, formData, options, post, ref, thread, threadID; if (e != null) { e.preventDefault(); } + force = e != null ? e.shiftKey : void 0; if (QR.req) { QR.abort(); return; } + $.forceSync('cooldowns'); if (QR.cooldown.seconds) { - QR.cooldown.auto = !QR.cooldown.auto; - QR.status(); - return; + if (force) { + QR.cooldown.clear(); + } else { + QR.cooldown.auto = !QR.cooldown.auto; + QR.status(); + return; + } } post = QR.posts[0]; + delete post.quotedText; post.forceSave(); threadID = post.thread; - thread = g.BOARD.threads[threadID]; + thread = g.BOARD.threads.get(threadID); if (g.BOARD.ID === 'f' && threadID === 'new') { filetag = QR.nodes.flashTag.value; } if (threadID === 'new') { threadID = null; - if (g.BOARD.ID === 'vg' && !post.sub) { + if (!!g.BOARD.config.require_subject && !post.sub) { err = 'New threads require a subject.'; - } else if (!($.hasClass(d.body, 'text_only') || post.file || (textOnly = !!$('input[name=textonly]', $.id('postForm'))))) { + } else if (!(!!g.BOARD.config.text_only || post.file)) { err = 'No file selected.'; } - } else if (g.BOARD.threads[threadID].isClosed) { + } else if (g.BOARD.threads.get(threadID).isClosed) { err = 'You can\'t reply to this thread anymore.'; } else if (!(post.com || post.file)) { err = 'No comment or file.'; @@ -19972,29 +24796,29 @@ QR = (function() { if (g.BOARD.ID === 'r9k' && !((ref = post.com) != null ? ref.match(/[a-z-]/i) : void 0)) { err || (err = 'Original comment required.'); } - if (QR.captcha.isEnabled && !err) { - captcha = QR.captcha.getOne(); + if (QR.captcha.isEnabled && !(QR.captcha === Captcha.v2 && /\b_ct=/.test(d.cookie) && threadID) && !(err && !force)) { + captcha = QR.captcha.getOne(!!threadID); + if (QR.captcha === Captcha.v2) { + captcha || (captcha = Captcha.cache.request(!!threadID)); + } if (!captcha) { err = 'No valid captcha.'; QR.captcha.setup(!QR.cooldown.auto || d.activeElement === QR.nodes.status); } } QR.cleanNotifications(); - if (err) { + if (err && !force) { QR.cooldown.auto = false; QR.status(); QR.error(err); return; } QR.cooldown.auto = QR.posts.length > 1; - if (Conf['Auto Hide QR'] && !QR.cooldown.auto) { - QR.hide(); - } - if (!QR.cooldown.auto && $.x('ancestor::div[@id="qr"]', d.activeElement)) { - d.activeElement.blur(); - } post.lock(); formData = { + MAX_FILE_SIZE: QR.max_size, + mode: 'regist', + pwd: QR.persona.getPassword(), resto: threadID, name: !QR.forcedAnon ? post.name : void 0, email: post.email, @@ -20003,66 +24827,74 @@ QR = (function() { upfile: post.file, filetag: filetag, spoiler: post.spoiler, - textonly: textOnly, - mode: 'regist', - pwd: QR.persona.getPassword() + flag: post.flag }; options = { responseType: 'document', withCredentials: true, - onload: QR.response, - onerror: function() { - delete QR.req; - post.unlock(); - QR.cooldown.auto = false; - QR.status(); - return QR.error($.el('span', { - innerHTML: "Connection error while posting. [More info]" - })); - } - }; - extra = { + onloadend: QR.response, form: $.formData(formData) }; if (Conf['Show Upload Progress']) { - extra.upCallbacks = { - onload: function() { + options.onprogress = function(e) { + var ref1; + if (this !== ((ref1 = QR.req) != null ? ref1.upload : void 0)) { + return; + } + if (e.loaded < e.total) { + QR.req.progress = (Math.round(e.loaded / e.total * 100)) + "%"; + } else { QR.req.isUploadFinished = true; QR.req.progress = '...'; - return QR.status(); - }, - onprogress: function(e) { - QR.req.progress = (Math.round(e.loaded / e.total * 100)) + "%"; - return QR.status(); } + return QR.status(); }; } cb = function(response) { + var key, val; if (response != null) { - if (response.challenge != null) { - extra.form.append('recaptcha_challenge_field', response.challenge); - extra.form.append('recaptcha_response_field', response.response); + QR.currentCaptcha = response; + if (QR.captcha === Captcha.v2) { + if (response.challenge != null) { + options.form.append('recaptcha_challenge_field', response.challenge); + options.form.append('recaptcha_response_field', response.response); + } else { + options.form.append('g-recaptcha-response', response.response); + } } else { - extra.form.append('g-recaptcha-response', response.response); + for (key in response) { + val = response[key]; + options.form.append(key, val); + } } } - QR.req = $.ajax("https://sys.4chan.org/" + g.BOARD + "/post", options, extra); + QR.req = $.ajax("https://sys." + (location.hostname.split('.')[1]) + ".org/" + g.BOARD + "/post", options); return QR.req.progress = '...'; }; if (typeof captcha === 'function') { QR.req = { progress: '...', abort: function() { + if (QR.captcha === Captcha.v2) { + Captcha.cache.abort(); + } return cb = null; } }; captcha(function(response) { - if (response) { + if (QR.captcha === Captcha.v2 && Captcha.cache.haveCookie()) { + if (typeof cb === "function") { + cb(); + } + if (response) { + return Captcha.cache.save(response); + } + } else if (response) { return typeof cb === "function" ? cb(response) : void 0; } else { delete QR.req; post.unlock(); - QR.cooldown.auto = !!QR.captcha.captchas.length; + QR.cooldown.auto = !!Captcha.cache.getCount(); return QR.status(); } }); @@ -20072,39 +24904,52 @@ QR = (function() { return QR.status(); }, response: function() { - var _, ban, err, h1, isReply, lastPostToThread, m, post, postID, postsCount, ref, ref1, ref2, req, resDoc, seconds, threadID, url; - req = QR.req; + var URL, _, base, connErr, err, h1, isReply, j, lastPostToThread, len, m, mi, open, post, postID, postsCount, ref, ref1, ref2, ref3, seconds, threadID; + if (this !== QR.req) { + return; + } delete QR.req; post = QR.posts[0]; post.unlock(); - resDoc = req.response; - if (ban = $('.banType', resDoc)) { - err = $.el('span', ban.textContent.toLowerCase() === 'banned' ? { - innerHTML: "You are banned on " + ($(".board", resDoc)).innerHTML + "! ;_;
          Click here to see the reason." - } : { - innerHTML: "You were issued a warning on " + ($(".board", resDoc)).innerHTML + " as " + ($(".nameBlock", resDoc)).innerHTML + ".
          Reason: " + ($(".reason", resDoc)).innerHTML - }); - } else if (err = resDoc.getElementById('errmsg')) { - if ((ref = $('a', err)) != null) { - ref.target = '_blank'; + if ((err = (ref = this.response) != null ? ref.getElementById('errmsg') : void 0)) { + if ((ref1 = $('a', err)) != null) { + ref1.target = '_blank'; + } + } else if ((connErr = !this.response || this.response.title !== 'Post successful!')) { + err = QR.connectionError(); + if (QR.captcha === Captcha.v2 && QR.currentCaptcha) { + Captcha.cache.save(QR.currentCaptcha); + } + } else if (this.status !== 200) { + err = "Error " + this.statusText + " (" + this.status + ")"; + } + if (!connErr) { + if (typeof (base = QR.captcha).setUsed === "function") { + base.setUsed(); } - } else if (resDoc.title !== 'Post successful!') { - err = 'Connection error with sys.4chan.org.'; - } else if (req.status !== 200) { - err = "Error " + req.statusText + " (" + req.status + ")"; } + delete QR.currentCaptcha; if (err) { - if (/captcha|verification/i.test(err.textContent) || err === 'Connection error with sys.4chan.org.') { + QR.errorCount = (QR.errorCount || 0) + 1; + if (/captcha|verification/i.test(err.textContent) || connErr) { if (/mistyped/i.test(err.textContent)) { err = 'You mistyped the CAPTCHA, or the CAPTCHA malfunctioned.'; } else if (/expired/i.test(err.textContent)) { err = 'This CAPTCHA is no longer valid because it has expired.'; } - QR.cooldown.auto = QR.captcha.isEnabled || err === 'Connection error with sys.4chan.org.'; - QR.cooldown.addDelay(post, 2); - } else if (err.textContent && (m = err.textContent.match(/(?:(\d+)\s+minutes?\s+)?(\d+)\s+second/i)) && !/duplicate|hour/i.test(err.textContent)) { + if (QR.errorCount >= 5) { + QR.cooldown.auto = false; + } else { + QR.cooldown.auto = QR.captcha.isEnabled || connErr; + QR.cooldown.addDelay(post, 2); + } + } else if (err.textContent && (m = err.textContent.match(/\d+\s+(?:minute|second)/gi)) && !/duplicate|hour/i.test(err.textContent)) { QR.cooldown.auto = !/have\s+been\s+muted/i.test(err.textContent); - seconds = 60 * (+(m[1] || 0)) + (+m[2]); + seconds = 0; + for (j = 0, len = m.length; j < len; j++) { + mi = m[j]; + seconds += (/minute/i.test(mi) ? 60 : 1) * (+mi.match(/\d+/)[0]); + } if (/muted/i.test(err.textContent)) { QR.cooldown.addMute(seconds); } else { @@ -20113,16 +24958,14 @@ QR = (function() { } else { QR.cooldown.auto = false; } - QR.captcha.setup(QR.cooldown.auto && ((ref1 = d.activeElement) === QR.nodes.status || ref1 === d.body)); - if (QR.captcha.isEnabled && !QR.captcha.captchas.length) { - QR.cooldown.auto = false; - } + QR.captcha.setup(QR.cooldown.auto && ((ref2 = d.activeElement) === QR.nodes.status || ref2 === d.body)); QR.status(); QR.error(err); return; } - h1 = $('h1', resDoc); - ref2 = h1.nextSibling.textContent.match(/thread:(\d+),no:(\d+)/), _ = ref2[0], threadID = ref2[1], postID = ref2[2]; + delete QR.errorCount; + h1 = $('h1', this.response); + ref3 = h1.nextSibling.textContent.match(/thread:(\d+),no:(\d+)/), _ = ref3[0], threadID = ref3[1], postID = ref3[2]; postID = +postID; threadID = +threadID || postID; isReply = threadID !== postID; @@ -20139,37 +24982,49 @@ QR = (function() { postsCount = QR.posts.length - 1; QR.cooldown.auto = postsCount && isReply; lastPostToThread = !((function() { - var j, len, p, ref3; - ref3 = QR.posts.slice(1); - for (j = 0, len = ref3.length; j < len; j++) { - p = ref3[j]; + var k, len1, p, ref4; + ref4 = QR.posts.slice(1); + for (k = 0, len1 = ref4.length; k < len1; k++) { + p = ref4[k]; if (p.thread === post.thread) { return true; } } })()); - if (!(Conf['Persistent QR'] || postsCount)) { - QR.close(); - } else { + if (postsCount) { post.rm(); QR.captcha.setup(d.activeElement === QR.nodes.status); + } else if (Conf['Persistent QR']) { + post.rm(); + if (Conf['Auto Hide QR']) { + QR.hide(); + } else { + QR.blur(); + } + } else { + QR.close(); } QR.cleanNotifications(); if (Conf['Posting Success Notifications']) { QR.notifications.push(new Notice('success', h1.textContent, 5)); } QR.cooldown.add(threadID, postID); - url = threadID === postID ? window.location.origin + "/" + g.BOARD + "/thread/" + threadID : threadID !== g.THREADID && lastPostToThread ? window.location.origin + "/" + g.BOARD + "/thread/" + threadID + "#p" + postID : void 0; - if (url) { + URL = threadID === postID ? window.location.origin + "/" + g.BOARD + "/thread/" + threadID : threadID !== g.THREADID && lastPostToThread && Conf['Open Post in New Tab'] ? window.location.origin + "/" + g.BOARD + "/thread/" + threadID + "#p" + postID : void 0; + if (URL) { + open = Conf['Open Post in New Tab'] || postsCount ? function() { + return $.open(URL); + } : function() { + return location.href = URL; + }; if (threadID === postID) { - QR.waitForThread(url); + QR.waitForThread(URL, open); } else { - $.open(url); + open(); } } return QR.status(); }, - waitForThread: function(url) { + waitForThread: function(url, cb) { var attempts, check; attempts = 0; check = function() { @@ -20177,21 +25032,26 @@ QR = (function() { onloadend: function() { attempts++; if (attempts >= 6 || this.status === 200) { - return $.open(url); + return cb(); } else { return setTimeout(check, attempts * $.SECOND); } - } - }, { + }, + responseType: 'text', type: 'HEAD' }); }; return check(); }, abort: function() { - if (QR.req && !QR.req.isUploadFinished) { - QR.req.abort(); + var oldReq; + if ((oldReq = QR.req) && !QR.req.isUploadFinished) { delete QR.req; + oldReq.abort(); + if (QR.captcha === Captcha.v2 && QR.currentCaptcha) { + Captcha.cache.save(QR.currentCaptcha); + } + delete QR.currentCaptcha; QR.posts[0].unlock(); QR.cooldown.auto = false; QR.notifications.push(new Notice('info', 'QR upload aborted.', 5)); @@ -20208,26 +25068,19 @@ QR = (function() { QR.cooldown = { seconds: 0, delays: { - thread: 0, - reply: 0, - image: 0, - reply_intra: 0, - image_intra: 0, - deletion: 60, - thread_global: 300 + deletion: 60 }, init: function() { if (!Conf['Quick Reply']) { return; } this.data = Conf['cooldowns']; + this.changes = $.dict(); return $.sync('cooldowns', this.sync); }, setup: function() { - var delay, m, ref, type; - if (m = Get.scriptData().match(/\bcooldowns *= *({[^}]+})/)) { - $.extend(QR.cooldown.delays, JSON.parse(m[1])); - } + var delay, ref, type; + $.extend(QR.cooldown.delays, g.BOARD.cooldowns()); QR.cooldown.maxDelay = 0; ref = QR.cooldown.delays; for (type in ref) { @@ -20249,7 +25102,7 @@ QR = (function() { return QR.cooldown.count(); }, sync: function(data) { - QR.cooldown.data = data || {}; + QR.cooldown.data = data || $.dict(); return QR.cooldown.start(); }, add: function(threadID, postID) { @@ -20270,6 +25123,7 @@ QR = (function() { postID: postID }); } + QR.cooldown.save(); return QR.cooldown.start(); }, addDelay: function(post, delay) { @@ -20280,6 +25134,7 @@ QR = (function() { cooldown = QR.cooldown.categorize(post); cooldown.delay = delay; QR.cooldown.set(g.BOARD.ID, Date.now(), cooldown); + QR.cooldown.save(); return QR.cooldown.start(); }, addMute: function(delay) { @@ -20290,6 +25145,7 @@ QR = (function() { type: 'mute', delay: delay }); + QR.cooldown.save(); return QR.cooldown.start(); }, "delete": function(post) { @@ -20297,22 +25153,21 @@ QR = (function() { if (!QR.cooldown.data) { return; } - $.forceSync('cooldowns'); - cooldowns = ((base = QR.cooldown.data)[name = post.board.ID] || (base[name] = {})); + cooldowns = ((base = QR.cooldown.data)[name = post.board.ID] || (base[name] = $.dict())); for (id in cooldowns) { cooldown = cooldowns[id]; if ((cooldown.delay == null) && cooldown.threadID === post.thread.ID && cooldown.postID === post.ID) { - delete cooldowns[id]; + QR.cooldown.set(post.board.ID, id, null); } } - return QR.cooldown.save([post.board.ID]); + return QR.cooldown.save(); }, secondsDeletion: function(post) { var cooldown, cooldowns, seconds, start; if (!(QR.cooldown.data && Conf['Cooldown'])) { return 0; } - cooldowns = QR.cooldown.data[post.board.ID] || {}; + cooldowns = QR.cooldown.data[post.board.ID] || $.dict(); for (start in cooldowns) { cooldown = cooldowns[start]; if ((cooldown.delay == null) && cooldown.threadID === post.thread.ID && cooldown.postID === post.ID) { @@ -20334,28 +25189,56 @@ QR = (function() { }; } }, + mergeChange: function(data, scope, id, value) { + if (value) { + return (data[scope] || (data[scope] = $.dict()))[id] = value; + } else if (scope in data) { + delete data[scope][id]; + if (Object.keys(data[scope]).length === 0) { + return delete data[scope]; + } + } + }, set: function(scope, id, value) { - var base, cooldowns; - $.forceSync('cooldowns'); - cooldowns = ((base = QR.cooldown.data)[scope] || (base[scope] = {})); - cooldowns[id] = value; - return $.set('cooldowns', QR.cooldown.data); + var base; + QR.cooldown.mergeChange(QR.cooldown.data, scope, id, value); + return ((base = QR.cooldown.changes)[scope] || (base[scope] = $.dict()))[id] = value; }, - save: function(scopes) { - var data, i, len, scope; - data = QR.cooldown.data; - for (i = 0, len = scopes.length; i < len; i++) { - scope = scopes[i]; - if (scope in data && !Object.keys(data[scope]).length) { - delete data[scope]; - } + save: function() { + var changes; + changes = QR.cooldown.changes; + if (!Object.keys(changes).length) { + return; } - return $.set('cooldowns', data); + return $.get('cooldowns', $.dict(), function(arg) { + var cooldowns, id, ref, scope, value; + cooldowns = arg.cooldowns; + for (scope in QR.cooldown.changes) { + ref = QR.cooldown.changes[scope]; + for (id in ref) { + value = ref[id]; + QR.cooldown.mergeChange(cooldowns, scope, id, value); + } + QR.cooldown.data = cooldowns; + } + return $.set('cooldowns', cooldowns, function() { + return QR.cooldown.changes = $.dict(); + }); + }); }, - count: function() { + clear: function() { + QR.cooldown.data = $.dict(); + QR.cooldown.changes = $.dict(); + QR.cooldown.auto = false; + QR.cooldown.update(); + return $.queueTask($["delete"], 'cooldowns'); + }, + update: function() { var base, cooldown, cooldowns, elapsed, i, len, maxDelay, nCooldowns, now, ref, ref1, save, scope, seconds, start, suffix, threadID, type, update; - $.forceSync('cooldowns'); - save = []; + if (!QR.cooldown.isCounting) { + return; + } + save = false; nCooldowns = 0; now = Date.now(); ref = QR.cooldown.categorize(QR.posts[0]), type = ref.type, threadID = ref.threadID; @@ -20364,20 +25247,20 @@ QR = (function() { ref1 = [g.BOARD.ID, 'global']; for (i = 0, len = ref1.length; i < len; i++) { scope = ref1[i]; - cooldowns = ((base = QR.cooldown.data)[scope] || (base[scope] = {})); + cooldowns = ((base = QR.cooldown.data)[scope] || (base[scope] = $.dict())); for (start in cooldowns) { cooldown = cooldowns[start]; start = +start; elapsed = Math.floor((now - start) / $.SECOND); if (elapsed < 0) { - delete cooldowns[start]; - save.push(scope); + QR.cooldown.set(scope, start, null); + save = true; continue; } if (cooldown.delay != null) { if (cooldown.delay <= elapsed) { - delete cooldowns[start]; - save.push(scope); + QR.cooldown.set(scope, start, null); + save = true; } else if ((cooldown.type === type && cooldown.threadID === threadID) || cooldown.type === 'mute') { seconds = Math.max(seconds, cooldown.delay - elapsed); } @@ -20388,23 +25271,23 @@ QR = (function() { maxDelay = Math.max(maxDelay, parseInt(Conf['customCooldown'], 10)); } if (maxDelay <= elapsed) { - delete cooldowns[start]; - save.push(scope); + QR.cooldown.set(scope, start, null); + save = true; continue; } if ((type === 'thread') === (cooldown.threadID === cooldown.postID) && cooldown.boardID !== g.BOARD.ID) { - suffix = scope === 'global' ? '_global' : type !== 'thread' && threadID === cooldown.threadID ? '_intra' : ''; + suffix = scope === 'global' ? '_global' : ''; seconds = Math.max(seconds, QR.cooldown.delays[type + suffix] - elapsed); - } - if (QR.cooldown.customCooldown) { - seconds = Math.max(seconds, parseInt(Conf['customCooldown'], 10) - elapsed); + if (QR.cooldown.customCooldown) { + seconds = Math.max(seconds, parseInt(Conf['customCooldown'], 10) - elapsed); + } } } nCooldowns += Object.keys(cooldowns).length; } } - if (save.length) { - QR.cooldown.save(save); + if (save) { + QR.cooldown.save; } if (nCooldowns) { clearTimeout(QR.cooldown.timeout); @@ -20415,9 +25298,12 @@ QR = (function() { update = seconds !== QR.cooldown.seconds; QR.cooldown.seconds = seconds; if (update) { - QR.status(); + return QR.status(); } - if (seconds === 0 && QR.cooldown.auto && !QR.req) { + }, + count: function() { + QR.cooldown.update(); + if (QR.cooldown.seconds === 0 && QR.cooldown.auto && !QR.req) { return QR.submit(); } } @@ -20441,7 +25327,7 @@ QR = (function() { $.on(a, 'click', this.editFile); return Menu.menu.addEntry({ el: a, - order: 95, + order: 90, open: function(post) { var file; QR.oekaki.menu.post = post; @@ -20478,6 +25364,9 @@ QR = (function() { }); return video.currentTime = currentTime; }); + $.on(video, 'error', function() { + return QR.openError(); + }); return video.src = URL.createObjectURL(blob); } else { blob.name = post.file.name; @@ -20513,15 +25402,15 @@ QR = (function() { }, load: function(cb) { var n, onload, script, style; - if ($('script[src^="//s.4cdn.org/js/painter"]', d.head)) { + if ($('script[src^="//s.4cdn.org/js/tegaki"]', d.head)) { return cb(); } else { style = $.el('link', { rel: 'stylesheet', - href: "//s.4cdn.org/css/painter." + (Date.now()) + ".css" + href: "//s.4cdn.org/css/tegaki." + (Date.now()) + ".css" }); script = $.el('script', { - src: "//s.4cdn.org/js/painter.min." + (Date.now()) + ".js" + src: "//s.4cdn.org/js/tegaki.min." + (Date.now()) + ".js" }); n = 0; onload = function() { @@ -20578,45 +25467,55 @@ QR = (function() { })); }; cb = function(e) { - var file, isVideo; - document.removeEventListener('QRFile', cb, false); - if (!e.detail) { + var canvas, selected; + if (e) { + this.removeEventListener('QRMetadata', cb, false); + } + selected = document.getElementById('selected'); + if (!(selected != null ? selected.dataset.type : void 0)) { return error('No file to edit.'); } - if (!/^(image|video)\//.test(e.detail.type)) { + if (!/^(image|video)\//.test(selected.dataset.type)) { return error('Not an image.'); } - isVideo = /^video\//.test(e.detail.type); - file = document.createElement(isVideo ? 'video' : 'img'); - file.addEventListener('error', function() { - return error('Could not open file.', false); + if (!selected.dataset.height) { + return error('Metadata not available.'); + } + if (selected.dataset.height === 'loading') { + selected.addEventListener('QRMetadata', cb, false); + return; + } + if (Tegaki.bg) { + Tegaki.destroy(); + } + FCX.oekakiName = name; + Tegaki.open({ + onDone: FCX.oekakiCB, + onCancel: function() { + return Tegaki.bgColor = '#ffffff'; + }, + width: +selected.dataset.width, + height: +selected.dataset.height, + bgColor: 'transparent' }); - file.addEventListener((isVideo ? 'loadeddata' : 'load'), function() { - if (Tegaki.bg) { - Tegaki.destroy(); - } - FCX.oekakiName = name; - Tegaki.open({ - onDone: FCX.oekakiCB, - onCancel: function() { - return Tegaki.bgColor = '#ffffff'; - }, - width: file.naturalWidth || file.videoWidth, - height: file.naturalHeight || file.videoHeight, - bgColor: 'transparent' - }); - return Tegaki.activeCtx.drawImage(file, 0, 0); + canvas = document.createElement('canvas'); + canvas.width = canvas.naturalWidth = +selected.dataset.width; + canvas.height = canvas.naturalHeight = +selected.dataset.height; + canvas.hidden = true; + document.body.appendChild(canvas); + canvas.addEventListener('QRImageDrawn', function() { + this.remove(); + return Tegaki.onOpenImageLoaded.call(this); }, false); - return file.src = URL.createObjectURL(e.detail); + return canvas.dispatchEvent(new CustomEvent('QRDrawFile', { + bubbles: true + })); }; if (Tegaki.bg && Tegaki.onDoneCb === FCX.oekakiCB && source === FCX.oekakiLatest) { FCX.oekakiName = name; return Tegaki.resume(); } else { - document.addEventListener('QRFile', cb, false); - return document.dispatchEvent(new CustomEvent('QRGetFile', { - bubbles: true - })); + return cb(); } }); }); @@ -20720,7 +25619,8 @@ QR = (function() { var persona; persona = arg['QR.persona']; persona = { - name: post.name + name: post.name, + flag: post.flag }; return $.set('QR.persona', persona); }); @@ -20743,9 +25643,7 @@ QR = (function() { draggable: true, href: 'javascript:;' }); - $.extend(el, { - innerHTML: "" - }); + $.extend(el, {innerHTML: ""}); this.nodes = { el: el, rm: el.firstChild, @@ -20763,8 +25661,9 @@ QR = (function() { return function(e) { _this.spoiler = e.target.checked; if (_this === QR.selected) { - return QR.nodes.spoiler.checked = _this.spoiler; + QR.nodes.spoiler.checked = _this.spoiler; } + return _this.preventAutoPost(); }; })(this)); ref = $$('label', el); @@ -20789,6 +25688,9 @@ QR = (function() { _this.name = 'name' in QR.persona.always ? QR.persona.always.name : prev ? prev.name : persona.name; _this.email = 'email' in QR.persona.always ? QR.persona.always.email : ''; _this.sub = 'sub' in QR.persona.always ? QR.persona.always.sub : ''; + if (QR.nodes.flag) { + _this.flag = prev ? prev.flag : persona.flag && persona.flag in g.BOARD.config.board_flags ? persona.flag : void 0; + } if (QR.selected === _this) { return _this.load(); } @@ -20798,13 +25700,11 @@ QR = (function() { this.select(); } this.unlock(); - $.queueTask(function() { - return QR.captcha.onNewPost(); - }); + QR.captcha.moreNeeded(); } _Class.prototype.rm = function() { - var index; + var base, index; this["delete"](); index = QR.posts.indexOf(this); if (QR.posts.length === 1) { @@ -20814,7 +25714,8 @@ QR = (function() { (QR.posts[index - 1] || QR.posts[index + 1]).select(); } QR.posts.splice(index, 1); - return QR.status(); + QR.status(); + return typeof (base = QR.captcha).updateThread === "function" ? base.updateThread() : void 0; }; _Class.prototype["delete"] = function() { @@ -20832,7 +25733,7 @@ QR = (function() { if (this !== QR.selected) { return; } - ref = ['thread', 'name', 'email', 'sub', 'com', 'fileButton', 'filename', 'spoiler']; + ref = ['thread', 'name', 'email', 'sub', 'com', 'fileButton', 'filename', 'spoiler', 'flag']; for (i = 0, len = ref.length; i < len; i++) { name = ref[i]; if (node = QR.nodes[name]) { @@ -20865,7 +25766,7 @@ QR = (function() { _Class.prototype.load = function() { var i, len, name, node, ref; - ref = ['thread', 'name', 'email', 'sub', 'com', 'filename']; + ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'flag']; for (i = 0, len = ref.length; i < len; i++) { name = ref[i]; if (!(node = QR.nodes[name])) { @@ -20878,32 +25779,44 @@ QR = (function() { return QR.characterCount(); }; - _Class.prototype.save = function(input) { - var name, ref; + _Class.prototype.save = function(input, forced) { + var base, name, prev; if (input.type === 'checkbox') { this.spoiler = input.checked; return; } name = input.dataset.name; + if (name !== 'thread' && name !== 'name' && name !== 'email' && name !== 'sub' && name !== 'com' && name !== 'filename' && name !== 'flag') { + return; + } + prev = this[name] || input.dataset["default"] || null; this[name] = input.value || input.dataset["default"] || null; switch (name) { case 'thread': (this.thread !== 'new' ? $.addClass : $.rmClass)(QR.nodes.el, 'reply-to-thread'); - return QR.status(); + QR.status(); + if (typeof (base = QR.captcha).updateThread === "function") { + base.updateThread(); + } + break; case 'com': this.updateComment(); - if (QR.cooldown.auto && this === QR.posts[0] && (0 < (ref = QR.cooldown.seconds) && ref <= 5)) { - return QR.cooldown.auto = false; - } break; case 'filename': if (!this.file) { return; } this.saveFilename(); - return this.updateFilename(); + this.updateFilename(); + break; case 'name': - return QR.persona.set(this); + case 'flag': + if (this[name] !== prev) { + QR.persona.set(this); + } + } + if (!forced) { + return this.preventAutoPost(); } }; @@ -20912,13 +25825,22 @@ QR = (function() { if (this !== QR.selected) { return; } - ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler']; + ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler', 'flag']; for (i = 0, len = ref.length; i < len; i++) { name = ref[i]; if (!(node = QR.nodes[name])) { continue; } - this.save(node); + this.save(node, true); + } + }; + + _Class.prototype.preventAutoPost = function() { + if (QR.cooldown.auto && this === QR.posts[0]) { + QR.cooldown.update(); + if (QR.cooldown.seconds <= 5) { + return QR.cooldown.auto = false; + } } }; @@ -20935,9 +25857,14 @@ QR = (function() { QR.characterCount(); } this.nodes.span.textContent = this.com; - return $.queueTask(function() { - return QR.captcha.onPostChange(); - }); + QR.captcha.moreNeeded(); + if (QR.captcha === Captcha.v2) { + return Captcha.cache.prerequest(); + } + }; + + _Class.prototype.isOnlyQuotes = function() { + return (this.com || '').trim() === (this.quotedText || '').trim(); }; _Class.rmErrored = function(e) { @@ -20959,14 +25886,12 @@ QR = (function() { } }; - _Class.prototype.error = function(className, message) { + _Class.prototype.error = function(className, message, link) { var div, ref, rm, rmAll; div = $.el('div', { className: className }); - $.extend(div, { - innerHTML: E(message) + "
          [delete] [delete all]" - }); + $.extend(div, {innerHTML: E(message) + ((link) ? " [More info]" : "") + "
          [delete post] [delete all]"}); (this.errors || (this.errors = [])).push(div); ref = $$('a', div), rm = ref[0], rmAll = ref[1]; $.on(div, 'click', (function(_this) { @@ -20988,8 +25913,8 @@ QR = (function() { return QR.error(div, true); }; - _Class.prototype.fileError = function(message) { - return this.error('file-error', this.filename + ": " + message); + _Class.prototype.fileError = function(message, link) { + return this.error('file-error', this.filename + ": " + message, link); }; _Class.prototype.dismissErrors = function(test) { @@ -21014,7 +25939,7 @@ QR = (function() { var ext, ref; this.file = file1; if (Conf['Randomize Filename'] && g.BOARD.ID !== 'f') { - this.filename = "" + (Date.now() - Math.floor(Math.random() * 365 * $.DAY)); + this.filename = "" + (Date.now() * 1000 - Math.floor(Math.random() * 365 * $.DAY * 1000)); if (ext = this.file.name.match(QR.validExtension)) { this.filename += ext[0]; } @@ -21024,9 +25949,7 @@ QR = (function() { this.filesize = $.bytesToString(this.file.size); this.checkSize(); $.addClass(this.nodes.el, 'has-file'); - $.queueTask(function() { - return QR.captcha.onPostChange(); - }); + QR.captcha.moreNeeded(); URL.revokeObjectURL(this.URL); this.saveFilename(); if (this === QR.selected) { @@ -21034,12 +25957,15 @@ QR = (function() { } else { this.updateFilename(); } - this.nodes.el.style.backgroundImage = null; + this.rmMetadata(); + this.nodes.el.dataset.type = this.file.type; + this.nodes.el.style.backgroundImage = ''; if (ref = this.file.type, indexOf.call(QR.mimeTypes, ref) < 0) { - return this.fileError('Unsupported file type.'); + this.fileError('Unsupported file type.'); } else if (/^(image|video)\//.test(this.file.type)) { - return this.readFile(); + this.readFile(); } + return this.preventAutoPost(); }; _Class.prototype.checkSize = function() { @@ -21066,26 +25992,32 @@ QR = (function() { $.off(el, event, onload); $.off(el, 'error', onerror); _this.checkDimensions(el); - return _this.setThumbnail(el); + _this.setThumbnail(el); + return $.event('QRMetadata', null, _this.nodes.el); }; })(this); onerror = (function(_this) { return function() { $.off(el, event, onload); $.off(el, 'error', onerror); - _this.fileError((isVideo ? 'Video' : 'Image') + " appears corrupt"); - return URL.revokeObjectURL(el.src); + _this.fileError("Corrupt " + (isVideo ? 'video' : 'image') + " or error reading metadata.", 'https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions#error-reading-metadata'); + URL.revokeObjectURL(el.src); + _this.nodes.el.removeAttribute('data-height'); + return $.event('QRMetadata', null, _this.nodes.el); }; })(this); + this.nodes.el.dataset.height = 'loading'; $.on(el, event, onload); $.on(el, 'error', onerror); return el.src = URL.createObjectURL(this.file); }; _Class.prototype.checkDimensions = function(el) { - var duration, height, max_height, max_width, ref, videoHeight, videoWidth, width; + var duration, height, max_height, max_width, videoHeight, videoWidth, width; if (el.tagName === 'IMG') { height = el.height, width = el.width; + this.nodes.el.dataset.height = height; + this.nodes.el.dataset.width = width; if (height > QR.max_height || width > QR.max_width) { this.fileError("Image too large (image: " + height + "x" + width + "px, max: " + QR.max_height + "x" + QR.max_width + "px)"); } @@ -21094,6 +26026,9 @@ QR = (function() { } } else { videoHeight = el.videoHeight, videoWidth = el.videoWidth, duration = el.duration; + this.nodes.el.dataset.height = videoHeight; + this.nodes.el.dataset.width = videoWidth; + this.nodes.el.dataset.duration = duration; max_height = Math.min(QR.max_height, QR.max_height_video); max_width = Math.min(QR.max_width, QR.max_width_video); if (videoHeight > max_height || videoWidth > max_width) { @@ -21107,7 +26042,7 @@ QR = (function() { } else if (duration > QR.max_duration_video) { this.fileError("Video too long (video: " + duration + "s, max: " + QR.max_duration_video + "s)"); } - if (((ref = g.BOARD.ID) !== 'gif' && ref !== 'wsg') && $.hasAudio(el)) { + if (BoardConfig.noAudio(g.BOARD.ID) && $.hasAudio(el)) { return this.fileError('Audio not allowed'); } } @@ -21160,19 +26095,30 @@ QR = (function() { delete this.filesize; this.nodes.el.removeAttribute('title'); QR.nodes.filename.removeAttribute('title'); - this.nodes.el.style.backgroundImage = null; + this.rmMetadata(); + this.nodes.el.style.backgroundImage = ''; $.rmClass(this.nodes.el, 'has-file'); this.showFileData(); URL.revokeObjectURL(this.URL); - return this.dismissErrors(function(error) { + this.dismissErrors(function(error) { return $.hasClass(error, 'file-error'); }); + return this.preventAutoPost(); + }; + + _Class.prototype.rmMetadata = function() { + var attr, i, len, ref; + ref = ['type', 'height', 'width', 'duration']; + for (i = 0, len = ref.length; i < len; i++) { + attr = ref[i]; + this.nodes.el.removeAttribute("data-" + attr); + } }; _Class.prototype.saveFilename = function() { this.file.newName = (this.filename || '').replace(/[\/\\]/g, '-'); if (!QR.validExtension.test(this.filename)) { - return this.file.newName += "." + (QR.extensionFromType[this.file.type] || 'jpg'); + return this.file.newName += "." + ($.getOwn(QR.extensionFromType, this.file.type) || 'jpg'); } }; @@ -21208,6 +26154,7 @@ QR = (function() { _Class.prototype.pasteText = function(file) { var reader; this.pasting = true; + this.preventAutoPost(); reader = new FileReader(); reader.onload = (function(_this) { return function(e) { @@ -21245,7 +26192,7 @@ QR = (function() { }; _Class.prototype.drop = function() { - var el, index, newIndex, oldIndex, post; + var base, el, index, newIndex, oldIndex, post; $.rmClass(this, 'over'); if (!this.draggable) { return; @@ -21256,10 +26203,14 @@ QR = (function() { }; oldIndex = index(el); newIndex = index(this); + if (QR.posts[oldIndex].isLocked || QR.posts[newIndex].isLocked) { + return; + } (oldIndex < newIndex ? $.after : $.before)(this, el); post = QR.posts.splice(oldIndex, 1)[0]; QR.posts.splice(newIndex, 0, post); - return QR.status(); + QR.status(); + return typeof (base = QR.captcha).updateThread === "function" ? base.updateThread() : void 0; }; return _Class; @@ -21272,12 +26223,15 @@ QuoteBacklink = (function() { var QuoteBacklink; QuoteBacklink = { - containers: {}, + containers: $.dict(), init: function() { var ref; if (((ref = g.VIEW) !== 'index' && ref !== 'thread') || !Conf['Quote Backlinks']) { return; } + if ((this.bottomBacklinks = Conf['Bottom Backlinks'])) { + $.addClass(doc, 'bottom-backlinks'); + } Callbacks.Post.push({ name: 'Quote Backlinking Part 1', cb: this.firstNode @@ -21288,17 +26242,13 @@ QuoteBacklink = (function() { }); }, firstNode: function() { - var a, clone, container, containers, hash, i, j, k, len, len1, len2, link, markYours, nodes, post, quote, ref, ref1, ref2; + var a, clone, container, containers, hash, i, j, k, len, len1, len2, link, markYours, nodes, post, quote, ref, ref1; if (this.isClone || !this.quotes.length || this.isRebuilt) { return; } - markYours = Conf['Mark Quotes of You'] && ((ref = QuoteYou.db) != null ? ref.get({ - boardID: this.board.ID, - threadID: this.thread.ID, - postID: this.ID - }) : void 0); + markYours = Conf['Mark Quotes of You'] && QuoteYou.isYou(this); a = $.el('a', { - href: Build.postURL(this.board.ID, this.thread.ID, this.ID), + href: g.SITE.Build.postURL(this.board.ID, this.thread.ID, this.ID), className: this.isHidden ? 'filtered backlink' : 'backlink', textContent: Conf['backlink'].replace(/%(?:id|%)/g, (function(_this) { return function(x) { @@ -21307,16 +26257,19 @@ QuoteBacklink = (function() { '%%': '%' }[x]; }; - })(this)) + (markYours ? '\u00A0(You)' : '') + })(this)) }); - ref1 = this.quotes; - for (i = 0, len = ref1.length; i < len; i++) { - quote = ref1[i]; + if (markYours) { + $.add(a, QuoteYou.mark.cloneNode(true)); + } + ref = this.quotes; + for (i = 0, len = ref.length; i < len; i++) { + quote = ref[i]; containers = [QuoteBacklink.getContainer(quote)]; - if ((post = g.posts[quote]) && post.nodes.backlinkContainer) { - ref2 = post.clones; - for (j = 0, len1 = ref2.length; j < len1; j++) { - clone = ref2[j]; + if ((post = g.posts.get(quote)) && post.nodes.backlinkContainer) { + ref1 = post.clones; + for (j = 0, len1 = ref1.length; j < len1; j++) { + clone = ref1[j]; containers.push(clone.nodes.backlinkContainer); } } @@ -21341,7 +26294,7 @@ QuoteBacklink = (function() { secondNode: function() { var container; if (this.isClone && (this.origin.isReply || Conf['OP Backlinks'])) { - this.nodes.backlinkContainer = $('.container', this.nodes.info); + this.nodes.backlinkContainer = $('.container', this.nodes.post); return; } if (!(this.isReply || Conf['OP Backlinks'])) { @@ -21349,7 +26302,11 @@ QuoteBacklink = (function() { } container = QuoteBacklink.getContainer(this.fullID); this.nodes.backlinkContainer = container; - return $.add(this.nodes.info, container); + if (QuoteBacklink.bottomBacklinks) { + return $.add(this.nodes.post, container); + } else { + return $.add(this.nodes.info, container); + } }, getContainer: function(id) { var base; @@ -21375,7 +26332,10 @@ QuoteCT = (function() { if (Conf['Comment Expansion']) { ExpandComment.callbacks.push(this.node); } - this.text = '\u00A0(Cross-thread)'; + this.mark = $.el('span', { + textContent: '\u00A0(Cross-thread)', + className: 'qmark-ct' + }); return Callbacks.Post.push({ name: 'Mark Cross-thread Quotes', cb: this.node @@ -21395,10 +26355,10 @@ QuoteCT = (function() { continue; } if (this.isClone) { - quotelink.textContent = quotelink.textContent.replace(QuoteCT.text, ''); + $.rm($('.qmark-ct', quotelink)); } if (boardID === board.ID && threadID !== thread.ID) { - $.add(quotelink, $.tn(QuoteCT.text)); + $.add(quotelink, QuoteCT.mark.cloneNode(true)); } } } @@ -21458,11 +26418,14 @@ QuoteInline = (function() { }, toggle: function(e) { var boardID, context, postID, quoter, ref, ref1, threadID; - if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { + if ($.modifiedClick(e)) { return; } ref = Get.postDataFromLink(this), boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID; - if (Conf['Inline Cross-thread Quotes Only'] && g.VIEW === 'thread' && ((ref1 = g.posts[boardID + "." + postID]) != null ? ref1.nodes.root.offsetParent : void 0)) { + if (Conf['Inline Cross-thread Quotes Only'] && g.VIEW === 'thread' && ((ref1 = g.posts.get(boardID + "." + postID)) != null ? ref1.nodes.root.offsetParent : void 0)) { + return; + } + if ($.hasClass(doc, 'catalog-mode')) { return; } e.preventDefault(); @@ -21480,7 +26443,7 @@ QuoteInline = (function() { }, findRoot: function(quotelink, isBacklink) { if (isBacklink) { - return quotelink.parentNode.parentNode; + return $.x('ancestor::*[parent::*[contains(@class,"post")]][1]', quotelink); } else { return $.x('ancestor-or-self::*[parent::blockquote][1]', quotelink); } @@ -21497,7 +26460,7 @@ QuoteInline = (function() { qroot = $.x('ancestor::*[contains(@class,"postContainer")][1]', root); $.addClass(qroot, 'hasInline'); new Fetcher(boardID, threadID, postID, inline, quoter); - if (!((post = g.posts[boardID + "." + postID]) && context.thread === post.thread)) { + if (!((post = g.posts.get(boardID + "." + postID)) && context.thread === post.thread)) { return; } if (isBacklink && Conf['Forward Hiding']) { @@ -21510,21 +26473,23 @@ QuoteInline = (function() { return Unread.readSinglePost(post); }, rm: function(quotelink, boardID, threadID, postID, context) { - var el, inlined, isBacklink, post, qroot, ref, root; + var el, inlined, isBacklink, parentNode, post, qroot, ref, root; isBacklink = $.hasClass(quotelink, 'backlink'); root = QuoteInline.findRoot(quotelink, isBacklink); root = $.x("following-sibling::div[@data-full-i-d='" + boardID + "." + postID + "'][1]", root); qroot = $.x('ancestor::*[contains(@class,"postContainer")][1]', root); + parentNode = root.parentNode; $.rm(root); + $.event('PostsRemoved', null, parentNode); if (!$('.inline', qroot)) { $.rmClass(qroot, 'hasInline'); } if (!(el = root.firstElementChild)) { return; } - post = g.posts[boardID + "." + postID]; + post = g.posts.get(boardID + "." + postID); post.rmClone(el.dataset.clone); - if (Conf['Forward Hiding'] && isBacklink && context.thread === g.threads[boardID + "." + threadID] && !--post.forwarded) { + if (Conf['Forward Hiding'] && isBacklink && context.thread === g.threads.get(boardID + "." + threadID) && !--post.forwarded) { delete post.forwarded; $.rmClass(post.nodes.root, 'forwarded'); } @@ -21553,7 +26518,10 @@ QuoteOP = (function() { if (Conf['Comment Expansion']) { ExpandComment.callbacks.push(this.node); } - this.text = '\u00A0(OP)'; + this.mark = $.el('span', { + textContent: '\u00A0(OP)', + className: 'qmark-op' + }); return Callbacks.Post.push({ name: 'Mark OP Quotes', cb: this.node @@ -21571,7 +26539,7 @@ QuoteOP = (function() { if (this.isClone && (ref = this.thread.fullID, indexOf.call(quotes, ref) >= 0)) { i = 0; while (quotelink = quotelinks[i++]) { - quotelink.textContent = quotelink.textContent.replace(QuoteOP.text, ''); + $.rm($('.qmark-op', quotelink)); } } fullID = this.context.thread.fullID; @@ -21582,7 +26550,7 @@ QuoteOP = (function() { while (quotelink = quotelinks[i++]) { ref1 = Get.postDataFromLink(quotelink), boardID = ref1.boardID, postID = ref1.postID; if ((boardID + "." + postID) === fullID) { - $.add(quotelink, $.tn(QuoteOP.text)); + $.add(quotelink, QuoteOP.mark.cloneNode(true)); } } } @@ -21599,7 +26567,17 @@ QuotePreview = (function() { QuotePreview = { init: function() { var ref; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Quote Previewing'])) { + if (!Conf['Quote Previewing']) { + return; + } + if (g.VIEW === 'archive') { + $.on(d, 'mouseover', function(e) { + if (e.target.nodeName === 'A' && $.hasClass(e.target, 'quotelink')) { + return QuotePreview.mouseover.call(e.target, e); + } + }); + } + if ((ref = g.VIEW) !== 'index' && ref !== 'thread') { return; } if (Conf['Comment Expansion']) { @@ -21620,7 +26598,7 @@ QuotePreview = (function() { }, mouseover: function(e) { var boardID, i, len, origin, post, postID, posts, qp, ref, threadID; - if ($.hasClass(this, 'inlined') || !d.contains(this)) { + if (($.hasClass(this, 'inlined') && !$.hasClass(doc, 'catalog-mode')) || !d.contains(this)) { return; } ref = Get.postDataFromLink(this), boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID; @@ -21637,7 +26615,7 @@ QuotePreview = (function() { endEvents: 'mouseout click', cb: QuotePreview.mouseout }); - if (Conf['Quote Highlighting'] && (origin = g.posts[boardID + "." + postID])) { + if (Conf['Quote Highlighting'] && (origin = g.posts.get(boardID + "." + postID))) { posts = [origin].concat(origin.clones); posts.pop(); for (i = 0, len = posts.length; i < len; i++) { @@ -21651,6 +26629,7 @@ QuotePreview = (function() { if (!(root = this.el.firstElementChild)) { return; } + $.event('PostsRemoved', null, Header.hover); clone = Get.postFromRoot(root); post = clone.origin; post.rmClone(root.dataset.clone); @@ -21692,7 +26671,7 @@ QuoteStrikeThrough = (function() { for (i = 0, len = ref.length; i < len; i++) { quotelink = ref[i]; ref1 = Get.postDataFromLink(quotelink), boardID = ref1.boardID, postID = ref1.postID; - if ((ref2 = g.posts[boardID + "." + postID]) != null ? ref2.isHidden : void 0) { + if ((ref2 = g.posts.get(boardID + "." + postID)) != null ? ref2.isHidden : void 0) { $.addClass(quotelink, 'filtered'); } } @@ -21716,16 +26695,12 @@ QuoteThreading = if (!(Conf['Quote Threading'] && g.VIEW === 'thread')) { return; } - this.controls = $.el('label', { - innerHTML: " Threading" - }); + this.controls = $.el('label', {innerHTML: " Threading"}); this.threadNewLink = $.el('span', { className: 'brackets-wrap threadnewlink', hidden: true }); - $.extend(this.threadNewLink, { - innerHTML: "Thread New Posts" - }); + $.extend(this.threadNewLink, {innerHTML: "Thread New Posts"}); this.input = $('input', this.controls); this.input.checked = Conf['Thread Quotes']; $.on(this.input, 'change', this.setEnabled); @@ -21749,15 +26724,26 @@ QuoteThreading = cb: this.node }); }, - parent: {}, - children: {}, - inserted: {}, + parent: $.dict(), + children: $.dict(), + inserted: $.dict(), + toggleThreading: function() { + return this.setThreadingState(!Conf['Thread Quotes']); + }, + setThreadingState: function(enabled) { + this.input.checked = enabled; + this.setEnabled.call(this.input); + return this.rethread.call(this.input); + }, setEnabled: function() { var other, ref; - other = (ref = ReplyPruning.inputs) != null ? ref.enabled : void 0; - if (this.checked && (other != null ? other.checked : void 0)) { - other.checked = false; - $.event('change', null, other); + if (this.checked) { + $.set('Prune All Threads', false); + other = (ref = ReplyPruning.inputs) != null ? ref.enabled : void 0; + if (other != null ? other.checked : void 0) { + other.checked = false; + $.event('change', null, other); + } } return $.cb.checked.call(this); }, @@ -21782,7 +26768,7 @@ QuoteThreading = ref = this.quotes; for (j = 0, len = ref.length; j < len; j++) { quote = ref[j]; - if (parent = g.posts[quote]) { + if (parent = g.posts.get(quote)) { if (!parent.isFetchedQuote && parent.isReply && parent.ID < this.ID) { parents.add(parent.ID); if (!lastParent || parent.ID > lastParent.ID) { @@ -21890,7 +26876,7 @@ QuoteThreading = } else { nodes = []; Unread.order = new RandomAccessList(); - QuoteThreading.inserted = {}; + QuoteThreading.inserted = $.dict(); posts.forEach(function(post) { if (post.isFetchedQuote) { return; @@ -21906,7 +26892,7 @@ QuoteThreading = return delete post.nodes.threadContainer; } }); - $.add(thread.OP.nodes.root.parentNode, nodes); + $.add(thread.nodes.root, nodes); } Unread.position = Unread.order.first; Unread.updatePosition(); @@ -21934,19 +26920,23 @@ QuoteYou = (function() { return Conf['Remember Your Posts'] = enabled; }); $.on(d, 'QRPostSuccessful', function(e) { - var boardID, postID, ref, threadID; - $.forceSync('Remember Your Posts'); - if (Conf['Remember Your Posts']) { + var cb; + cb = PostRedirect.delay(); + return $.get('Remember Your Posts', Conf['Remember Your Posts'], function(items) { + var boardID, postID, ref, threadID; + if (!items['Remember Your Posts']) { + return; + } ref = e.detail, boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID; return QuoteYou.db.set({ boardID: boardID, threadID: threadID, postID: postID, val: true - }); - } + }, cb); + }); }); - if ((ref = g.VIEW) !== 'index' && ref !== 'thread') { + if ((ref = g.VIEW) !== 'index' && ref !== 'thread' && ref !== 'archive') { return; } if (Conf['Highlight Own Posts']) { @@ -21958,22 +26948,30 @@ QuoteYou = (function() { if (Conf['Comment Expansion']) { ExpandComment.callbacks.push(this.node); } - this.text = '\u00A0(You)'; - return Callbacks.Post.push({ + this.mark = $.el('span', { + textContent: '\u00A0(You)', + className: 'qmark-you' + }); + Callbacks.Post.push({ name: 'Mark Quotes of You', cb: this.node }); + return QuoteYou.menu.init(); + }, + isYou: function(post) { + var ref; + return !!((ref = QuoteYou.db) != null ? ref.get({ + boardID: post.boardID, + threadID: post.threadID, + postID: post.ID + }) : void 0); }, node: function() { var i, len, quotelink, ref; if (this.isClone) { return; } - if (QuoteYou.db.get({ - boardID: this.board.ID, - threadID: this.thread.ID, - postID: this.ID - })) { + if (QuoteYou.isYou(this)) { $.addClass(this.nodes.root, 'yourPost'); } if (!this.quotes.length) { @@ -21986,17 +26984,73 @@ QuoteYou = (function() { continue; } if (Conf['Mark Quotes of You']) { - $.add(quotelink, $.tn(QuoteYou.text)); + $.add(quotelink, QuoteYou.mark.cloneNode(true)); } $.addClass(quotelink, 'you'); $.addClass(this.nodes.root, 'quotesYou'); } }, + menu: { + init: function() { + var input, label, ref; + label = $.el('label', { + className: 'toggle-you' + }, {innerHTML: " You"}); + input = $('input', label); + $.on(input, 'change', QuoteYou.menu.toggle); + return (ref = Menu.menu) != null ? ref.addEntry({ + el: label, + order: 80, + open: function(post) { + QuoteYou.menu.post = post.origin || post; + input.checked = QuoteYou.isYou(post); + return true; + } + }) : void 0; + }, + toggle: function() { + var clone, data, i, j, len, len1, post, quotelink, quoter, ref, ref1; + post = QuoteYou.menu.post; + data = { + boardID: post.board.ID, + threadID: post.thread.ID, + postID: post.ID, + val: true + }; + if (this.checked) { + QuoteYou.db.set(data); + } else { + QuoteYou.db["delete"](data); + } + ref = [post].concat(post.clones); + for (i = 0, len = ref.length; i < len; i++) { + clone = ref[i]; + clone.nodes.root.classList.toggle('yourPost', this.checked); + } + ref1 = Get.allQuotelinksLinkingTo(post); + for (j = 0, len1 = ref1.length; j < len1; j++) { + quotelink = ref1[j]; + if (this.checked) { + if (Conf['Mark Quotes of You']) { + $.add(quotelink, QuoteYou.mark.cloneNode(true)); + } + } else { + $.rm($('.qmark-you', quotelink)); + } + quotelink.classList.toggle('you', this.checked); + if ($.hasClass(quotelink, 'quotelink')) { + quoter = Get.postFromNode(quotelink).nodes.root; + quoter.classList.toggle('quotesYou', !!$('.quotelink.you', quoter)); + } + } + } + }, cb: { seek: function(type) { - var highlight, post, posts, result, str; - if (highlight = $('.highlight')) { - $.rmClass(highlight, 'highlight'); + var highlight, highlighted, post, posts, result, str; + highlight = g.SITE.classes.highlight; + if ((highlighted = $("." + highlight))) { + $.rmClass(highlighted, highlight); } if (!(QuoteYou.lastRead && doc.contains(QuoteYou.lastRead) && $.hasClass(QuoteYou.lastRead, 'quotesYou'))) { if (!(post = QuoteYou.lastRead = $('.quotesYou'))) { @@ -22019,15 +27073,22 @@ QuoteYou = (function() { return QuoteYou.cb.scroll(posts[type === 'following' ? 0 : posts.length - 1]); }, scroll: function(root) { - var post; - post = $('.post', root); - if (!post.getBoundingClientRect().height) { + var node, post, sel; + post = Get.postFromRoot(root); + if (!post.nodes.post.getBoundingClientRect().height) { return false; } else { QuoteYou.lastRead = root; - window.location = "#" + post.id; - Header.scrollTo(post); - $.addClass(post, 'highlight'); + location.href = Get.url('post', post); + Header.scrollTo(post.nodes.post); + if (post.isReply) { + sel = "" + g.SITE.selectors.postContainer + g.SITE.selectors.highlightable.reply; + node = post.nodes.root; + if (!node.matches(sel)) { + node = $(sel, node); + } + $.addClass(node, g.SITE.classes.highlight); + } return true; } } @@ -22049,6 +27110,7 @@ Quotify = (function() { if (((ref = g.VIEW) !== 'index' && ref !== 'thread') || !Conf['Resurrect Quotes']) { return; } + $.addClass(doc, 'resurrect-quotes'); if (Conf['Comment Expansion']) { ExpandComment.callbacks.push(this.node); } @@ -22075,16 +27137,16 @@ Quotify = (function() { } }, parseArchivelink: function(link) { - var boardID, m, postID, threadID; + var boardID, m, postID, ref, threadID; if (!(m = link.pathname.match(/^\/([^\/]+)\/thread\/S?(\d+)\/?$/))) { return; } - if (link.hostname === 'boards.4chan.org') { + if ((ref = link.hostname) === 'boards.4chan.org' || ref === 'boards.4channel.org') { return; } boardID = m[1]; threadID = m[2]; - postID = link.hash.match(/^#p?(\d+)$|$/)[1] || threadID; + postID = link.hash.match(/^#[pq]?(\d+)$|$/)[1] || threadID; if (Redirect.to('post', { boardID: boardID, postID: postID @@ -22114,19 +27176,20 @@ Quotify = (function() { } boardID = (m = quote.match(/^>>>\/([a-z\d]+)/)) ? m[1] : this.board.ID; quoteID = boardID + "." + postID; - if (post = g.posts[quoteID]) { + if (post = g.posts.get(quoteID)) { if (!post.isDead) { a = $.el('a', { - href: Build.postURL(boardID, post.thread.ID, postID), + href: g.SITE.Build.postURL(boardID, post.thread.ID, postID), className: 'quotelink', textContent: quote }); } else { a = $.el('a', { - href: Build.postURL(boardID, post.thread.ID, postID), + href: g.SITE.Build.postURL(boardID, post.thread.ID, postID), className: 'quotelink deadlink', - textContent: quote + "\u00A0(Dead)" + textContent: quote }); + $.add(a, Post.deadMark.cloneNode(true)); $.extend(a.dataset, { boardID: boardID, threadID: post.thread.ID, @@ -22147,8 +27210,9 @@ Quotify = (function() { a = $.el('a', { href: redirect || 'javascript:;', className: 'deadlink', - textContent: quote + "\u00A0(Dead)" + textContent: quote }); + $.add(a, Post.deadMark.cloneNode(true)); if (fetchable) { $.addClass(a, 'quotelink'); $.extend(a.dataset, { @@ -22162,7 +27226,7 @@ Quotify = (function() { this.quotes.push(quoteID); } if (!a) { - deadlink.textContent = quote + "\u00A0(Dead)"; + $.add(deadlink, Post.deadMark.cloneNode(true)); return; } $.replace(deadlink, a); @@ -22188,35 +27252,36 @@ Quotify = (function() { }).call(this); Main = (function() { - var Main; + var Main, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; Main = { init: function() { - var db, flatten, items, j, key, len, ref; - if (d.body && !$('title', d.head)) { - return; - } - if (window['4chan X antidup']) { - return; - } - window['4chan X antidup'] = true; - if (location.hostname === 'www.google.com') { - $.get('Captcha Fixes', true, function(arg1) { - var enabled; - enabled = arg1['Captcha Fixes']; - if (enabled) { - return $.ready(function() { - return Captcha.fixes.init(); - }); - } - }); - return; - } + var db, flatten, i, items, j, k, key, len, mountedCB, ref, ref1, ref2, w; + try { + w = window; + if ($.platform === 'crx') { + w = w.wrappedJSObject || w; + } + if ('4chan X antidup' in w) { + return; + } + w['4chan X antidup'] = true; + } catch (error1) {} try { - if (window.frameElement && window.frameElement.src === '') { + if (window.frameElement && ((ref = window.frameElement.src) === '' || ref === 'about:blank')) { return; } - } catch (_error) {} + } catch (error1) {} + if (doc && $.hasClass(doc, 'fourchan-x')) { + return; + } + $.asap(docSet, function() { + $.addClass(doc, 'fourchan-x', 'seaweedchan'); + if ($.engine) { + return $.addClass(doc, "ua-" + $.engine); + } + }); $.on(d, '4chanXInitFinished', function() { if (Main.expectInitFinished) { return delete Main.expectInitFinished; @@ -22225,10 +27290,25 @@ Main = (function() { return $.addClass(doc, 'tainted'); } }); + mountedCB = function() { + var cb, j, len, ref1, results; + d.removeEventListener('mounted', mountedCB, true); + Main.isMounted = true; + ref1 = Main.mountedCBs; + results = []; + for (j = 0, len = ref1.length; j < len; j++) { + cb = ref1[j]; + try { + results.push(cb()); + } catch (error1) {} + } + return results; + }; + d.addEventListener('mounted', mountedCB, true); flatten = function(parent, obj) { var key, val; if (obj instanceof Array) { - Conf[parent] = obj[0]; + Conf[parent] = $.dict.clone(obj[0]); } else if (typeof obj === 'object') { for (key in obj) { val = obj[key]; @@ -22238,77 +27318,95 @@ Main = (function() { Conf[parent] = obj; } }; - flatten(null, Config); - ref = DataBoard.keys; - for (j = 0, len = ref.length; j < len; j++) { - db = ref[j]; - Conf[db] = { - boards: {} - }; + if ((ref1 = location.hostname) === 'boards.4chan.org' || ref1 === 'boards.4channel.org') { + $.global(function() { + var fromCharCode0; + fromCharCode0 = String.fromCharCode; + return String.fromCharCode = function() { + if (document.body) { + String.fromCharCode = fromCharCode0; + } else if (document.currentScript && !document.currentScript.src) { + throw Error(); + } + return fromCharCode0.apply(this, arguments); + }; + }); + $.asap(docSet, function() { + return $.onExists(doc, 'iframe[srcdoc]', $.rm); + }); } + flatten(null, Config); + ref2 = DataBoard.keys; + for (j = 0, len = ref2.length; j < len; j++) { + db = ref2[j]; + Conf[db] = $.dict(); + } + Conf['customTitles'] = $.dict.clone({ + '4chan.org': { + boards: { + 'qa': { + 'boardTitle': { + orig: '/qa/ - Question & Answer', + title: '/qa/ - 2D/Random' + } + } + } + } + }); + Conf['boardConfig'] = { + boards: $.dict() + }; Conf['archives'] = Redirect.archives; - Conf['selectedArchives'] = {}; - Conf['cooldowns'] = {}; - Conf['Index Sort'] = {}; + Conf['selectedArchives'] = $.dict(); + Conf['cooldowns'] = $.dict(); + Conf['Index Sort'] = $.dict(); + for (i = k = 0; k < 2; i = ++k) { + Conf["Last Long Reply Thresholds " + i] = $.dict(); + } + Conf['siteProperties'] = $.dict(); Conf['Except Archives from Encryption'] = false; Conf['JSON Navigation'] = true; Conf['Oekaki Links'] = true; Conf['Show Name and Subject'] = false; Conf['QR Shortcut'] = true; Conf['Bottom QR Link'] = true; - if ($.platform === 'crx') { - $.global(function() { - var k, key, len1, oldFun, ref1, whitelist; - whitelist = document.currentScript.dataset.whitelist; - whitelist = whitelist.split('\n').filter(function(x) { - return x[0] !== "'"; - }); - whitelist.push(location.protocol + "//" + location.host); - oldFun = {}; - ref1 = ['createElement', 'write']; - for (k = 0, len1 = ref1.length; k < len1; k++) { - key = ref1[k]; - oldFun[key] = document[key]; - document[key] = (function(key) { - return function(arg) { - var s; - s = document.currentScript; - if (s && s.src && whitelist.indexOf(s.src.split('/').slice(0, 3).join('/')) < 0) { - throw Error(); - } - return oldFun[key].call(document, arg); - }; - })(key); + Conf['Toggleable Thread Watcher'] = true; + Conf['siteSoftware'] = ''; + Conf['Use Faster Image Host'] = 'true'; + Conf['Captcha Fixes'] = true; + Conf['captchaServiceDomain'] = ''; + Conf['captchaServiceKey'] = $.dict(); + if (/\.4chan(?:nel)?\.org$/.test(location.hostname) && !SW.yotsuba.regexp.pass.test(location.href) && !SW.yotsuba.regexp.captcha.test(location.href) && !$$('script:not([src])', d).filter(function(s) { + return /this\[/.test(s.textContent); + }).length) { + ($.getSync || $.get)({ + 'jsWhitelist': Conf['jsWhitelist'] + }, function(arg) { + var jsWhitelist, parsedList; + jsWhitelist = arg.jsWhitelist; + parsedList = jsWhitelist.replace(/^#.*$/mg, '').replace(/[\s;]+/g, ' ').trim(); + if (/\S/.test(parsedList)) { + return $.addCSP("script-src " + parsedList); } - return document.addEventListener('csp-ready', function() { - var results; - results = []; - for (key in oldFun) { - results.push(document[key] = oldFun[key]); - } - return results; - }, false); - }, { - whitelist: Conf['jsWhitelist'] }); } - items = {}; + items = $.dict(); for (key in Conf) { items[key] = void 0; } items['previousversion'] = void 0; return ($.getSync || $.get)(items, function(items) { - var jsWhitelist, ref1; - jsWhitelist = (ref1 = items['jsWhitelist']) != null ? ref1 : Conf['jsWhitelist']; - $.addCSP("script-src " + (jsWhitelist.replace(/[\s;]+/g, ' '))); - if ($.platform === 'crx') { - $.event('csp-ready'); + var ref3; + if (!$.perProtocolSettings && /\.4chan(?:nel)?\.org$/.test(location.hostname) && ((ref3 = items['Redirect to HTTPS']) != null ? ref3 : Conf['Redirect to HTTPS']) && location.protocol !== 'https:') { + location.replace('https://' + location.host + location.pathname + location.search + location.hash); + return; } return $.asap(docSet, function() { - var ref2, val; + var ref4, val; if ($.cantSet) { } else if (items.previousversion == null) { + Main.isFirstRun = true; Main.ready(function() { $.set('previousversion', g.VERSION); return Settings.open(); @@ -22318,9 +27416,9 @@ Main = (function() { } for (key in Conf) { val = Conf[key]; - Conf[key] = (ref2 = items[key]) != null ? ref2 : val; + Conf[key] = (ref4 = items[key]) != null ? ref4 : val; } - return Main.initFeatures(); + return Site.init(Main.initFeatures); }); }); }, @@ -22332,100 +27430,105 @@ Main = (function() { return $.set(changes, function() { var el, ref; if ((ref = items['Show Updated Notifications']) != null ? ref : true) { - el = $.el('span', { - innerHTML: "4chan X has been updated to version " + E(g.VERSION) + "." - }); + el = $.el('span', {innerHTML: "4chan X has been updated to version " + E(g.VERSION) + "."}); return new Notice('info', el, 15); } }); }, + parseURL: function(site, url) { + var pathname, r, ref; + if (site == null) { + site = g.SITE; + } + if (url == null) { + url = location; + } + r = {}; + if (!site) { + return r; + } + r.siteID = site.ID; + if (typeof site.isBoardlessPage === "function" ? site.isBoardlessPage(url) : void 0) { + return r; + } + pathname = url.pathname.split(/\/+/); + r.boardID = pathname[1]; + if (site.isFileURL(url)) { + r.VIEW = 'file'; + } else if (typeof site.isAuxiliaryPage === "function" ? site.isAuxiliaryPage(url) : void 0) { + + } else if ((ref = pathname[2]) === 'thread' || ref === 'res') { + r.VIEW = 'thread'; + r.threadID = r.THREADID = +pathname[3].replace(/\.\w+$/, ''); + } else if (pathname[2] === 'archive' && pathname[3] === 'res') { + r.VIEW = 'thread'; + r.threadID = r.THREADID = +pathname[4].replace(/\.\w+$/, ''); + r.threadArchived = true; + } else if (/^(?:catalog|archive)(?:\.\w+)?$/.test(pathname[2])) { + r.VIEW = pathname[2].replace(/\.\w+$/, ''); + } else if (/^(?:index|\d*)(?:\.\w+)?$/.test(pathname[2])) { + r.VIEW = 'index'; + } + return r; + }, initFeatures: function() { - var err, feature, hostname, j, len, match, name, pathname, ref, ref1, ref2, ref3, search; - hostname = location.hostname, search = location.search; - pathname = location.pathname.split(/\/+/); - if (hostname !== 'www.4chan.org') { - g.BOARD = new Board(pathname[1]); + var base, err, feature, j, len, name, ref, ref1; + $.global(function() { + document.documentElement.classList.add('js-enabled'); + return window.FCX = {}; + }); + Main.jsEnabled = $.hasClass(doc, 'js-enabled'); + if (typeof $.ajaxPageInit === "function") { + $.ajaxPageInit(); } - if (hostname === 'boards.4chan.org' || hostname === 'sys.4chan.org' || hostname === 'www.4chan.org') { - $.global(function() { - document.documentElement.classList.add('js-enabled'); - return window.FCX = {}; - }); - Main.jsEnabled = $.hasClass(doc, 'js-enabled'); + $.extend(g, Main.parseURL()); + if (g.boardID) { + g.BOARD = new Board(g.boardID); } - switch (hostname) { - case 'www.4chan.org': - $.onExists(doc, 'body', function() { - return $.addStyle(CSS.www); - }); - Captcha.replace.init(); - return; - case 'sys.4chan.org': - if (pathname[2] === 'imgboard.php') { - if (/\bmode=report\b/.test(search)) { - Report.init(); - } else if ((match = search.match(/\bres=(\d+)/))) { - $.ready(function() { - var ref; - if (Conf['404 Redirect'] && ((ref = $.id('errmsg')) != null ? ref.textContent : void 0) === 'Error: Specified thread does not exist.') { - return Redirect.navigate('thread', { - boardID: g.BOARD.ID, - postID: +match[1] - }); - } - }); + if (!g.VIEW) { + if (typeof (base = g.SITE).initAuxiliary === "function") { + base.initAuxiliary(); + } + return; + } + if (g.VIEW === 'file') { + $.asap((function() { + return d.readyState !== 'loading'; + }), function() { + var base1, pathname, video; + if (g.SITE.software === 'yotsuba' && Conf['404 Redirect'] && (typeof (base1 = g.SITE).is404 === "function" ? base1.is404() : void 0)) { + pathname = location.pathname.split(/\/+/); + return Redirect.navigate('file', { + boardID: g.BOARD.ID, + filename: pathname[pathname.length - 1] + }); + } else if (video = $('video')) { + if (Conf['Volume in New Tab']) { + Volume.setup(video); } - } else if (pathname[2] === 'post') { - PostSuccessful.init(); - } - return; - case 'i.4cdn.org': - if (!(pathname[2] && !/s\.jpg$/.test(pathname[2]))) { - return; - } - $.asap((function() { - return d.readyState !== 'loading'; - }), function() { - var ref, video; - if (Conf['404 Redirect'] && ((ref = d.title) === '4chan - Temporarily Offline' || ref === '4chan - 404 Not Found')) { - return Redirect.navigate('file', { - boardID: g.BOARD.ID, - filename: pathname[pathname.length - 1] - }); - } else if (video = $('video')) { - if (Conf['Volume in New Tab']) { - Volume.setup(video); - } - if (Conf['Loop in New Tab']) { - video.loop = true; - video.controls = false; - video.play(); - return ImageCommon.addControls(video); - } + if (Conf['Loop in New Tab']) { + video.loop = true; + video.controls = false; + video.play(); + return ImageCommon.addControls(video); } - }); - return; - } - if ((ref = pathname[2]) === 'thread' || ref === 'res') { - g.VIEW = 'thread'; - g.THREADID = +pathname[3]; - } else if ((ref1 = pathname[2]) === 'catalog' || ref1 === 'archive') { - g.VIEW = pathname[2]; - } else if (pathname[2].match(/^\d*$/)) { - g.VIEW = 'index'; - } else { + } + }); return; } g.threads = new SimpleDict(); g.posts = new SimpleDict(); $.onExists(doc, 'body', Main.initStyle); - ref2 = Main.features; - for (j = 0, len = ref2.length; j < len; j++) { - ref3 = ref2[j], name = ref3[0], feature = ref3[1]; + ref = Main.features; + for (j = 0, len = ref.length; j < len; j++) { + ref1 = ref[j], name = ref1[0], feature = ref1[1]; + if (g.SITE.disabledFeatures && indexOf.call(g.SITE.disabledFeatures, name) >= 0) { + continue; + } try { feature.init(); - } catch (_error) { - err = _error; + } catch (error1) { + err = error1; Main.handleErrors({ message: "\"" + name + "\" initialization crashed.", error: err @@ -22442,13 +27545,11 @@ Main = (function() { if ((ref = $('link[href*=mobile]', d.head)) != null) { ref.disabled = true; } - $.addClass(doc, 'fourchan-x', 'seaweedchan'); + doc.dataset.host = location.host; + $.addClass(doc, "sw-" + g.SITE.software); $.addClass(doc, g.VIEW === 'thread' ? 'thread-view' : g.VIEW); - if ($.engine) { - $.addClass(doc, "ua-" + $.engine); - } - $.onExists(doc, '.ad-cnt', function(ad) { - return $.onExists(ad, 'img', function() { + $.onExists(doc, '.ad-cnt, .adg-rects > .desktop', function(ad) { + return $.onExists(ad, 'img, iframe', function() { return $.addClass(doc, 'ads-loaded'); }); }); @@ -22462,7 +27563,7 @@ Main = (function() { return $.toggleClass(doc, 'autohiding-scrollbar'); } }); - $.addStyle(CSS.boards, 'fourchanx-css'); + $.addStyle(CSS.sub(CSS.boards), 'fourchanx-css'); Main.bgColorStyle = $.el('style', { id: 'fourchanx-bgcolor-css' }); @@ -22481,135 +27582,152 @@ Main = (function() { return Main.setClass(); }, setClass: function() { - var mainStyleSheet, setStyle, style, styleSheets; - if (g.VIEW === 'catalog') { - $.addClass(doc, $.id('base-css').href.match(/catalog_(\w+)/)[1].replace('_new', '').replace(/_+/g, '-')); - return; + var j, knownStyles, len, mainStyleSheet, ref, ref1, setStyle, style, styleSheet, styleSheets; + knownStyles = ['yotsuba', 'yotsuba-b', 'futaba', 'burichan', 'photon', 'tomorrow', 'spooky']; + if (g.SITE.software === 'yotsuba' && g.VIEW === 'catalog') { + if ((mainStyleSheet = $.id('base-css'))) { + style = (ref = mainStyleSheet.href.match(/catalog_(\w+)/)) != null ? ref[1].replace('_new', '').replace(/_+/g, '-') : void 0; + if (indexOf.call(knownStyles, style) >= 0) { + $.addClass(doc, style); + return; + } + } } - style = 'yotsuba-b'; - mainStyleSheet = $('link[title=switch]', d.head); - styleSheets = $$('link[rel="alternate stylesheet"]', d.head); + style = mainStyleSheet = styleSheets = null; setStyle = function() { - var bgColor, div, j, len, styleSheet; - $.rmClass(doc, style); - style = null; - for (j = 0, len = styleSheets.length; j < len; j++) { - styleSheet = styleSheets[j]; - if (styleSheet.href === (mainStyleSheet != null ? mainStyleSheet.href : void 0)) { - style = styleSheet.title.toLowerCase().replace('new', '').trim().replace(/\s+/g, '-'); - break; + var bgColor, css, div, j, len, rgb, s, styleSheet; + if (g.SITE.software === 'yotsuba') { + $.rmClass(doc, style); + style = null; + for (j = 0, len = styleSheets.length; j < len; j++) { + styleSheet = styleSheets[j]; + if (styleSheet.href === (mainStyleSheet != null ? mainStyleSheet.href : void 0)) { + style = styleSheet.title.toLowerCase().replace('new', '').trim().replace(/\s+/g, '-'); + if (style === '_special') { + style = styleSheet.href.match(/[a-z]*(?=[^\/]*$)/)[0]; + } + if (indexOf.call(knownStyles, style) < 0) { + style = null; + } + break; + } + } + if (style) { + $.addClass(doc, style); + $.rm(Main.bgColorStyle); + return; } } - if (style) { - $.addClass(doc, style); - return $.rm(Main.bgColorStyle); - } else { - div = $.el('div', { - className: 'reply' - }); - div.style.cssText = 'position: absolute; visibility: hidden;'; - $.add(d.body, div); - bgColor = window.getComputedStyle(div).backgroundColor; - $.rm(div); - Main.bgColorStyle.textContent = ".dialog, .suboption-list > div:last-of-type {\n background-color: " + bgColor + ";\n}"; - return $.after($.id('fourchanx-css'), Main.bgColorStyle); + div = g.SITE.bgColoredEl(); + div.style.position = 'absolute'; + div.style.visibility = 'hidden'; + $.add(d.body, div); + bgColor = window.getComputedStyle(div).backgroundColor; + $.rm(div); + rgb = bgColor.match(/[\d.]+/g); + if (!/^rgb\(/.test(bgColor)) { + s = window.getComputedStyle(d.body); + bgColor = s.backgroundColor + " " + s.backgroundImage + " " + s.backgroundRepeat + " " + s.backgroundPosition; + } + css = ".dialog, .suboption-list > div:last-of-type, :root.catalog-hover-expand .catalog-container:hover > .post {\n background: " + bgColor + ";\n}\n.unread-mark-read {\n background-color: rgba(" + (rgb.slice(0, 3).join(', ')) + ", " + (0.5 * (rgb[3] || 1)) + ");\n}"; + if ($.luma(rgb) < 100) { + css += ".watch-thread-link {\n background-image: url(\"data:image/svg+xml,\");\n}"; } + Main.bgColorStyle.textContent = css; + return $.after($.id('fourchanx-css'), Main.bgColorStyle); }; - setStyle(); + $.onExists(d.head, g.SITE.selectors.styleSheet, function(el) { + mainStyleSheet = el; + if (g.SITE.software === 'yotsuba') { + styleSheets = $$('link[rel="alternate stylesheet"]', d.head); + } + new MutationObserver(setStyle).observe(mainStyleSheet, { + attributes: true, + attributeFilter: ['href'] + }); + $.on(mainStyleSheet, 'load', setStyle); + return setStyle(); + }); if (!mainStyleSheet) { - return; + ref1 = $$('link[rel="stylesheet"]', d.head); + for (j = 0, len = ref1.length; j < len; j++) { + styleSheet = ref1[j]; + $.on(styleSheet, 'load', setStyle); + } + return setStyle(); } - return new MutationObserver(setStyle).observe(mainStyleSheet, { - attributes: true, - attributeFilter: ['href'] - }); }, initReady: function() { - var msg, ref, ref1, ref2; - if (g.VIEW === 'thread' && (((ref = d.title) === '4chan - Temporarily Offline' || ref === '4chan - 404 Not Found') || ($('.board') && !$('.opContainer')))) { - ThreadWatcher.set404(g.BOARD.ID, g.THREADID, function() { - if (Conf['404 Redirect']) { - return Redirect.navigate('thread', { - boardID: g.BOARD.ID, - threadID: g.THREADID, - postID: +location.hash.match(/\d+/) - }, "/" + g.BOARD + "/"); - } - }); - return; - } - if ((ref1 = d.title) === '4chan - Temporarily Offline' || ref1 === '4chan - 404 Not Found') { + var base, base1, msg; + if (typeof (base = g.SITE).is404 === "function" ? base.is404() : void 0) { + if (g.VIEW === 'thread') { + ThreadWatcher.set404(g.BOARD.ID, g.THREADID, function() { + if (Conf['404 Redirect']) { + return Redirect.navigate('thread', { + boardID: g.BOARD.ID, + threadID: g.THREADID, + postID: +location.hash.match(/\d+/) + }, "/" + g.BOARD + "/"); + } + }); + } return; } - if (((ref2 = g.VIEW) === 'index' || ref2 === 'thread') && !$('.board + *')) { - msg = $.el('div', { - innerHTML: "The page didn't load completely.
          Some features may not work unless you reload." - }); + if (typeof (base1 = g.SITE).isIncomplete === "function" ? base1.isIncomplete() : void 0) { + msg = $.el('div', {innerHTML: "The page didn't load completely.
          Some features may not work unless you reload."}); $.on($('a', msg), 'click', function() { return location.reload(); }); new Notice('warning', msg); } - if (!(Conf['JSON Index'] && g.VIEW === 'index')) { - return Main.initThread(); + if (g.VIEW === 'catalog') { + return Main.initCatalog(); + } else if (!Index.enabled) { + if (g.SITE.awaitBoard) { + return g.SITE.awaitBoard(Main.initThread); + } else { + return Main.initThread(); + } } else { Main.expectInitFinished = true; return $.event('4chanXInitFinished'); } }, initThread: function() { - var board, err, errors, j, k, len, len1, m, postRoot, posts, ref, ref1, scriptData, thread, threadRoot, threads; - if ((board = $('.board'))) { + var base, base1, board, errors, posts, ref, s, threads; + s = g.SITE.selectors; + if ((board = $(((ref = s.boardFor) != null ? ref[g.VIEW] : void 0) || s.board))) { threads = []; posts = []; - ref = $$('.board > .thread', board); - for (j = 0, len = ref.length; j < len; j++) { - threadRoot = ref[j]; - thread = new Thread(+threadRoot.id.slice(1), g.BOARD); - threads.push(thread); - ref1 = $$('.thread > .postContainer', threadRoot); - for (k = 0, len1 = ref1.length; k < len1; k++) { - postRoot = ref1[k]; - if ($('.postMessage', postRoot)) { - try { - posts.push(new Post(postRoot, thread, g.BOARD)); - } catch (_error) { - err = _error; - if (!errors) { - errors = []; - } - errors.push({ - message: "Parsing of Post No." + (postRoot.id.match(/\d+/)) + " failed. Post will be skipped.", - error: err - }); - } - } + errors = []; + try { + if (typeof (base = g.SITE).preParsingFixes === "function") { + base.preParsingFixes(board); } - } - if (errors) { + } catch (error1) {} + Main.addThreadsObserver = new MutationObserver(Main.addThreads); + Main.addPostsObserver = new MutationObserver(Main.addPosts); + Main.addThreadsObserver.observe(board, { + childList: true + }); + Main.parseThreads($$(s.thread, board), threads, posts, errors); + if (errors.length) { Main.handleErrors(errors); } if (g.VIEW === 'thread') { - scriptData = Get.scriptData(); - threads[0].postLimit = /\bbumplimit *= *1\b/.test(scriptData); - threads[0].fileLimit = /\bimagelimit *= *1\b/.test(scriptData); - threads[0].ipCount = (m = scriptData.match(/\bunique_ips *= *(\d+)\b/)) ? +m[1] : void 0; - } - if (g.BOARD.ID === 'f' && g.VIEW === 'thread') { - $.ajax("//a.4cdn.org/f/thread/" + g.THREADID + ".json", { - timeout: $.MINUTE, - onloadend: function() { - if (this.response && posts[0].file) { - return posts[0].file.text.dataset.md5 = posts[0].file.MD5 = this.response.posts[0].md5; - } - } - }); + if (g.threadArchived) { + threads[0].isArchived = true; + threads[0].kill(); + } + if (typeof (base1 = g.SITE).parseThreadMetadata === "function") { + base1.parseThreadMetadata(threads[0]); + } } Main.callbackNodes('Thread', threads); return Main.callbackNodesDB('Post', posts, function() { - var l, len2, post; - for (l = 0, len2 = posts.length; l < len2; l++) { - post = posts[l]; + var j, len, post; + for (j = 0, len = posts.length; j < len; j++) { + post = posts[j]; QuoteThreading.insert(post); } Main.expectInitFinished = true; @@ -22620,6 +27738,189 @@ Main = (function() { return $.event('4chanXInitFinished'); } }, + parseThreads: function(threadRoots, threads, posts, errors) { + var boardID, boardObj, j, len, postRoots, ref, thread, threadID, threadRoot; + for (j = 0, len = threadRoots.length; j < len; j++) { + threadRoot = threadRoots[j]; + boardObj = (boardID = threadRoot.dataset.board) ? (boardID = encodeURIComponent(boardID), g.boards[boardID] || new Board(boardID)) : g.BOARD; + threadID = +threadRoot.id.match(/\d*$/)[0]; + if (!threadID || ((ref = boardObj.threads.get(threadID)) != null ? ref.nodes.root : void 0)) { + return; + } + thread = new Thread(threadID, boardObj); + thread.nodes.root = threadRoot; + threads.push(thread); + postRoots = $$(g.SITE.selectors.postContainer, threadRoot); + if (g.SITE.isOPContainerThread) { + postRoots.unshift(threadRoot); + } + Main.parsePosts(postRoots, thread, posts, errors); + Main.addPostsObserver.observe(threadRoot, { + childList: true + }); + } + }, + parsePosts: function(postRoots, thread, posts, errors) { + var err, j, len, postRoot; + for (j = 0, len = postRoots.length; j < len; j++) { + postRoot = postRoots[j]; + if (!(postRoot.dataset.fullID && g.posts.get(postRoot.dataset.fullID)) && $(g.SITE.selectors.comment, postRoot)) { + try { + posts.push(new Post(postRoot, thread, thread.board)); + } catch (error1) { + err = error1; + errors.push({ + message: "Parsing of Post No." + (postRoot.id.match(/\d+/)) + " failed. Post will be skipped.", + error: err, + html: postRoot.outerHTML + }); + } + } + } + }, + addThreads: function(records) { + var errors, j, k, len, len1, node, posts, record, ref, threadRoots, threads; + threadRoots = []; + for (j = 0, len = records.length; j < len; j++) { + record = records[j]; + ref = record.addedNodes; + for (k = 0, len1 = ref.length; k < len1; k++) { + node = ref[k]; + if (node.nodeType === Node.ELEMENT_NODE && node.matches(g.SITE.selectors.thread)) { + threadRoots.push(node); + } + } + } + if (!threadRoots.length) { + return; + } + threads = []; + posts = []; + errors = []; + Main.parseThreads(threadRoots, threads, posts, errors); + if (errors.length) { + Main.handleErrors(errors); + } + Main.callbackNodes('Thread', threads); + return Main.callbackNodesDB('Post', posts, function() { + return $.event('PostsInserted', null, records[0].target); + }); + }, + addPosts: function(records) { + var anyRemoved, el, errors, j, k, l, len, len1, len2, n, node, postRoots, posts, record, ref, ref1, ref2, thread, threads, threadsRM; + threads = []; + threadsRM = []; + posts = []; + errors = []; + for (j = 0, len = records.length; j < len; j++) { + record = records[j]; + thread = Get.threadFromRoot(record.target); + postRoots = []; + ref = record.addedNodes; + for (k = 0, len1 = ref.length; k < len1; k++) { + node = ref[k]; + if (node.nodeType === Node.ELEMENT_NODE) { + if (node.matches(g.SITE.selectors.postContainer) || (node = $(g.SITE.selectors.postContainer, node))) { + postRoots.push(node); + } + } + } + n = posts.length; + Main.parsePosts(postRoots, thread, posts, errors); + if (posts.length > n && indexOf.call(threads, thread) < 0) { + threads.push(thread); + } + anyRemoved = false; + ref1 = record.removedNodes; + for (l = 0, len2 = ref1.length; l < len2; l++) { + el = ref1[l]; + if (((ref2 = Get.postFromRoot(el)) != null ? ref2.nodes.root : void 0) === el && !doc.contains(el)) { + anyRemoved = true; + break; + } + } + if (anyRemoved && indexOf.call(threadsRM, thread) < 0) { + threadsRM.push(thread); + } + } + if (errors.length) { + Main.handleErrors(errors); + } + return Main.callbackNodesDB('Post', posts, function() { + var len3, len4, m, o; + for (m = 0, len3 = threads.length; m < len3; m++) { + thread = threads[m]; + $.event('PostsInserted', null, thread.nodes.root); + } + for (o = 0, len4 = threadsRM.length; o < len4; o++) { + thread = threadsRM[o]; + $.event('PostsRemoved', null, thread.nodes.root); + } + }); + }, + initCatalog: function() { + var board, errors, s, threads; + s = g.SITE.selectors.catalog; + if (s && (board = $(s.board))) { + threads = []; + errors = []; + Main.addCatalogThreadsObserver = new MutationObserver(Main.addCatalogThreads); + Main.addCatalogThreadsObserver.observe(board, { + childList: true + }); + Main.parseCatalogThreads($$(s.thread, board), threads, errors); + if (errors.length) { + Main.handleErrors(errors); + } + Main.callbackNodes('CatalogThreadNative', threads); + } + Main.expectInitFinished = true; + return $.event('4chanXInitFinished'); + }, + parseCatalogThreads: function(threadRoots, threads, errors) { + var err, j, len, ref, thread, threadRoot; + for (j = 0, len = threadRoots.length; j < len; j++) { + threadRoot = threadRoots[j]; + try { + thread = new CatalogThreadNative(threadRoot); + if (((ref = thread.thread.catalogViewNative) != null ? ref.nodes.root : void 0) !== threadRoot) { + thread.thread.catalogViewNative = thread; + threads.push(thread); + } + } catch (error1) { + err = error1; + errors.push({ + message: "Parsing of Catalog Thread No." + ((threadRoot.dataset.id || threadRoot.id).match(/\d+/)) + " failed. Thread will be skipped.", + error: err, + html: threadRoot.outerHTML + }); + } + } + }, + addCatalogThreads: function(records) { + var errors, j, k, len, len1, node, record, ref, threadRoots, threads; + threadRoots = []; + for (j = 0, len = records.length; j < len; j++) { + record = records[j]; + ref = record.addedNodes; + for (k = 0, len1 = ref.length; k < len1; k++) { + node = ref[k]; + if (node.nodeType === Node.ELEMENT_NODE && node.matches(g.SITE.selectors.catalog.thread)) { + threadRoots.push(node); + } + } + } + if (!threadRoots.length) { + return; + } + threads = []; + errors = []; + Main.parseCatalogThreads(threadRoots, threads, errors); + if (errors.length) { + Main.handleErrors(errors); + } + return Main.callbackNodes('CatalogThreadNative', threads); + }, callbackNodes: function(klass, nodes) { var cb, i, node; i = 0; @@ -22655,11 +27956,21 @@ Main = (function() { return softTask(); }, handleErrors: function(errors) { - var div, error, j, len, logs; + var div, enabled, error, j, len, logs, msg; if (d.body && $.hasClass(d.body, 'fourchan_x') && !$.hasClass(doc, 'tainted')) { new Notice('error', 'Error: Multiple copies of 4chan X are enabled.'); $.addClass(doc, 'tainted'); } + if (g.SITE.testNativeExtension && !$.hasClass(doc, 'tainted')) { + enabled = g.SITE.testNativeExtension().enabled; + if (enabled) { + $.addClass(doc, 'tainted'); + if (Conf['Disable Native Extension'] && !Main.isFirstRun) { + msg = $.el('div', {innerHTML: "Failed to disable the native extension. You may need to block it."}); + new Notice('error', msg); + } + } + } if (!(errors instanceof Array)) { error = errors; } else if (errors.length === 1) { @@ -22669,9 +27980,7 @@ Main = (function() { new Notice('error', Main.parseError(error, Main.reportLink([error])), 15); return; } - div = $.el('div', { - innerHTML: E(errors.length) + " errors occurred." + (Main.reportLink(errors)).innerHTML + " [show]" - }); + div = $.el('div', {innerHTML: E(errors.length) + " errors occurred." + (Main.reportLink(errors)).innerHTML + " [show]"}); $.on(div.lastElementChild, 'click', function() { var ref; return ref = this.textContent === 'show' ? ['hide', false] : ['show', true], this.textContent = ref[0], logs.hidden = ref[1], ref; @@ -22688,9 +27997,7 @@ Main = (function() { parseError: function(data, reportLink) { var context, error, lines, message, ref, ref1; c.error(data.message, data.error.stack); - message = $.el('div', { - innerHTML: E(data.message) + ((reportLink) ? (reportLink).innerHTML : "") - }); + message = $.el('div', {innerHTML: E(data.message) + ((reportLink) ? (reportLink).innerHTML : "")}); error = $.el('div', { textContent: (data.error.name || 'Error') + ": " + (data.error.message || 'see console for details') }); @@ -22701,7 +28008,7 @@ Main = (function() { return [message, error, context]; }, reportLink: function(errors) { - var addDetails, data, details, title, url; + var addDetails, data, details, info, title, url; data = errors[0]; title = data.message; if (errors.length > 1) { @@ -22709,11 +28016,14 @@ Main = (function() { } details = ''; addDetails = function(text) { - if (!(encodeURIComponent(title + details + text + '\n').length > 8110)) { + if (!(encodeURIComponent(title + details + text + '\n').length > 8143)) { return details += text + '\n'; } }; - addDetails("[Please describe the steps needed to reproduce this error.]\n\nScript: 4chan X ccd0 v" + g.VERSION + " " + $.platform + "\nUser agent: " + navigator.userAgent + "\nURL: " + location.href); + addDetails("[Please describe the steps needed to reproduce this error.]\n\nScript: 4chan X ccd0 v" + g.VERSION + " " + $.platform + "\nURL: " + location.href + "\nUser agent: " + navigator.userAgent); + if ($.platform === 'userscript' && (info = typeof GM !== "undefined" && GM !== null ? GM.info : (typeof GM_info !== "undefined" && GM_info !== null ? GM_info : void 0))) { + addDetails("Userscript manager: " + info.scriptHandler + " " + info.version); + } addDetails('\n' + data.error); if (data.error.stack) { addDetails(data.error.stack.replace(data.error.toString(), '').trim()); @@ -22722,15 +28032,12 @@ Main = (function() { addDetails('\n`' + data.html + '`'); } details = details.replace(/file:\/{3}.+\//g, ''); - url = "https://gitreports.com/issue/ccd0/4chan-x?issue_title=" + (encodeURIComponent(title)) + "&details=" + (encodeURIComponent(details)); - return { - innerHTML: " [report]" - }; + url = 'https://github.com/ccd0/4chan-x/issues'.replace('%title', encodeURIComponent(title)).replace('%details', encodeURIComponent(details)); + return {innerHTML: " [report]"}; }, isThisPageLegit: function() { - var ref; if (!('thisPageIsLegit' in Main)) { - Main.thisPageIsLegit = location.hostname === 'boards.4chan.org' && !$('link[href*="favicon-status.ico"]', d.head) && ((ref = d.title) !== '4chan - Temporarily Offline' && ref !== '4chan - Error' && ref !== '504 Gateway Time-out'); + Main.thisPageIsLegit = g.SITE.isThisPageLegit ? g.SITE.isThisPageLegit() : !/^[45]\d\d\b/.test(document.title) && !/\.(?:json|rss)$/.test(location.pathname); } return Main.thisPageIsLegit; }, @@ -22741,7 +28048,15 @@ Main = (function() { } }); }, - features: [['Polyfill', Polyfill], ['Normalize URL', NormalizeURL], ['Captcha Configuration', Captcha.replace], ['Redirect', Redirect], ['Header', Header], ['Catalog Links', CatalogLinks], ['Settings', Settings], ['Index Generator', Index], ['Disable Autoplay', AntiAutoplay], ['Announcement Hiding', PSAHiding], ['Fourchan thingies', Fourchan], ['Color User IDs', IDColor], ['Highlight by User ID', IDHighlight], ['Custom CSS', CustomCSS], ['Thread Links', ThreadLinks], ['Linkify', Linkify], ['Reveal Spoilers', RemoveSpoilers], ['Resurrect Quotes', Quotify], ['Filter', Filter], ['Thread Hiding Buttons', ThreadHiding], ['Reply Hiding Buttons', PostHiding], ['Recursive', Recursive], ['Strike-through Quotes', QuoteStrikeThrough], ['Quick Reply Personas', QR.persona], ['Quick Reply', QR], ['Cooldown', QR.cooldown], ['Pass Link', PassLink], ['Menu', Menu], ['Index Generator (Menu)', Index.menu], ['Report Link', ReportLink], ['Thread Hiding (Menu)', ThreadHiding.menu], ['Reply Hiding (Menu)', PostHiding.menu], ['Delete Link', DeleteLink], ['Filter (Menu)', Filter.menu], ['Edit Link', QR.oekaki.menu], ['Download Link', DownloadLink], ['Archive Link', ArchiveLink], ['Quote Inlining', QuoteInline], ['Quote Previewing', QuotePreview], ['Quote Backlinks', QuoteBacklink], ['Mark Quotes of You', QuoteYou], ['Mark OP Quotes', QuoteOP], ['Mark Cross-thread Quotes', QuoteCT], ['Anonymize', Anonymize], ['Time Formatting', Time], ['Relative Post Dates', RelativeDates], ['File Info Formatting', FileInfo], ['Fappe Tyme', FappeTyme], ['Gallery', Gallery], ['Gallery (menu)', Gallery.menu], ['Sauce', Sauce], ['Image Expansion', ImageExpand], ['Image Expansion (Menu)', ImageExpand.menu], ['Reveal Spoiler Thumbnails', RevealSpoilers], ['Image Loading', ImageLoader], ['Image Hover', ImageHover], ['Volume Control', Volume], ['WEBM Metadata', Metadata], ['Comment Expansion', ExpandComment], ['Thread Expansion', ExpandThread], ['Favicon', Favicon], ['Unread', Unread], ['Quote Threading', QuoteThreading], ['Thread Stats', ThreadStats], ['Thread Updater', ThreadUpdater], ['Thread Watcher', ThreadWatcher], ['Thread Watcher (Menu)', ThreadWatcher.menu], ['Mark New IPs', MarkNewIPs], ['Index Navigation', Nav], ['Keybinds', Keybinds], ['Banner', Banner], ['Flash Features', Flash], ['Reply Pruning', ReplyPruning]] + mounted: function(cb) { + if (Main.isMounted) { + return cb(); + } else { + return Main.mountedCBs.push(cb); + } + }, + mountedCBs: [], + features: [['Polyfill', Polyfill], ['Board Configuration', BoardConfig], ['Normalize URL', NormalizeURL], ['Delay Redirect on Post', PostRedirect], ['Captcha Configuration', Captcha.replace], ['Image Host Rewriting', ImageHost], ['Redirect', Redirect], ['Header', Header], ['Catalog Links', CatalogLinks], ['Settings', Settings], ['Index Generator', Index], ['Disable Autoplay', AntiAutoplay], ['Announcement Hiding', PSAHiding], ['Fourchan thingies', Fourchan], ['Tinyboard Glue', Tinyboard], ['Color User IDs', IDColor], ['Highlight by User ID', IDHighlight], ['Count Posts by ID', IDPostCount], ['Custom CSS', CustomCSS], ['Thread Links', ThreadLinks], ['Linkify', Linkify], ['Reveal Spoilers', RemoveSpoilers], ['Resurrect Quotes', Quotify], ['Filter', Filter], ['Thread Hiding Buttons', ThreadHiding], ['Reply Hiding Buttons', PostHiding], ['Recursive', Recursive], ['Strike-through Quotes', QuoteStrikeThrough], ['Quick Reply Personas', QR.persona], ['Quick Reply', QR], ['Cooldown', QR.cooldown], ['Post Jumper', PostJumper], ['Pass Link', PassLink], ['Menu', Menu], ['Index Generator (Menu)', Index.menu], ['Report Link', ReportLink], ['Copy Text Link', CopyTextLink], ['Thread Hiding (Menu)', ThreadHiding.menu], ['Reply Hiding (Menu)', PostHiding.menu], ['Delete Link', DeleteLink], ['Filter (Menu)', Filter.menu], ['Edit Link', QR.oekaki.menu], ['Download Link', DownloadLink], ['Archive Link', ArchiveLink], ['Quote Inlining', QuoteInline], ['Quote Previewing', QuotePreview], ['Quote Backlinks', QuoteBacklink], ['Mark Quotes of You', QuoteYou], ['Mark OP Quotes', QuoteOP], ['Mark Cross-thread Quotes', QuoteCT], ['Anonymize', Anonymize], ['Time Formatting', Time], ['Relative Post Dates', RelativeDates], ['File Info Formatting', FileInfo], ['Fappe Tyme', FappeTyme], ['Gallery', Gallery], ['Gallery (menu)', Gallery.menu], ['Sauce', Sauce], ['Image Expansion', ImageExpand], ['Image Expansion (Menu)', ImageExpand.menu], ['Reveal Spoiler Thumbnails', RevealSpoilers], ['Image Loading', ImageLoader], ['Image Hover', ImageHover], ['Volume Control', Volume], ['WEBM Metadata', Metadata], ['Comment Expansion', ExpandComment], ['Thread Expansion', ExpandThread], ['Favicon', Favicon], ['Unread', Unread], ['Unread Line in Index', UnreadIndex], ['Quote Threading', QuoteThreading], ['Thread Stats', ThreadStats], ['Thread Updater', ThreadUpdater], ['Thread Watcher', ThreadWatcher], ['Thread Watcher (Menu)', ThreadWatcher.menu], ['Mark New IPs', MarkNewIPs], ['Index Navigation', Nav], ['Keybinds', Keybinds], ['Banner', Banner], ['Announcements', PSA], ['Flash Features', Flash], ['Reply Pruning', ReplyPruning], ['Mod Contact Links', ModContact]] }; return Main; diff --git a/builds/4chan-X.crx b/builds/4chan-X.crx index 99d70f83e0..bc344c6178 100644 Binary files a/builds/4chan-X.crx and b/builds/4chan-X.crx differ diff --git a/builds/4chan-X.meta.js b/builds/4chan-X.meta.js index 4063a501ee..f5104aec7e 100644 --- a/builds/4chan-X.meta.js +++ b/builds/4chan-X.meta.js @@ -1,10 +1,10 @@ // ==UserScript== // @name 4chan X -// @version 1.12.0.0 +// @version 1.14.23.1 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X -// @description Cross-browser userscript for maximum lurking on 4chan. +// @description 4chan X is a script that adds various features to anonymous imageboards. // @license MIT; https://github.com/ccd0/4chan-x/blob/master/LICENSE // @include http://boards.4chan.org/* // @include https://boards.4chan.org/* @@ -12,17 +12,85 @@ // @include https://sys.4chan.org/* // @include http://www.4chan.org/* // @include https://www.4chan.org/* +// @include http://boards.4channel.org/* +// @include https://boards.4channel.org/* +// @include http://sys.4channel.org/* +// @include https://sys.4channel.org/* +// @include http://www.4channel.org/* +// @include https://www.4channel.org/* // @include http://i.4cdn.org/* // @include https://i.4cdn.org/* -// @include https://www.google.com/recaptcha/api2/anchor?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @include https://www.google.com/recaptcha/api2/frame?*&k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @include http://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @include https://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @exclude http://www.4chan.org/pass -// @exclude https://www.4chan.org/pass -// @exclude http://www.4chan.org/pass?* -// @exclude https://www.4chan.org/pass?* -// @connect i.4cdn.org +// @include http://is.4chan.org/* +// @include https://is.4chan.org/* +// @include http://is2.4chan.org/* +// @include https://is2.4chan.org/* +// @include http://is.4channel.org/* +// @include https://is.4channel.org/* +// @include http://is2.4channel.org/* +// @include https://is2.4channel.org/* +// @include https://erischan.org/* +// @include https://www.erischan.org/* +// @include https://fufufu.moe/* +// @include https://gnfos.com/* +// @include https://himasugi.blog/* +// @include https://www.himasugi.blog/* +// @include https://kakashinenpo.com/* +// @include https://www.kakashinenpo.com/* +// @include https://kissu.moe/* +// @include https://www.kissu.moe/* +// @include https://lainchan.org/* +// @include https://www.lainchan.org/* +// @include https://merorin.com/* +// @include https://ota-ch.com/* +// @include https://www.ota-ch.com/* +// @include https://ponyville.us/* +// @include https://www.ponyville.us/* +// @include https://smuglo.li/* +// @include https://notso.smuglo.li/* +// @include https://smugloli.net/* +// @include https://smug.nepu.moe/* +// @include https://sportschan.org/* +// @include https://www.sportschan.org/* +// @include https://sushigirl.us/* +// @include https://www.sushigirl.us/* +// @include https://tvch.moe/* +// @exclude http://www.4chan.org/advertise +// @exclude https://www.4chan.org/advertise +// @exclude http://www.4chan.org/advertise?* +// @exclude https://www.4chan.org/advertise?* +// @exclude http://www.4chan.org/donate +// @exclude https://www.4chan.org/donate +// @exclude http://www.4chan.org/donate?* +// @exclude https://www.4chan.org/donate?* +// @exclude http://www.4channel.org/advertise +// @exclude https://www.4channel.org/advertise +// @exclude http://www.4channel.org/advertise?* +// @exclude https://www.4channel.org/advertise?* +// @exclude http://www.4channel.org/donate +// @exclude https://www.4channel.org/donate +// @exclude http://www.4channel.org/donate?* +// @exclude https://www.4channel.org/donate?* +// @connect 4chan.org +// @connect 4channel.org +// @connect 4cdn.org +// @connect 4chenz.github.io +// @connect archive.4plebs.org +// @connect warosu.org +// @connect desuarchive.org +// @connect boards.fireden.net +// @connect arch.b4k.co +// @connect archived.moe +// @connect thebarchive.com +// @connect archiveofsins.com +// @connect archive.palanq.win +// @connect eientei.xyz +// @connect api.clyp.it +// @connect api.dailymotion.com +// @connect api.github.com +// @connect soundcloud.com +// @connect api.streamable.com +// @connect vimeo.com +// @connect www.youtube.com // @connect * // @grant GM_getValue // @grant GM_setValue @@ -31,6 +99,12 @@ // @grant GM_addValueChangeListener // @grant GM_openInTab // @grant GM_xmlhttpRequest +// @grant GM.getValue +// @grant GM.setValue +// @grant GM.deleteValue +// @grant GM.listValues +// @grant GM.openInTab +// @grant GM.xmlHttpRequest // @run-at document-start // @updateURL https://www.4chan-x.net/builds/4chan-X.meta.js // @downloadURL https://www.4chan-x.net/builds/4chan-X.user.js diff --git a/builds/4chan-X.user.js b/builds/4chan-X.user.js index 1ecd88149c..53c04ccbce 100644 --- a/builds/4chan-X.user.js +++ b/builds/4chan-X.user.js @@ -1,10 +1,10 @@ // ==UserScript== // @name 4chan X -// @version 1.12.0.0 +// @version 1.14.23.1 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X -// @description Cross-browser userscript for maximum lurking on 4chan. +// @description 4chan X is a script that adds various features to anonymous imageboards. // @license MIT; https://github.com/ccd0/4chan-x/blob/master/LICENSE // @include http://boards.4chan.org/* // @include https://boards.4chan.org/* @@ -12,17 +12,85 @@ // @include https://sys.4chan.org/* // @include http://www.4chan.org/* // @include https://www.4chan.org/* +// @include http://boards.4channel.org/* +// @include https://boards.4channel.org/* +// @include http://sys.4channel.org/* +// @include https://sys.4channel.org/* +// @include http://www.4channel.org/* +// @include https://www.4channel.org/* // @include http://i.4cdn.org/* // @include https://i.4cdn.org/* -// @include https://www.google.com/recaptcha/api2/anchor?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @include https://www.google.com/recaptcha/api2/frame?*&k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @include http://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @include https://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc* -// @exclude http://www.4chan.org/pass -// @exclude https://www.4chan.org/pass -// @exclude http://www.4chan.org/pass?* -// @exclude https://www.4chan.org/pass?* -// @connect i.4cdn.org +// @include http://is.4chan.org/* +// @include https://is.4chan.org/* +// @include http://is2.4chan.org/* +// @include https://is2.4chan.org/* +// @include http://is.4channel.org/* +// @include https://is.4channel.org/* +// @include http://is2.4channel.org/* +// @include https://is2.4channel.org/* +// @include https://erischan.org/* +// @include https://www.erischan.org/* +// @include https://fufufu.moe/* +// @include https://gnfos.com/* +// @include https://himasugi.blog/* +// @include https://www.himasugi.blog/* +// @include https://kakashinenpo.com/* +// @include https://www.kakashinenpo.com/* +// @include https://kissu.moe/* +// @include https://www.kissu.moe/* +// @include https://lainchan.org/* +// @include https://www.lainchan.org/* +// @include https://merorin.com/* +// @include https://ota-ch.com/* +// @include https://www.ota-ch.com/* +// @include https://ponyville.us/* +// @include https://www.ponyville.us/* +// @include https://smuglo.li/* +// @include https://notso.smuglo.li/* +// @include https://smugloli.net/* +// @include https://smug.nepu.moe/* +// @include https://sportschan.org/* +// @include https://www.sportschan.org/* +// @include https://sushigirl.us/* +// @include https://www.sushigirl.us/* +// @include https://tvch.moe/* +// @exclude http://www.4chan.org/advertise +// @exclude https://www.4chan.org/advertise +// @exclude http://www.4chan.org/advertise?* +// @exclude https://www.4chan.org/advertise?* +// @exclude http://www.4chan.org/donate +// @exclude https://www.4chan.org/donate +// @exclude http://www.4chan.org/donate?* +// @exclude https://www.4chan.org/donate?* +// @exclude http://www.4channel.org/advertise +// @exclude https://www.4channel.org/advertise +// @exclude http://www.4channel.org/advertise?* +// @exclude https://www.4channel.org/advertise?* +// @exclude http://www.4channel.org/donate +// @exclude https://www.4channel.org/donate +// @exclude http://www.4channel.org/donate?* +// @exclude https://www.4channel.org/donate?* +// @connect 4chan.org +// @connect 4channel.org +// @connect 4cdn.org +// @connect 4chenz.github.io +// @connect archive.4plebs.org +// @connect warosu.org +// @connect desuarchive.org +// @connect boards.fireden.net +// @connect arch.b4k.co +// @connect archived.moe +// @connect thebarchive.com +// @connect archiveofsins.com +// @connect archive.palanq.win +// @connect eientei.xyz +// @connect api.clyp.it +// @connect api.dailymotion.com +// @connect api.github.com +// @connect soundcloud.com +// @connect api.streamable.com +// @connect vimeo.com +// @connect www.youtube.com // @connect * // @grant GM_getValue // @grant GM_setValue @@ -31,6 +99,12 @@ // @grant GM_addValueChangeListener // @grant GM_openInTab // @grant GM_xmlhttpRequest +// @grant GM.getValue +// @grant GM.setValue +// @grant GM.deleteValue +// @grant GM.listValues +// @grant GM.openInTab +// @grant GM.xmlHttpRequest // @run-at document-start // @updateURL https://www.4chan-x.net/builds/4chan-X.meta.js // @downloadURL https://www.4chan-x.net/builds/4chan-X.user.js @@ -121,11 +195,11 @@ 'use strict'; -var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, Build, CSS, Callbacks, Captcha, CatalogLinks, CatalogThread, Config, Connection, CrossOrigin, CustomCSS, DataBoard, DeleteLink, DownloadLink, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, Fetcher, FileInfo, Filter, Flash, Fourchan, Gallery, Get, Header, IDColor, IDHighlight, ImageCommon, ImageExpand, ImageHover, ImageLoader, Index, Keybinds, Linkify, Main, MarkNewIPs, Menu, Metadata, Nav, NormalizeURL, Notice, PSAHiding, PassLink, Polyfill, Post, PostHiding, PostSuccessful, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, ReplyPruning, Report, ReportLink, RevealSpoilers, Sauce, Settings, SimpleDict, Thread, ThreadHiding, ThreadLinks, ThreadStats, ThreadUpdater, ThreadWatcher, Time, UI, Unread, Volume; +var $, $$, Anonymize, AntiAutoplay, ArchiveLink, Banner, Board, BoardConfig, CSS, Callbacks, Captcha, CatalogLinks, CatalogThread, CatalogThreadNative, Config, Connection, CopyTextLink, CrossOrigin, CustomCSS, DataBoard, DeleteLink, DownloadLink, Embedding, ExpandComment, ExpandThread, FappeTyme, Favicon, Fetcher, FileInfo, Filter, Flash, Fourchan, Gallery, Get, Header, IDColor, IDHighlight, IDPostCount, ImageCommon, ImageExpand, ImageHost, ImageHover, ImageLoader, Index, Keybinds, Linkify, Main, MarkNewIPs, Menu, Metadata, ModContact, Nav, NormalizeURL, Notice, PSA, PSAHiding, PassLink, PassMessage, Polyfill, Post, PostHiding, PostJumper, PostRedirect, PostSuccessful, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, QuoteStrikeThrough, QuoteThreading, QuoteYou, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, ReplyPruning, Report, ReportLink, RevealSpoilers, SW, Sauce, Settings, ShimSet, SimpleDict, Site, Test, Thread, ThreadHiding, ThreadLinks, ThreadStats, ThreadUpdater, ThreadWatcher, Time, Tinyboard, UI, Unread, UnreadIndex, Volume; var Conf, E, c, d, doc, docSet, g; -Conf = {}; +Conf = Object.create(null); c = console; d = document; doc = d.documentElement; @@ -136,9 +210,10 @@ docSet = function() { }; g = { - VERSION: '1.12.0.0', + VERSION: '1.14.23.1', NAMESPACE: '4chan X.', - boards: {} + sites: Object.create(null), + boards: Object.create(null) }; E = (function() { @@ -169,25 +244,24 @@ E.cat = function(templates) { return html; }; -E.url = function(content) { - return "data:text/html;charset=utf-8," + encodeURIComponent(content.innerHTML); -}; - Config = (function() { var Config; Config = { main: { 'Miscellaneous': { + 'Redirect to HTTPS': [true, 'Redirect to the HTTPS version of 4chan.'], 'JSON Index': [true, 'Replace the original board index with one supporting searching, sorting, infinite scrolling, and a catalog mode.'], 'Use 4chan X Catalog': [true, 'Link to 4chan X\'s catalog instead of the native 4chan one.', 1], 'Index Refresh Notifications': [false, 'Show a notice at the top of the page when the index is refreshed.', 1], + 'Follow Cursor': [true, 'Image Hover and Quote Preview move with the mouse cursor.'], 'Open Threads in New Tab': [false, 'Make links to threads in the index / 4chan X catalog open in a new tab.'], 'External Catalog': [false, 'Link to external catalog instead of the internal one.'], 'Catalog Links': [false, 'Add toggle link in header menu to turn Navigation links into links to each board\'s catalog.'], 'Announcement Hiding': [true, 'Add button to hide 4chan announcements.'], 'Desktop Notifications': [true, 'Enables desktop notifications across various 4chan X features.'], '404 Redirect': [true, 'Redirect dead threads and images to the archives.'], + 'Archive Report': [true, 'Enable reporting posts to supported archives.'], 'Exempt Archives from Encryption': [true, 'Permit loading content from, and warningless redirects to, HTTP-only archives from HTTPS pages.'], 'Keybinds': [true, 'Bind actions to keyboard shortcuts.'], 'Time Formatting': [true, 'Localize and format timestamps.'], @@ -198,13 +272,16 @@ Config = (function() { 'Thread Expansion': [true, 'Add buttons to expand threads.'], 'Index Navigation': [false, 'Add buttons to navigate between threads.'], 'Reply Navigation': [false, 'Add buttons to navigate to top / bottom of thread.'], + 'Unique ID and Capcode Navigation': [false, 'Add buttons to navigate to posts having the same unique ID or capcode.'], 'Custom Board Titles': [true, 'Allow editing of the board title and subtitle by ctrl/\u2318+clicking them.'], 'Persistent Custom Board Titles': [false, 'Force custom board titles to be persistent, even if the board titles are updated.', 1], 'Show Updated Notifications': [true, 'Show notifications when 4chan X is successfully updated.'], 'Color User IDs': [true, 'Assign unique colors to user IDs on boards that use them'], + 'Count Posts by ID': [true, 'Display number of posts in the thread when hovering over an ID.'], 'Remove Spoilers': [false, 'Remove all spoilers in text.'], 'Reveal Spoilers': [false, 'Indicate spoilers if Remove Spoilers is enabled, or make the text appear hovered if Remove Spoiler is disabled.'], 'Normalize URL': [true, 'Rewrite the URL of the current page, removing slugs and excess slashes, and changing /res/ to /thread/.'], + 'Work around CORB Bug': [true, 'Leave this checked until your garbage browser is fixed.'], 'Disable Autoplaying Sounds': [false, 'Prevent sounds on the page from autoplaying.'], 'Disable Native Extension': [true, '4chan X is NOT designed to work with the native extension.'], 'Enable Native Flash Embedding': [true, 'Activate the native extension\'s Flash embedding if the native extension is disabled.'] @@ -212,6 +289,7 @@ Config = (function() { 'Linkification': { 'Linkify': [true, 'Convert text into links where applicable.'], 'Link Title': [true, 'Replace the link of a supported site with its actual title.', 1], + 'Cover Preview': [true, 'Show preview of supported links on hover.', 1], 'Embedding': [true, 'Embed supported services. Note: Some services don\'t work on HTTPS.', 1], 'Auto-embed': [false, 'Auto-embed Linkify Embeds.', 2], 'Floating Embeds': [false, 'Embed content in a frame that remains in place when the page is scrolled.', 2] @@ -220,6 +298,8 @@ Config = (function() { 'Anonymize': [false, 'Make everyone Anonymous.'], 'Filter': [true, 'Self-moderation placebo.'], 'Filtered Backlinks': [false, 'When enabled, shows backlinks to filtered posts with a line-through decoration. Otherwise, hides the backlinks.', 1], + 'Filter in Native Catalog': [true, 'Apply 4chan X filters in native catalog.', 1], + 'MD5 Quick Filter Notifications': [true, 'Show notification when quick filtering MD5s using the button or keybind.', 1], 'Recursive Hiding': [true, 'Hide replies of hidden posts, recursively.'], 'Thread Hiding Buttons': [true, 'Add buttons to hide entire threads.'], 'Reply Hiding Buttons': [true, 'Add buttons to hide single replies.'], @@ -228,8 +308,8 @@ Config = (function() { 'Images and Videos': { 'Image Expansion': [true, 'Expand images / videos.'], 'Image Hover': [true, 'Show full image / video on mouseover.'], - 'Image Hover in Catalog': [false, 'Show full image / video on mouseover in 4chan X catalog.'], - 'Gallery': [true, 'Adds a simple and cute image gallery.'], + 'Image Hover in Catalog': [true, 'Show full image / video on mouseover in 4chan X catalog.'], + 'Gallery': [true, 'Adds a simple and cute image gallery. Has more options in the gallery menu.'], 'Fullscreen Gallery': [false, 'Open gallery in fullscreen mode.', 1], 'PDF in Gallery': [false, 'Show PDF files in gallery.', 1], 'Sauce': [true, 'Add sauce links to images.'], @@ -238,11 +318,12 @@ Config = (function() { 'Replace GIF': [false, 'Replace gif thumbnails with the actual image.'], 'Replace JPG': [false, 'Replace jpg thumbnails with the actual image.'], 'Replace PNG': [false, 'Replace png thumbnails with the actual image.'], - 'Replace WEBM': [false, 'Replace webm thumbnails with the actual webm video. Probably will degrade browser performance ;)'], - 'Image Prefetching': [false, 'Add link in header menu to turn on image preloading.'], + 'Replace WEBM': [false, 'Replace webm, mp4, and ogv thumbnails with the actual video. Probably will degrade browser performance ;)'], + 'Image Prefetching': [true, 'Add a shortcut icon to the header to turn on image preloading.'], 'Fappe Tyme': [true, 'Hide posts without images when header menu item is checked. *hint* *hint*'], 'Werk Tyme': [true, 'Hide all post images when header menu item is checked.'], 'Autoplay': [true, 'Videos begin playing immediately when opened.'], + 'Restart when Opened': [false, 'Restart GIFs and WebMs when you hover over or expand them.'], 'Show Controls': [true, 'Show controls on videos expanded inline.'], 'Click Passthrough': [false, 'Clicks on videos trigger your browser\'s default behavior. Videos can be contracted with button / dragging to the left.', 1], 'Allow Sound': [true, 'Open videos with the sound unmuted.'], @@ -253,12 +334,13 @@ Config = (function() { 'Menu': { 'Menu': [true, 'Add a drop-down menu to posts.'], 'Report Link': [true, 'Add a report link to the menu.', 1], + 'Copy Text Link': [true, 'Add a link to copy the post\'s text.', 1], 'Thread Hiding Link': [true, 'Add a link to hide entire threads.', 1], 'Reply Hiding Link': [true, 'Add a link to hide single replies.', 1], 'Delete Link': [true, 'Add post and image deletion links to the menu.', 1], 'Archive Link': [true, 'Add an archive link to the menu.', 1], 'Edit Link': [true, 'Add a link to edit the image in Tegaki, /i/\'s painting program. Requires Quick Reply.', 1], - 'Download Link': [true, 'Add a download with original filename link to the menu.', 1] + 'Download Link': [false, 'Add a download with original filename link to the menu.', 1] }, 'Monitoring': { 'Thread Updater': [true, 'Fetch and insert new replies. Has more options in the header menu and the "Advanced" tab.'], @@ -269,20 +351,24 @@ Config = (function() { 'Unread Line': [true, 'Show a line to distinguish read posts from unread ones.'], 'Remember Last Read Post': [true, 'Remember how far you\'ve read after you close the thread.'], 'Scroll to Last Read Post': [true, 'Scroll back to the last read post when reopening a thread.', 1], + 'Unread Line in Index': [false, 'Show a line between read and unread posts in threads in the index.', 1], + 'Remove Thread Excerpt': [false, 'Replace the excerpt of the thread in the tab title with the board title.'], 'Thread Stats': [true, 'Display reply and image count.'], 'IP Count in Stats': [true, 'Display the unique IP count in the thread stats.', 1], 'Page Count in Stats': [true, 'Display the page count in the thread stats.', 1], 'Updater and Stats in Header': [true, 'Places the thread updater and thread stats in the header instead of floating them.'], - 'Thread Watcher': [true, 'Bookmark threads.'], + 'Thread Watcher': [true, 'Bookmark threads. Has more options in the thread watcher menu.'], 'Fixed Thread Watcher': [true, 'Makes the thread watcher scroll with the page.', 1], - 'Toggleable Thread Watcher': [true, 'Adds a shortcut for the thread watcher and hides the watcher by default.', 1], + 'Persistent Thread Watcher': [false, 'The thread watcher will be visible when the page is loaded.', 1], 'Mark New IPs': [false, 'Label each post from a new IP with the thread\'s current IP count.'], - 'Reply Pruning': [true, 'Hide old replies in long threads. Number of replies shown can be set from header menu.'] + 'Reply Pruning': [true, 'Add option in header menu to hide old replies in long threads. Activated by default in stickies.'], + 'Prune All Threads': [false, 'Activate Reply Pruning by default in all threads.', 1] }, 'Posting and Captchas': { 'Quick Reply': [true, 'All-in-one form to reply, create threads, automate dumping and more.'], 'Persistent QR': [false, 'The Quick reply won\'t disappear after posting.', 1], - 'Auto Hide QR': [true, 'Automatically hide the quick reply when posting.', 1], + 'Auto Hide QR': [true, 'Automatically hide the quick reply when posting.', 2], + 'Open Post in New Tab': [true, 'Open new threads in a new tab, and open replies in a new tab if you\'re not already in the thread.', 1], 'Remember QR Size': [false, 'Remember the size of the Quick reply.', 1], 'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.', 1], 'Randomize Filename': [false, 'Set the filename to a random timestamp within the past year. Disabled on /f/.', 1], @@ -292,15 +378,13 @@ Config = (function() { 'Posting Success Notifications': [true, 'Show notifications on successful post creation or file uploading.', 1], 'Auto-load captcha': [false, 'Automatically load the captcha in the QR even if your post is empty.', 1], 'Post on Captcha Completion': [false, 'Submit the post immediately when the captcha is completed.', 1], - 'Captcha Fixes': [true, 'Make captcha easier to use, especially with the keyboard.'], - 'Use Recaptcha v1': [false, 'Use the old text version of Recaptcha in the post form.'], - 'Use Recaptcha v1 in Reports': [false, 'Use the text captcha in the report window.'], - 'Force Noscript Captcha': [false, 'Use the non-Javascript fallback captcha even if Javascript is enabled (Recaptcha v2 only).'], + 'Force Noscript Captcha': [false, 'Use the non-Javascript fallback captcha even if Javascript is enabled.'], 'Pass Link': [false, 'Add a 4chan Pass login link to the bottom of the page.'] }, 'Quote Links': { 'Quote Backlinks': [true, 'Add quote backlinks.'], 'OP Backlinks': [true, 'Add backlinks to the OP.', 1], + 'Bottom Backlinks': [false, 'Place backlinks at the bottom of posts.', 1], 'Quote Inlining': [true, 'Inline quoted post on click.'], 'Inline Cross-thread Quotes Only': [false, 'Don\'t inline quote links when the posts are visible in the thread.', 1], 'Quote Hash Navigation': [false, 'Include an extra link after quotes for autoscrolling to quoted posts.', 1], @@ -324,6 +408,7 @@ Config = (function() { 'Expand spoilers': [true, 'Expand all images along with spoilers.'], 'Expand videos': [true, 'Expand all images also expands videos.'], 'Expand from here': [false, 'Expand all images only from current position to thread end.'], + 'Expand thread only': [false, 'In index, expand all images only within the current thread.'], 'Advance on contract': [false, 'Advance to next post when contracting an expanded image.'] }, gallery: { @@ -338,17 +423,23 @@ Config = (function() { threadWatcher: { 'Current Board': [false, 'Only show watched threads from the current board.'], 'Auto Update Thread Watcher': [true, 'Periodically check status of watched threads.'], - 'Auto Watch': [false, 'Automatically watch threads you start.'], - 'Auto Watch Reply': [false, 'Automatically watch threads you reply to.'], + 'Auto Watch': [true, 'Automatically watch threads you start.'], + 'Auto Watch Reply': [true, 'Automatically watch threads you reply to.'], 'Auto Prune': [false, 'Automatically remove dead threads.'], - 'Show Unread Count': [true, 'Show number of unread posts in watched threads.'] + 'Show Page': [true, 'Show what page watched threads are on.'], + 'Show Unread Count': [true, 'Show number of unread posts in watched threads.'], + 'Show Site Prefix': [true, 'When multiple sites are shown in the thread watcher, add a prefix to board names to distinguish them.'], + 'Require OP Quote Link': [false, 'For purposes of thread watcher highlighting, only consider posts with a quote link to the OP as replies to the OP.'] }, filter: { + general: '', postID: "# Highlight dubs on [s4s]:\n#/(\\d)\\1$/;highlight;top:no;boards:s4s", name: "# Filter any namefags:\n#/^(?!Anonymous$)/", uniqueID: "# Filter a specific ID:\n#/Txhvk1Tl/", tripcode: "# Filter any tripfag\n#/^!/", capcode: "# Set a custom class for mods:\n#/Mod$/;highlight:mod;op:yes\n# Set a custom class for admins:\n#/Admin$/;highlight:admin;op:yes", + pass: "# Filter anyone using since4pass:\n#/./", + email: '', subject: "# Filter Generals on /v/:\n#/general/i;boards:v;op:only", comment: "# Filter Stallman copypasta on /g/:\n#/what you\'re refer+ing to as linux/i;boards:g\n# Filter posts with 20 or more quote links:\n#/(?:>>\\d(?:(?!>>\\d)[^])*){20}/\n# Filter posts like T H I S / H / I / S:\n#/^>?\\s?\\w\\s?(\\w)\\s?(\\w)\\s?(\\w).*$[\\s>]+\\1[\\s>]+\\2[\\s>]+\\3/im", flag: '', @@ -357,7 +448,7 @@ Config = (function() { filesize: '', MD5: '' }, - sauces: "# Reverse image search:\nhttps://www.google.com/searchbyimage?image_url=%IMG&safe=off\n#https://www.yandex.com/images/search?rpt=imageview&img_url=%IMG\n#//tineye.com/search?url=%IMG\n\n# Specialized reverse image search:\n//iqdb.org/?url=%IMG\nhttps://whatanime.ga/?auto&url=%IMG;text:wait\n#//3d.iqdb.org/?url=%IMG\n#//saucenao.com/search.php?url=%IMG\n\n# \"View Same\" in archives:\nhttp://eye.swfchan.com/search/?q=%name;types:swf\n#https://desustorage.org/_/search/image/%sMD5/\n#https://archive.4plebs.org/_/search/image/%sMD5/\n#https://boards.fireden.net/_/search/image/%sMD5/\n#https://foolz.fireden.net/_/search/image/%sMD5/\n\n# Other tools:\n#http://regex.info/exif.cgi?imgurl=%URL\n#//imgops.com/%URL;types:gif,jpg,png\n#//www.gif-explode.com/%URL;types:gif", + sauces: "# Known filename formats:\nhttps://www.pixiv.net/member_illust.php?mode=medium&illust_id=%$1;regexp:/^(\\d+)_p\\d+/\njavascript:void(open(\"https://www.deviantart.com/\"+%$1.replace(/_/g,\"-\")+\"/art/\"+parseInt(%$2,36)));regexp:/^\\w+_by_(\\w+)[_-]d([\\da-z]{6})\\b/\nhttps://imgur.com/%$1;regexp:/^(?![a-zA-Z][a-z]{6})(?![A-Z]{7})(?!\\d{7})([\\da-zA-Z]{7})(?: \\(\\d+\\))?\\.\\w+$/\nhttps://flickr.com/photo.gne?id=%$1;regexp:/^(\\d+)_[\\da-f]{10}(?:_\\w)*\\b/\nhttps://www.facebook.com/photo.php?fbid=%$1;regexp:/^\\d+_(\\d+)_\\d+_[no]\\b/\n\n# Reverse image search:\nhttps://www.google.com/searchbyimage?sbisrc=4chanx&image_url=%IMG&safe=off\nhttps://yandex.com/images/search?rpt=imageview&url=%IMG\n#//tineye.com/search?url=%IMG\n#//www.bing.com/images/search?q=imgurl:%IMG&view=detailv2&iss=sbi#enterInsights\n#https://lens.google.com/uploadbyurl?url=%IMG;text:lens\n\n# Specialized reverse image search:\n//iqdb.org/?url=%IMG\nhttps://trace.moe/?auto&url=%IMG;text:wait\n#//3d.iqdb.org/?url=%IMG\n#//saucenao.com/search.php?url=%IMG\n\n# \"View Same\" in archives:\nhttp://eye.swfchan.com/search/?q=%name;types:swf\n#https://desuarchive.org/_/search/image/%sMD5/\n#https://archive.4plebs.org/_/search/image/%sMD5/\n#https://boards.fireden.net/_/search/image/%sMD5/\n#https://foolz.fireden.net/_/search/image/%sMD5/\n\n# Other tools:\n#http://exif.regex.info/exif.cgi?imgurl=%URL\n#//imgops.com/start?url=%URL;types:gif,jpg,png\n#//www.gif-explode.com/%URL;types:gif", FappeT: { werk: false }, @@ -366,10 +457,12 @@ Config = (function() { 'Index Mode': 'paged', 'Previous Index Mode': 'paged', 'Index Size': 'small', - 'Show Replies': true, - 'Pin Watched Threads': false, - 'Anchor Hidden Threads': true, - 'Refreshed Navigation': false + 'Show Replies': [true, 'Show replies in the index, and also in the catalog if "Catalog hover expand" is checked.'], + 'Catalog Hover Expand': [false, 'Expand the comment and show more details when you hover over a thread in the catalog.'], + 'Catalog Hover Toggle': [true, 'Turn "Catalog hover expand" on and off by clicking in the catalog.'], + 'Pin Watched Threads': [false, 'Move watched threads to the start of the index.'], + 'Anchor Hidden Threads': [true, 'Move hidden threads to the end of the index.'], + 'Refreshed Navigation': [false, 'Refresh index when navigating through pages.'] }, Header: { 'Fixed Header': true, @@ -383,20 +476,23 @@ Config = (function() { 'Custom Board Navigation': true }, archives: { - archiveLists: 'https://mayhemydg.github.io/archives.json/archives.json', + archiveLists: 'https://4chenz.github.io/archives.json/archives.json', lastarchivecheck: 0, archiveAutoUpdate: true }, - boardnav: "[ toggle-all ]\na-replace\nc-replace\ng-replace\nk-replace\nv-replace\nvg-replace\nvr-replace\nck-replace\nco-replace\nfit-replace\njp-replace\nmu-replace\nsp-replace\ntv-replace\nvp-replace\n[external-text:\"FAQ\",\"https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions\"]", + externalCatalogURLs: "//catalog.neet.tv/%board/;boards:4chan.org: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", + boardnav: "[ toggle-all ]\n[current-index-text:\"Index\"\ncurrent-catalog-text:\"Catalog\"\ncurrent-expired-text:\"Expired\"\ncurrent-archive-text:\"Archive\"]\n[external-text:\"FAQ\",\"https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions\"]", QR: { 'QR.personas': "#options:\"sage\";boards:jp;always", sjisPreview: false }, - jsWhitelist: 'http://s.4cdn.org\nhttps://s.4cdn.org\nhttp://www.google.com\nhttps://www.google.com\nhttps://www.gstatic.com\nhttp://cdn.mathjax.org\nhttps://cdn.mathjax.org\n\'self\'\n\'unsafe-inline\'\n\'unsafe-eval\'', + jsWhitelist: '', captchaLanguage: '', time: '%m/%d/%y(%a)%H:%M:%S', + timeLocale: '', backlink: '>>%id', - fileInfo: '%l (%p%s, %r%g)', + pastedname: 'file', + fileInfo: '%l %d (%p%s, %r%g)', favicon: 'ferongr', usercss: "/* Board title rice */\ndiv.boardTitle {\n font-weight: 400 !important;\n}\n:root.yotsuba div.boardTitle {\n font-family: sans-serif !important;\n text-shadow: 1px 1px 1px rgba(100,0,0,0.6);\n}\n:root.yotsuba-b div.boardTitle {\n font-family: sans-serif !important;\n text-shadow: 1px 1px 1px rgba(105,10,15,0.6);\n}\n:root.photon div.boardTitle {\n font-family: sans-serif !important;\n text-shadow: 1px 1px 1px rgba(0,74,153,0.6);\n}\n:root.tomorrow div.boardTitle {\n font-family: sans-serif !important;\n text-shadow: 1px 1px 1px rgba(167,170,168,0.6);\n}\n", hotkeys: { @@ -412,15 +508,27 @@ Config = (function() { 'Math tags': ['Alt+m', 'Insert math tags.'], 'SJIS tags': ['Alt+a', 'Insert SJIS tags.'], 'Toggle sage': ['Alt+s', 'Toggle sage in options field.'], + 'Toggle Cooldown': ['Alt+Comma', 'Toggle custom cooldown timer.'], + 'Post from URL': ['Alt+l', 'Post from URL.'], + 'Add new post': ['Alt+n', 'Add new post to the QR dump list.'], 'Submit QR': ['Ctrl+Enter', 'Submit post.'], 'Watch': ['w', 'Watch thread.'], 'Update': ['r', 'Update the thread / refresh the index.'], 'Update thread watcher': ['Shift+r', 'Manually refresh thread watcher.'], + 'Toggle thread watcher': ['t', 'Toggle visibility of thread watcher.'], + 'Toggle threading': ['Shift+t', 'Toggle threading.'], + 'Mark thread read': ['Ctrl+0', 'Mark thread read from index (requires "Unread Line in Index").'], 'Expand image': ['Shift+e', 'Expand selected image.'], 'Expand images': ['e', 'Expand all images.'], 'Open Gallery': ['g', 'Opens the gallery.'], + 'Next Gallery Image': ['Right', 'Go to the next image in gallery mode.'], + 'Previous Gallery Image': ['Left', 'Go to the previous image in gallery mode.'], + 'Advance Gallery': ['Enter', 'Go to next image or, if Autoplay is off, play video.'], 'Pause': ['p', 'Pause/play videos in the gallery.'], 'Slideshow': ['Ctrl+Right', 'Toggle the gallery slideshow mode.'], + 'Rotate image clockwise': ['Shift+Right', 'Rotate image clockwise in gallery.'], + 'Rotate image anticlockwise': ['Shift+Left', 'Rotate image anticlockwise in gallery.'], + 'Download Gallery Image': ['Shift+j', 'Download current image in gallery.'], 'fappeTyme': ['f', 'Toggle Fappe Tyme.'], 'werkTyme': ['Shift+w', 'Toggle Werk Tyme.'], 'Front page': ['1', 'Jump to front page.'], @@ -442,6 +550,7 @@ Config = (function() { 'Previous reply': ['k', 'Select previous reply.'], 'Deselect reply': ['Shift+d', 'Deselect reply.'], 'Hide': ['x', 'Hide thread.'], + 'Quick Filter MD5': ['5', 'Add the MD5 of the selected image to the filter list.'], 'Previous Post Quoting You': ['Alt+Up', 'Scroll to the previous post that quotes you.'], 'Next Post Quoting You': ['Alt+Down', 'Scroll to the next post that quotes you.'] }, @@ -455,13 +564,26 @@ Config = (function() { 'Auto Update': [true, 'Automatically fetch new posts.'], 'Optional Increase': [false, 'Increase the intervals between updates on threads without new posts.'] }, - 'Interval': 30 + 'Interval': 5 }, customCooldown: 0, customCooldownEnabled: true, 'Thread Quotes': false, 'Max Replies': 1000, - 'Autohiding Scrollbar': false + 'Autohiding Scrollbar': false, + position: { + 'embedding.position': 'top: 50px; right: 0px;', + 'thread-stats.position': 'bottom: 0px; right: 0px;', + 'updater.position': 'bottom: 0px; left: 0px;', + 'thread-watcher.position': 'top: 50px; left: 0px;', + 'qr.position': 'top: 50px; right: 0px;' + }, + fourchanImageHost: 'i.4cdn.org', + hiddenPSAList: [{}], + knownBanners: '0.jpg,1.jpg,2.jpg,4.jpg,6.jpg,7.jpg,8.jpg,9.jpg,10.jpg,11.jpg,12.jpg,13.jpg,14.jpg,16.jpg,17.jpg,18.jpg,19.jpg,20.jpg,21.jpg,22.jpg,24.jpg,25.jpg,26.jpg,28.jpg,29.jpg,33.jpg,38.jpg,39.jpg,43.jpg,44.jpg,45.jpg,46.jpg,47.jpg,52.jpg,54.jpg,57.jpg,59.jpg,60.jpg,61.jpg,64.jpg,66.jpg,67.jpg,69.jpg,71.jpg,72.jpg,76.jpg,77.jpg,81.jpg,82.jpg,83.jpg,84.jpg,88.jpg,90.jpg,91.jpg,96.jpg,98.jpg,99.jpg,100.jpg,104.jpg,106.jpg,116.jpg,119.jpg,137.jpg,140.jpg,148.jpg,149.jpg,150.jpg,154.jpg,156.jpg,157.jpg,158.jpg,159.jpg,161.jpg,162.jpg,164.jpg,165.jpg,166.jpg,167.jpg,168.jpg,169.jpg,170.jpg,171.jpg,172.jpg,173.jpg,174.jpg,175.jpg,176.jpg,178.jpg,179.jpg,180.jpg,181.jpg,182.jpg,183.jpg,186.jpg,189.jpg,190.jpg,192.jpg,193.jpg,194.jpg,197.jpg,198.jpg,200.jpg,201.jpg,202.jpg,203.jpg,205.jpg,206.jpg,207.jpg,208.jpg,210.jpg,213.jpg,214.jpg,215.jpg,216.jpg,218.jpg,219.jpg,220.jpg,221.jpg,222.jpg,223.jpg,224.jpg,227.jpg,0.png,1.png,2.png,3.png,5.png,6.png,9.png,10.png,11.png,12.png,14.png,16.png,19.png,20.png,21.png,22.png,23.png,24.png,26.png,27.png,28.png,29.png,30.png,31.png,32.png,33.png,34.png,37.png,39.png,40.png,41.png,42.png,43.png,44.png,45.png,48.png,49.png,50.png,51.png,52.png,53.png,57.png,58.png,59.png,64.png,66.png,67.png,68.png,69.png,70.png,71.png,72.png,76.png,78.png,79.png,81.png,82.png,85.png,86.png,87.png,89.png,95.png,98.png,100.png,101.png,102.png,105.png,106.png,107.png,109.png,110.png,111.png,112.png,113.png,114.png,115.png,116.png,118.png,119.png,120.png,121.png,122.png,123.png,126.png,128.png,130.png,134.png,136.png,138.png,139.png,140.png,142.png,145.png,146.png,149.png,150.png,151.png,152.png,153.png,154.png,155.png,156.png,157.png,158.png,159.png,160.png,163.png,164.png,165.png,166.png,167.png,168.png,169.png,170.png,171.png,172.png,173.png,174.png,178.png,179.png,180.png,181.png,182.png,184.png,186.png,188.png,190.png,192.png,193.png,194.png,195.png,196.png,197.png,198.png,200.png,202.png,203.png,205.png,206.png,207.png,209.png,212.png,213.png,214.png,216.png,217.png,218.png,219.png,220.png,221.png,222.png,223.png,224.png,225.png,226.png,229.png,231.png,232.png,233.png,234.png,235.png,237.png,238.png,239.png,240.png,241.png,242.png,244.png,245.png,246.png,247.png,248.png,249.png,250.png,253.png,254.png,255.png,256.png,257.png,258.png,259.png,260.png,262.png,268.png,0.gif,1.gif,2.gif,3.gif,4.gif,5.gif,6.gif,7.gif,8.gif,9.gif,10.gif,12.gif,13.gif,14.gif,15.gif,16.gif,18.gif,19.gif,20.gif,21.gif,22.gif,23.gif,24.gif,28.gif,29.gif,30.gif,33.gif,34.gif,35.gif,36.gif,37.gif,39.gif,40.gif,42.gif,44.gif,45.gif,46.gif,48.gif,50.gif,52.gif,54.gif,55.gif,57.gif,58.gif,59.gif,60.gif,61.gif,63.gif,64.gif,66.gif,67.gif,68.gif,69.gif,70.gif,72.gif,73.gif,75.gif,76.gif,77.gif,78.gif,80.gif,81.gif,82.gif,83.gif,86.gif,87.gif,88.gif,92.gif,93.gif,94.gif,95.gif,96.gif,97.gif,98.gif,99.gif,100.gif,101.gif,102.gif,103.gif,104.gif,105.gif,106.gif,108.gif,109.gif,110.gif,111.gif,112.gif,113.gif,115.gif,116.gif,117.gif,118.gif,119.gif,120.gif,122.gif,123.gif,124.gif,127.gif,129.gif,130.gif,131.gif,134.gif,135.gif,136.gif,138.gif,139.gif,141.gif,144.gif,146.gif,148.gif,149.gif,153.gif,154.gif,155.gif,157.gif,158.gif,159.gif,160.gif,161.gif,162.gif,164.gif,166.gif,167.gif,168.gif,169.gif,170.gif,171.gif,172.gif,173.gif,174.gif,175.gif,176.gif,177.gif,178.gif,181.gif,182.gif,183.gif,185.gif,186.gif,187.gif,188.gif,189.gif,190.gif,191.gif,192.gif,193.gif,195.gif,196.gif,197.gif,200.gif,201.gif,202.gif,203.gif,204.gif,205.gif,206.gif,207.gif,208.gif,209.gif,210.gif,211.gif,212.gif,213.gif,214.gif,215.gif,216.gif,217.gif,219.gif,220.gif,221.gif,222.gif,224.gif,225.gif,226.gif,227.gif,228.gif,230.gif,232.gif,233.gif,234.gif,235.gif,238.gif,240.gif,241.gif,243.gif,244.gif,245.gif,246.gif,247.gif,249.gif,250.gif,251.gif,253.gif', + passMessageClosed: false, + 'Prerequest Captcha': false, + 'PSAseen': [[]] }; return Config; @@ -472,12 +594,12 @@ CSS = { boards: "/*!\n\ - * Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome\n\ + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome\n\ * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)\n\ */\n\ @font-face {\n\ font-family: FontAwesome;\n\ - src: url('data:application/font-woff;base64,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') format('woff');\n\ + src: url('data:application/font-woff;base64,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') format('woff');\n\ font-weight: 400;\n\ font-style: normal;\n\ }\n\ @@ -1005,7 +1127,7 @@ boards: .fa-optin-monster:before {content: \"\\f23c\";}\n\ .fa-opencart:before {content: \"\\f23d\";}\n\ .fa-expeditedssl:before {content: \"\\f23e\";}\n\ -.fa-battery-4:before, .fa-battery-full:before {content: \"\\f240\";}\n\ +.fa-battery-4:before, .fa-battery:before, .fa-battery-full:before {content: \"\\f240\";}\n\ .fa-battery-3:before, .fa-battery-three-quarters:before {content: \"\\f241\";}\n\ .fa-battery-2:before, .fa-battery-half:before {content: \"\\f242\";}\n\ .fa-battery-1:before, .fa-battery-quarter:before {content: \"\\f243\";}\n\ @@ -1115,6 +1237,47 @@ boards: .fa-themeisle:before {content: \"\\f2b2\";}\n\ .fa-google-plus-circle:before, .fa-google-plus-official:before {content: \"\\f2b3\";}\n\ .fa-fa:before, .fa-font-awesome:before {content: \"\\f2b4\";}\n\ +.fa-handshake-o:before {content: \"\\f2b5\";}\n\ +.fa-envelope-open:before {content: \"\\f2b6\";}\n\ +.fa-envelope-open-o:before {content: \"\\f2b7\";}\n\ +.fa-linode:before {content: \"\\f2b8\";}\n\ +.fa-address-book:before {content: \"\\f2b9\";}\n\ +.fa-address-book-o:before {content: \"\\f2ba\";}\n\ +.fa-vcard:before, .fa-address-card:before {content: \"\\f2bb\";}\n\ +.fa-vcard-o:before, .fa-address-card-o:before {content: \"\\f2bc\";}\n\ +.fa-user-circle:before {content: \"\\f2bd\";}\n\ +.fa-user-circle-o:before {content: \"\\f2be\";}\n\ +.fa-user-o:before {content: \"\\f2c0\";}\n\ +.fa-id-badge:before {content: \"\\f2c1\";}\n\ +.fa-drivers-license:before, .fa-id-card:before {content: \"\\f2c2\";}\n\ +.fa-drivers-license-o:before, .fa-id-card-o:before {content: \"\\f2c3\";}\n\ +.fa-quora:before {content: \"\\f2c4\";}\n\ +.fa-free-code-camp:before {content: \"\\f2c5\";}\n\ +.fa-telegram:before {content: \"\\f2c6\";}\n\ +.fa-thermometer-4:before, .fa-thermometer:before, .fa-thermometer-full:before {content: \"\\f2c7\";}\n\ +.fa-thermometer-3:before, .fa-thermometer-three-quarters:before {content: \"\\f2c8\";}\n\ +.fa-thermometer-2:before, .fa-thermometer-half:before {content: \"\\f2c9\";}\n\ +.fa-thermometer-1:before, .fa-thermometer-quarter:before {content: \"\\f2ca\";}\n\ +.fa-thermometer-0:before, .fa-thermometer-empty:before {content: \"\\f2cb\";}\n\ +.fa-shower:before {content: \"\\f2cc\";}\n\ +.fa-bathtub:before, .fa-s15:before, .fa-bath:before {content: \"\\f2cd\";}\n\ +.fa-podcast:before {content: \"\\f2ce\";}\n\ +.fa-window-maximize:before {content: \"\\f2d0\";}\n\ +.fa-window-minimize:before {content: \"\\f2d1\";}\n\ +.fa-window-restore:before {content: \"\\f2d2\";}\n\ +.fa-times-rectangle:before, .fa-window-close:before {content: \"\\f2d3\";}\n\ +.fa-times-rectangle-o:before, .fa-window-close-o:before {content: \"\\f2d4\";}\n\ +.fa-bandcamp:before {content: \"\\f2d5\";}\n\ +.fa-grav:before {content: \"\\f2d6\";}\n\ +.fa-etsy:before {content: \"\\f2d7\";}\n\ +.fa-imdb:before {content: \"\\f2d8\";}\n\ +.fa-ravelry:before {content: \"\\f2d9\";}\n\ +.fa-eercast:before {content: \"\\f2da\";}\n\ +.fa-microchip:before {content: \"\\f2db\";}\n\ +.fa-snowflake-o:before {content: \"\\f2dc\";}\n\ +.fa-superpowers:before {content: \"\\f2dd\";}\n\ +.fa-wpexplorer:before {content: \"\\f2de\";}\n\ +.fa-meetup:before {content: \"\\f2e0\";}\n\ .fa::before {\n\ font-family: FontAwesome;\n\ font-weight: 400;\n\ @@ -1190,13 +1353,11 @@ boards: font: 13px sans-serif;\n\ outline: none;\n\ transition: color .25s, border-color .25s;\n\ - transition: color .25s, border-color .25s;\n\ }\n\ -.field::-moz-placeholder,\n\ -.field:hover::-moz-placeholder {\n\ - color: #AAA !important;\n\ - font-size: 13px !important;\n\ - opacity: 1.0 !important;\n\ +.field::-moz-placeholder {\n\ + color: #AAA;\n\ + font-size: 13px;\n\ + opacity: 1;\n\ }\n\ .captch-img:hover,\n\ .field:hover {\n\ @@ -1225,10 +1386,10 @@ a[href=\"javascript:;\"] {\n\ .warning {\n\ color: red;\n\ }\n\ -#boardNavDesktop, #boardNavMobile {\n\ +:root.sw-yotsuba #boardNavDesktop, :root.sw-yotsuba #boardNavMobile {\n\ display: none !important;\n\ }\n\ -:root.hide-bottom-board-list #boardNavDesktopFoot {\n\ +:root.hide-bottom-board-list $site$boardListBottom {\n\ display: none;\n\ }\n\ body.hasDropDownNav{\n\ @@ -1241,61 +1402,137 @@ body.hasDropDownNav{\n\ border-radius: 3px;\n\ padding: 0px 2px;\n\ }\n\ +[hidden] {\n\ + display: none !important;\n\ +}\n\ /* 4chan style fixes */\n\ -.opContainer, .op {\n\ - display: block !important;\n\ - overflow: visible !important;\n\ +/* overrides 4chan CSS on div.opContainer, div.op */\n\ +:root.sw-yotsuba .opContainer, :root.sw-yotsuba .op {\n\ + display: block;\n\ + overflow: visible;\n\ }\n\ -.reply > .file > .fileText {\n\ +:root.sw-yotsuba .reply > .file > .fileText {\n\ margin: 0 20px;\n\ }\n\ -.hashlink::before {\n\ - content: ' ';\n\ - visibility: hidden;\n\ -}\n\ -.inline + .hashlink,\n\ -[hidden] {\n\ - display: none !important;\n\ +:root.sw-yotsuba #arc-list span.quote {\n\ + color: #789922;\n\ }\n\ -.fileText a {\n\ +:root.sw-yotsuba .fileText a {\n\ unicode-bidi: -moz-isolate;\n\ unicode-bidi: -webkit-isolate;\n\ }\n\ -#g-recaptcha {\n\ +:root.sw-yotsuba #g-recaptcha {\n\ min-height: 78px;\n\ height: auto;\n\ }\n\ -:root:not(.js-enabled) #postForm {\n\ +:root.sw-yotsuba:not(.js-enabled) #postForm {\n\ display: table;\n\ }\n\ -#captchaContainerAlt td:nth-child(2) {\n\ +:root.sw-yotsuba #captchaContainerAlt td:nth-child(2) {\n\ display: table-cell !important;\n\ }\n\ -canvas#tegaki-canvas {\n\ +:root.sw-yotsuba canvas#tegaki-canvas {\n\ background: none;\n\ }\n\ /* Disable obnoxious captcha fade-in. */\n\ -body > div:last-of-type {\n\ +:root.sw-yotsuba > body > div:last-of-type {\n\ transition: none !important;\n\ }\n\ /* Fix captcha scrolling to top of page. */\n\ -body > div[style*=\" top: -10000px;\"] {\n\ +:root.sw-yotsuba > body > div[style*=\" top: -10000px;\"] {\n\ visibility: hidden !important;\n\ }\n\ +/* Make long filenames wrap properly: https://github.com/ccd0/4chan-x/issues/1082 */\n\ +:root.sw-yotsuba .post > .file {\n\ + /* currently nonstandard but may be added: https://lists.w3.org/Archives/Public/www-style/2016Mar/0352.html, https://bugzilla.mozilla.org/show_bug.cgi?id=1296042 */\n\ + word-break: break-word;\n\ +}\n\ +:root.sw-yotsuba:not(.ua-webkit):not(.ua-blink) .fileText {\n\ + word-wrap: break-word;\n\ + max-width: calc(100vw - 90px);\n\ +}\n\ +:root.sw-yotsuba > body.is_catalog .thread > a > img {\n\ + display: inline-block;\n\ +}\n\ +/* Links to NSFW boards */\n\ +:root.sw-yotsuba .nwsb {\n\ + display: inline;\n\ +}\n\ +:root.sw-yotsuba .fileText {\n\ + max-width: auto;\n\ + white-space: normal;\n\ +}\n\ /* Ads */\n\ -:root:not(.ads-loaded) .ad-cnt,\n\ -:root:not(.ads-loaded) .ad-plea,\n\ -:root:not(.ads-loaded) hr.abovePostForm,\n\ -:root:not(.ads-loaded) .ad-plea-bottom + hr {\n\ +:root.sw-yotsuba .ad-cnt > *, :root.sw-yotsuba .adg-rects > *, :root.sw-yotsuba .bsa-cnt {\n\ + height: auto !important;\n\ +}\n\ +:root.sw-yotsuba:not(.ads-loaded) hr.abovePostForm,\n\ +:root.sw-yotsuba:not(.ads-loaded) .adg-rects > hr,\n\ +:root.sw-yotsuba #adg-ol + hr,\n\ +:root.sw-yotsuba .danbo-slot:empty {\n\ display: none;\n\ }\n\ -hr + div.center:not(.ad-cnt):not(.topad):not(.middlead):not(.bottomad) {\n\ +:root.sw-yotsuba .adg-rects {\n\ + margin: 0;\n\ + font-size: 0;\n\ +}\n\ +:root.sw-yotsuba div.center[style] {\n\ display: none !important;\n\ }\n\ +/* Tinyboard / vichan conflicts */\n\ +#menu > .hide-thread-link {\n\ + width: auto;\n\ + height: auto;\n\ + overflow: visible;\n\ + background-image: none;\n\ +}\n\ +#menu label.entry {\n\ + display: block;\n\ +}\n\ +#fourchanx-settings label {\n\ + display: inline;\n\ +}\n\ +.intro a[href=\"javascript:;\"],\n\ +#menu a {\n\ + margin: 0;\n\ +}\n\ +.gal-buttons.gal-buttons a {\n\ + font-size: inherit;\n\ +}\n\ +:root.sw-tinyboard.fixed.top-header:not(.autohide) .boardlist,\n\ +:root.sw-tinyboard.fixed.top-header:not(.autohide) .bar.top {\n\ + position: static;\n\ +}\n\ +:root.sw-tinyboard.fixed.top-header:not(.autohide) div.pages.top {\n\ + top: auto;\n\ + bottom: 0;\n\ +}\n\ +:root.sw-tinyboard.fixed.top-header.autohide .boardlist,\n\ +:root.sw-tinyboard.fixed.top-header.autohide .bar.top {\n\ + z-index: 3;\n\ +}\n\ +/* Tinyboard site style conflicts */\n\ +:root[data-host=\"fufufu.moe\"].fixed.top-header:not(.autohide) div.pages.top {\n\ + top: 26px;\n\ + bottom: auto;\n\ +}\n\ +:root[data-host=\"merorin.com\"].fixed.top-header:not(.autohide) span.settings {\n\ + top: 26px;\n\ +}\n\ +:root[data-host=\"fufufu.moe\"]:not(.fixed) #header-bar {\n\ + margin-top: 38px;\n\ +}\n\ +:root[data-host=\"lainchan.org\"]:not(.fixed) #header-bar {\n\ + margin-top: 17px;\n\ +}\n\ +:root[data-host=\"smuglo.li\"]:not(.fixed) #header-bar {\n\ + margin-top: 8px;\n\ +}\n\ /* Anti-autoplay */\n\ audio.controls-added {\n\ display: block;\n\ margin: auto;\n\ + white-space: normal;\n\ }\n\ :root.anti-autoplay div.embed {\n\ position: static;\n\ @@ -1304,14 +1541,12 @@ audio.controls-added {\n\ text-align: center;\n\ }\n\ :root.anti-autoplay .autoplay-removed {\n\ - display: block !important;\n\ visibility: visible !important;\n\ min-width: 640px;\n\ - min-height: 390px;\n\ + min-height: 360px;\n\ }\n\ /* fixed, z-index */\n\ #overlay,\n\ -#fourchanx-settings,\n\ #qp, #ihover,\n\ #navlinks, .fixed #header-bar,\n\ :root.float #updater,\n\ @@ -1319,11 +1554,8 @@ audio.controls-added {\n\ #qr {\n\ position: fixed;\n\ }\n\ -#fourchanx-settings {\n\ - z-index: 999;\n\ -}\n\ #overlay {\n\ - z-index: 900;\n\ + z-index: 999;\n\ }\n\ #qp, #ihover {\n\ z-index: 60;\n\ @@ -1490,56 +1722,57 @@ audio.controls-added {\n\ #toggleMsgBtn {\n\ display: none !important;\n\ }\n\ -.current {\n\ +.current,\n\ +:root.sw-yotsuba div#boardNavDesktopFoot a.current {\n\ font-weight: bold;\n\ }\n\ @media (min-width: 1300px) {\n\ - :root.fixed:not(.centered-links) #header-bar {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #header-bar {\n\ white-space: nowrap;\n\ display: -webkit-flex;\n\ display: flex;\n\ -webkit-align-items: center;\n\ align-items: center;\n\ }\n\ - :root.fixed:not(.centered-links) #board-list {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #board-list {\n\ -webkit-flex: auto;\n\ flex: auto;\n\ }\n\ - :root.fixed:not(.centered-links) #full-board-list {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list {\n\ display: -webkit-flex;\n\ display: flex;\n\ }\n\ - :root.fixed:not(.centered-links) .hide-board-list-container {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) .hide-board-list-container {\n\ -webkit-flex: none;\n\ flex: none;\n\ margin-right: 5px;\n\ }\n\ - :root.fixed:not(.centered-links) #full-board-list > .boardList {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList {\n\ -webkit-flex: auto;\n\ flex: auto;\n\ display: -webkit-flex;\n\ display: flex;\n\ width: 0px; /* XXX Fixes Edge not shrinking the board list below default size when needed */\n\ }\n\ - :root.fixed:not(.centered-links) #full-board-list > .boardList > a,\n\ - :root.fixed:not(.centered-links) #full-board-list > .boardList > span:not(.space):not(.spacer) {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > a,\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > span:not(.space):not(.spacer) {\n\ -webkit-flex: none;\n\ flex: none;\n\ padding: .17em;\n\ margin: -.17em -.32em;\n\ }\n\ - :root.fixed:not(.centered-links) #full-board-list > .boardList > span {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > span {\n\ pointer-events: none;\n\ }\n\ - :root.fixed:not(.centered-links) #full-board-list > .boardList > span.space {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > span.space {\n\ -webkit-flex: 0 .63 .63em;\n\ flex: 0 .63 .63em;\n\ }\n\ - :root.fixed:not(.centered-links) #full-board-list > .boardList > span.spacer {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > span.spacer {\n\ -webkit-flex: 0 .38 .38em;\n\ flex: 0 .38 .38em;\n\ }\n\ - :root.fixed:not(.centered-links) #shortcuts {\n\ + :root.sw-yotsuba.fixed:not(.centered-links) #shortcuts {\n\ float: initial;\n\ -webkit-flex: none;\n\ flex: none;\n\ @@ -1566,6 +1799,9 @@ audio.controls-added {\n\ left: 0;\n\ visibility: visible;\n\ }\n\ +#notifications:empty {\n\ + display: none;\n\ +}\n\ :root.fixed.top-header:not(.gallery-open) #header-bar #notifications,\n\ :root.fixed.top-header #header-bar.autohide #notifications {\n\ position: absolute;\n\ @@ -1629,6 +1865,8 @@ audio.controls-added {\n\ }\n\ #overlay {\n\ background-color: rgba(0, 0, 0, .5);\n\ + display: -webkit-flex;\n\ + display: flex;\n\ top: 0;\n\ left: 0;\n\ height: 100%;\n\ @@ -1643,16 +1881,16 @@ audio.controls-added {\n\ width: 900px;\n\ max-width: 100%;\n\ margin: auto;\n\ - padding: 3px;\n\ - top: 50%;\n\ - left: 50%;\n\ - -moz-transform: translate(-50%, -50%);\n\ - -webkit-transform: translate(-50%, -50%);\n\ - transform: translate(-50%, -50%);\n\ + padding: 5px;\n\ + display: -webkit-flex;\n\ + display: flex;\n\ + -webkit-flex-direction: column;\n\ + flex-direction: column;\n\ }\n\ #fourchanx-settings > nav {\n\ - padding: 2px 2px 0;\n\ - height: 15px;\n\ + padding: 2px 2px 8px;\n\ + display: -webkit-flex;\n\ + display: flex;\n\ }\n\ #fourchanx-settings > nav a {\n\ text-decoration: underline;\n\ @@ -1663,20 +1901,16 @@ audio.controls-added {\n\ margin: 0;\n\ }\n\ .section-container {\n\ + -webkit-flex: 1;\n\ + flex: 1;\n\ + position: relative;\n\ overflow: auto;\n\ - position: absolute;\n\ - top: 2.1em;\n\ - right: 5px;\n\ - bottom: 5px;\n\ - left: 5px;\n\ padding-right: 5px;\n\ + overscroll-behavior: contain;\n\ }\n\ .sections-list {\n\ - padding: 0 3px;\n\ - float: left;\n\ -}\n\ -.credits {\n\ - float: right;\n\ + -webkit-flex: 1;\n\ + flex: 1;\n\ }\n\ .export, .import, .reset {\n\ cursor: pointer;\n\ @@ -1743,6 +1977,9 @@ div[data-checked=\"false\"] > .suboption-list {\n\ border-left: 1px solid;\n\ border-bottom: 1px solid;\n\ }\n\ +#fourchanx-settings .section-main p {\n\ + margin: .5em 0 0;\n\ +}\n\ .section-filter ul {\n\ padding: 0;\n\ }\n\ @@ -1756,8 +1993,20 @@ div[data-checked=\"false\"] > .suboption-list {\n\ .section-main a, .section-filter a, .section-advanced a {\n\ text-decoration: underline;\n\ }\n\ +#sauce-doc-expand:not(:checked) ~ #sauce-doc {\n\ + max-height: 130px;\n\ + overflow: auto;\n\ +}\n\ +#sauce-doc > label {\n\ + float: right;\n\ + margin: 0 5px;\n\ +}\n\ +/* XXX for OneeChan */\n\ +#sauce-doc-expand + .riceCheck {\n\ + display: none;\n\ +}\n\ .section-sauce textarea {\n\ - height: 350px;\n\ + height: 430px;\n\ }\n\ .section-advanced .field[name=\"boardnav\"] {\n\ width: 100%;\n\ @@ -1765,7 +2014,9 @@ div[data-checked=\"false\"] > .suboption-list {\n\ .section-advanced textarea {\n\ height: 150px;\n\ }\n\ -.section-advanced textarea[name=\"archiveLists\"] {\n\ +.section-advanced textarea[name=\"archiveLists\"],\n\ +.section-advanced textarea[name=\"externalCatalogURLs\"],\n\ +.section-advanced textarea[name=\"knownBanners\"] {\n\ height: 75px;\n\ }\n\ .section-advanced .archive-cell {\n\ @@ -1784,6 +2035,12 @@ div[data-checked=\"false\"] > .suboption-list {\n\ font-style: normal;\n\ font-size: 11px;\n\ }\n\ +.favicon-preview > img {\n\ + vertical-align: middle;\n\ +}\n\ +.favicon-preview > img:nth-of-type(3n+1) {\n\ + margin-left: 4px;\n\ +}\n\ .section-keybinds .field {\n\ font-family: monospace;\n\ }\n\ @@ -1799,8 +2056,8 @@ div[data-checked=\"false\"] > .suboption-list {\n\ }\n\ #fourchanx-settings textarea {\n\ font-family: monospace;\n\ - min-width: 100%;\n\ - max-width: 100%;\n\ + width: 100%;\n\ + resize: vertical;\n\ }\n\ #fourchanx-settings code {\n\ color: #000;\n\ @@ -1814,8 +2071,8 @@ div[data-checked=\"false\"] > .suboption-list {\n\ #fourchanx-settings p {\n\ margin: 1em 0px;\n\ }\n\ -.unscroll {\n\ - overflow: hidden;\n\ +#fourchanx-settings table {\n\ + margin: auto;\n\ }\n\ /* Index */\n\ :root.index-loading .navLinks:not(.json-index),\n\ @@ -1853,9 +2110,26 @@ div[data-checked=\"false\"] > .suboption-list {\n\ #index-search:not([data-searching]) + #index-search-clear {\n\ display: none;\n\ }\n\ -#index-mode, #index-sort, #index-size {\n\ +#index-options {\n\ float: right;\n\ }\n\ +#lastlong-options {\n\ + display: inline-block;\n\ + vertical-align: middle;\n\ + height: 28px;\n\ + margin: -14px 0;\n\ +}\n\ +#lastlong-options > input {\n\ + padding: 0;\n\ + border: 0 !important;\n\ + text-align: center;\n\ + background: transparent;\n\ + display: block;\n\ + font-size: 12px;\n\ + height: 12px;\n\ + width: 30px;\n\ + margin: 1px 0;\n\ +}\n\ .summary {\n\ text-decoration: none;\n\ }\n\ @@ -1864,34 +2138,78 @@ div[data-checked=\"false\"] > .suboption-list {\n\ text-align: center;\n\ }\n\ .catalog-thread {\n\ - display: -webkit-inline-flex;\n\ - display: inline-flex;\n\ - text-align: left;\n\ - -webkit-flex-direction: column;\n\ - flex-direction: column;\n\ - -webkit-align-items: center;\n\ - align-items: center;\n\ - margin: 0 2px 5px;\n\ + display: inline-block;\n\ + -moz-box-sizing: border-box;\n\ + box-sizing: border-box;\n\ + border: 1px solid transparent;\n\ word-wrap: break-word;\n\ vertical-align: top;\n\ position: relative;\n\ }\n\ -.catalog-thread > a {\n\ - flex-shrink: 0;\n\ - -webkit-flex-shrink: 0;\n\ - position: relative;\n\ +/* overrides 4chan CSS on div.thread */\n\ +.catalog-thread.catalog-thread {\n\ + margin: 2px;\n\ }\n\ -.catalog-small .catalog-thread {\n\ +.catalog-small > .catalog-thread {\n\ width: 165px;\n\ - max-height: 320px;\n\ + height: 320px;\n\ }\n\ -.catalog-large .catalog-thread {\n\ +.catalog-large > .catalog-thread {\n\ width: 270px;\n\ - max-height: 410px;\n\ + height: 410px;\n\ +}\n\ +:root.catalog-hover-expand .catalog-thread:hover {\n\ + z-index: 1;\n\ +}\n\ +.catalog-container {\n\ + position: absolute;\n\ + top: -4px;\n\ + left: 0;\n\ + right: 0;\n\ + bottom: 0;\n\ +}\n\ +.catalog-container:not(:hover),\n\ +:root:not(.catalog-hover-expand) .catalog-container {\n\ + overflow: hidden;\n\ +}\n\ +.catalog-post {\n\ + position: absolute;\n\ + top: 4px;\n\ + left: 0;\n\ + right: 0;\n\ + border: 1px solid transparent;\n\ + padding-top: 20px;\n\ +}\n\ +/* overrides inline CSS from Index.cb.hoverAdjust */\n\ +:root:not(.catalog-hover-expand) .catalog-post {\n\ + left: 0 !important;\n\ + right: 0 !important;\n\ +}\n\ +/* overrides 4chan CSS on div.post */\n\ +.catalog-post.catalog-post {\n\ + margin: -21px -1px -1px;\n\ + overflow: visible;\n\ +}\n\ +.catalog-thread.noFile > * > .catalog-post {\n\ + margin-top: -7px;\n\ + padding-top: 6px;\n\ +}\n\ +:root.catalog-hover-expand .catalog-container:hover > .catalog-post {\n\ + margin-left: -61px;\n\ + margin-right: -61px;\n\ +}\n\ +:root.catalog-hover-expand .catalog-container:hover > * > :not(.catalog-replies) {\n\ + padding-left: 2px;\n\ + padding-right: 2px;\n\ +}\n\ +.catalog-link {\n\ + display: block;\n\ + position: relative;\n\ }\n\ .catalog-thumb {\n\ border-radius: 2px;\n\ box-shadow: 0 0 5px rgba(0, 0, 0, .25);\n\ + vertical-align: top;\n\ }\n\ .catalog-thumb.spoiler-file {\n\ width: 100px;\n\ @@ -1916,45 +2234,144 @@ div[data-checked=\"false\"] > .suboption-list {\n\ padding-left: 2px;\n\ }\n\ .catalog-stats > .menu-button {\n\ - text-align: center;\n\ font-weight: normal;\n\ }\n\ .catalog-stats > .menu-button > i::before {\n\ line-height: 11px;\n\ }\n\ .catalog-stats {\n\ - -webkit-flex-shrink: 0;\n\ - flex-shrink: 0;\n\ - cursor: help;\n\ font-size: 10px;\n\ font-weight: 700;\n\ - margin-top: 2px;\n\ + padding-top: 2px;\n\ }\n\ -.catalog-thread > .subject {\n\ - -webkit-flex-shrink: 0;\n\ - flex-shrink: 0;\n\ - -webkit-align-self: stretch;\n\ - align-self: stretch;\n\ - font-weight: 700;\n\ - line-height: 1;\n\ - text-align: center;\n\ +.catalog-stats > [title] {\n\ + cursor: help;\n\ }\n\ -.catalog-thread > .comment {\n\ - -webkit-flex-shrink: 1;\n\ - flex-shrink: 1;\n\ - -webkit-align-self: stretch;\n\ - align-self: stretch;\n\ +.catalog-post > .postMessage {\n\ + margin: 0;\n\ + padding-bottom: .3em;\n\ +}\n\ +.catalog-container:not(:hover) > * > .file,\n\ +.catalog-container:not(:hover) > * > .postInfo > :not(.subject),\n\ +.catalog-container:not(:hover) > * > .catalog-replies,\n\ +.catalog-container:not(:hover) .extra-linebreak,\n\ +.catalog-container:not(:hover) .abbr,\n\ +:root:not(.catalog-hover-expand) .catalog-container > * > .file,\n\ +:root:not(.catalog-hover-expand) .catalog-container > * > .postInfo > :not(.subject),\n\ +:root:not(.catalog-hover-expand) .catalog-container > * > .catalog-replies,\n\ +:root:not(.catalog-hover-expand) .catalog-container .extra-linebreak,\n\ +:root:not(.catalog-hover-expand) .catalog-container .abbr,\n\ +.catalog-thread > .catalog-container > :not(.catalog-post),\n\ +.catalog-post > .file > :not(.fileText),\n\ +.catalog-post > * > .fileText > :not(:first-child),\n\ +.catalog-post > .postInfo > :not(.subject):not(.nameBlock):not(.dateTime),\n\ +.catalog-post > .postInfo > .nameBlock > .contact-links,\n\ +.catalog-post > * > * > .posteruid,\n\ +.catalog-post > * > * > .postJumper,\n\ +:root.bottom-backlinks .catalog-post > .container,\n\ +.post:not(.catalog-post) > .catalog-link,\n\ +.post:not(.catalog-post) > .catalog-stats,\n\ +.post:not(.catalog-post) > .catalog-replies {\n\ + display: none;\n\ +}\n\ +.catalog-post > .file {\n\ + position: absolute;\n\ + left: 0;\n\ + right: 0;\n\ + top: 0;\n\ + min-height: 20px;\n\ + background-color: inherit;\n\ +}\n\ +.catalog-post > * > .fileText {\n\ + position: relative;\n\ + padding: 2px;\n\ + background-color: inherit;\n\ +}\n\ +.catalog-small .catalog-post > * .fileText {\n\ + font-size: 10px;\n\ +}\n\ +.catalog-post > * > .fileText:not(:hover) {\n\ + white-space: nowrap;\n\ overflow: hidden;\n\ - text-align: center;\n\ + text-overflow: ellipsis;\n\ }\n\ -/* /tg/ dice rolls */\n\ -.board_tg .catalog-thread > .comment > b {\n\ - font-weight: normal;\n\ +.catalog-post > * > .fileText:hover {\n\ + z-index: 1;\n\ }\n\ -.catalog-code {\n\ - background-color: #FFF;\n\ +/* overrides 4chan CSS on div.post div.postInfo */\n\ +.catalog-post > .postInfo.postInfo {\n\ + width: auto;\n\ +}\n\ +.catalog-post > * > .subject {\n\ + display: block;\n\ +}\n\ +.catalog-post > * > .dateTime {\n\ display: inline-block;\n\ + font-style: italic;\n\ +}\n\ +:root.catalog-hover-expand .catalog-container:hover > * > * > .nameBlock,\n\ +:root.catalog-hover-expand .catalog-container:hover > * > * > .dateTime,\n\ +:root.catalog-hover-expand .catalog-container:hover > * > .postMessage:not(:empty) {\n\ + padding-top: .3em;\n\ +}\n\ +.catalog-post .extra-linebreak {\n\ + content: ''; /* makes this work in Blink/WebKit */\n\ + display: block;\n\ + margin-top: .3em;\n\ +}\n\ +.catalog-reply {\n\ + text-align: left;\n\ + white-space: nowrap;\n\ + border-top: 1px solid transparent;\n\ + display: -webkit-flex;\n\ + display: flex;\n\ + -webkit-flex-direction: row;\n\ + flex-direction: row;\n\ + -webkit-align-items: stretch;\n\ + align-items: stretch;\n\ +}\n\ +.catalog-reply > * {\n\ + padding: 3px;\n\ + overflow: hidden;\n\ + -webkit-flex: none;\n\ + flex: none;\n\ +}\n\ +.catalog-reply > span {\n\ + font-style: italic;\n\ + font-weight: bold;\n\ +}\n\ +.catalog-reply-excerpt {\n\ + -webkit-flex: 1 1 auto;\n\ + flex: 1 1 auto;\n\ +}\n\ +.catalog-post .prettyprinted {\n\ max-width: 100%;\n\ + -moz-box-sizing: border-box;\n\ + box-sizing: border-box;\n\ +}\n\ +.catalog-post .MathJax_Display {\n\ + text-align: center !important;\n\ +}\n\ +.catalog-container:not(:hover) .exif,\n\ +:root:not(.catalog-hover-expand) .catalog-container .exif {\n\ + display: none !important;\n\ +}\n\ +.catalog-post > * > .exif {\n\ + border-collapse: collapse;\n\ +}\n\ +:root.catalog-hover-expand .catalog-container:hover .exif[style*=\"display: block;\"] {\n\ + display: inline-block !important;\n\ +}\n\ +.catalog-post > * > .exif,\n\ +.catalog-post > * > .exif > tbody {\n\ + background-color: inherit;\n\ +}\n\ +.catalog-post > * > .exif,\n\ +.catalog-post > * > .exif td {\n\ + min-width: 0;\n\ +}\n\ +.catalog-post > * > .exif td {\n\ + padding-top: 1px;\n\ }\n\ :root.hats-enabled .catalog-thread::after {\n\ content: '';\n\ @@ -1962,37 +2379,56 @@ div[data-checked=\"false\"] > .suboption-list {\n\ position: absolute;\n\ background-size: contain;\n\ }\n\ -:root.hats-enabled .catalog-small .catalog-thread::after {\n\ - left: -10px;\n\ - top: -65px;\n\ - width: 100px;\n\ - height: 100px;\n\ +:root.hats-enabled .catalog-small > .catalog-thread::after {\n\ + left: -8px;\n\ + top: -59px;\n\ + width: 96px;\n\ + height: 96px;\n\ +}\n\ +:root.hats-enabled:not(.werkTyme) .catalog-small > .catalog-thread:not(.noFile)::after {\n\ + left: calc(67px - .3px * var(--tn-w));\n\ }\n\ -:root.hats-enabled .catalog-large .catalog-thread::after {\n\ +:root.hats-enabled .catalog-large > .catalog-thread::after {\n\ left: -15px;\n\ - top: -105px;\n\ + top: -98px;\n\ width: 160px;\n\ height: 160px;\n\ }\n\ +:root.hats-enabled:not(.werkTyme) .catalog-large > .catalog-thread:not(.noFile)::after {\n\ + left: calc(110px - .5px * var(--tn-w));\n\ +}\n\ +/* Copy Text Link's textarea element */\n\ +textarea.copy-text-element {\n\ + height: 0;\n\ + width: 0;\n\ + position: absolute;\n\ + top: -10000px;\n\ +}\n\ /* Announcement Hiding */\n\ -:root.hide-announcement #globalMessage {\n\ +:root.hide-announcement $site$psa {\n\ display: none;\n\ }\n\ -span.hide-announcement {\n\ - font-size: 11px;\n\ - position: relative;\n\ - bottom: 5px;\n\ -}\n\ -.globalMessage, h2, h3 {\n\ - color: inherit !important;\n\ - font-size: 13px;\n\ - font-weight: 100;\n\ +.hide-announcement-button {\n\ + opacity: 0.4;\n\ + float: left;\n\ }\n\ /* Unread */\n\ -#unread-line {\n\ +.unread-line {\n\ margin: 0;\n\ border-color: rgb(255,0,0);\n\ }\n\ +.unread-line + br {\n\ + display: none;\n\ +}\n\ +.unread-mark-read {\n\ + float: right;\n\ + clear: both;\n\ + width: 100%;\n\ + text-align: right;\n\ +}\n\ +:not(.unread-thread) > .unread-mark-read {\n\ + display: none;\n\ +}\n\ /* Thread Updater */\n\ #updater {\n\ background: none;\n\ @@ -2001,10 +2437,11 @@ span.hide-announcement {\n\ }\n\ #updater > .move {\n\ position: absolute;\n\ - left: 0;\n\ top: -5px;\n\ - width: 100%;\n\ - height: 5px;\n\ + bottom: -5px;\n\ + left: -5px;\n\ + right: -5px;\n\ + z-index: -1;\n\ }\n\ #updater > div:last-child {\n\ text-align: center;\n\ @@ -2072,12 +2509,11 @@ span.hide-announcement {\n\ -webkit-flex-direction: row;\n\ flex-direction: row;\n\ }\n\ +#watched-threads .watcher-page,\n\ #watched-threads .watcher-unread {\n\ -webkit-flex: 0 0 auto;\n\ flex: 0 0 auto;\n\ -}\n\ -#watched-threads .watcher-unread::after {\n\ - content: \"\\00a0\";\n\ + margin-right: 2px;\n\ }\n\ #watched-threads .watcher-title {\n\ overflow: hidden;\n\ @@ -2085,12 +2521,15 @@ span.hide-announcement {\n\ -webkit-flex: 0 1 auto;\n\ flex: 0 1 auto;\n\ }\n\ +#watched-threads .watcher-title:not(:first-child) {\n\ + margin-left: 2px;\n\ +}\n\ +.replies-quoting-you > a, #watcher-link.replies-quoting-you, .last-page > a > .watcher-page {\n\ + color: #F00;\n\ +}\n\ #thread-watcher a {\n\ text-decoration: none;\n\ }\n\ -:root:not(.toggleable-watcher) #thread-watcher .move > .close {\n\ - display: none;\n\ -}\n\ #thread-watcher .move > .close {\n\ position: absolute;\n\ right: 0px;\n\ @@ -2127,17 +2566,24 @@ span.hide-announcement {\n\ cursor: pointer;\n\ }\n\ /* Quote */\n\ -.catalog-thread > .comment > span.quote, #arc-list span.quote {\n\ - color: #789922;\n\ +.hashlink::before {\n\ + content: ' ';\n\ + visibility: hidden;\n\ }\n\ -:root:not(.catalog-mode) .deadlink {\n\ +.inline + .hashlink {\n\ + display: none !important;\n\ +}\n\ +:root.resurrect-quotes .deadlink {\n\ text-decoration: none !important;\n\ }\n\ +.catalog-post .qmark-ct {\n\ + display: none;\n\ +}\n\ .backlink.deadlink:not(.forwardlink),\n\ .quotelink.deadlink:not(.forwardlink) {\n\ text-decoration: underline !important;\n\ }\n\ -.inlined {\n\ +:root:not(.catalog-mode) .inlined {\n\ opacity: .5;\n\ }\n\ #qp input, .forwarded {\n\ @@ -2158,11 +2604,25 @@ span.hide-announcement {\n\ .postNum + .container::before {\n\ content: \" \";\n\ }\n\ +:root.bottom-backlinks .container {\n\ + display: block;\n\ + clear: both;\n\ + margin: 0 4px;\n\ +}\n\ +:root.bottom-backlinks .backlink {\n\ + font-size: 90%;\n\ +}\n\ .inline {\n\ border: 1px solid;\n\ display: table;\n\ margin: 2px 0;\n\ }\n\ +.container ~ .inline {\n\ + margin-left: 20px;\n\ +}\n\ +:root.catalog-mode .inline {\n\ + display: none;\n\ +}\n\ .inline .post {\n\ border: 0 !important;\n\ background-color: transparent !important;\n\ @@ -2200,7 +2660,7 @@ span.hide-announcement {\n\ .expanded-image > .post > .file > .fileThumb > img[data-md5] {\n\ display: none;\n\ }\n\ -.full-image {\n\ +.full-image[data-file-i-d] {\n\ display: none;\n\ cursor: pointer;\n\ }\n\ @@ -2230,6 +2690,13 @@ span.hide-announcement {\n\ .fileThumb > .warning {\n\ clear: both;\n\ }\n\ +#ihover {\n\ + pointer-events: none;\n\ + /* XXX https://code.google.com/p/chromium/issues/detail?id=168840, https://bugs.webkit.org/show_bug.cgi?id=94158 */\n\ + max-height: 95vh;\n\ + max-height: calc(100vh - 25px);\n\ + max-width: 100vw;\n\ +}\n\ /* WEBM Metadata */\n\ .webm-title > a::before {\n\ content: \"title\";\n\ @@ -2262,22 +2729,29 @@ input[name=\"Default Volume\"] {\n\ margin: 0px;\n\ }\n\ /* Fappe and Werk Tyme */\n\ -:root.fappeTyme .thread > .noFile,\n\ -:root.fappeTyme .threadContainer > .noFile {\n\ +:root.fappeTyme $site$replyOriginal.noFile,\n\ +:root.fappeTyme $site$replyOriginal.noFile + br {\n\ display: none;\n\ }\n\ -:root.werkTyme .postContainer:not(.noFile) .fileThumb,\n\ +:root.werkTyme $site$thumbLink,\n\ +:root.werkTyme $site$file$thumb,\n\ :root.werkTyme .catalog-thumb:not(.deleted-file):not(.no-file),\n\ :root:not(.werkTyme) .werkTyme-filename {\n\ display: none;\n\ }\n\ .werkTyme-filename {\n\ font-weight: bold;\n\ + font-size: 110%;\n\ }\n\ -:root.werkTyme .catalog-thread > a {\n\ +:root.werkTyme .catalog-link {\n\ + box-shadow: 0 0 5px rgba(0, 0, 0, .25);\n\ + padding: 8px;\n\ text-align: center;\n\ - -webkit-align-self: stretch;\n\ - align-self: stretch;\n\ +}\n\ +:root.werkTyme .catalog-thumb {\n\ + box-shadow: none;\n\ + padding: 0;\n\ + vertical-align: middle;\n\ }\n\ .indicator {\n\ background: rgba(255,0,0,0.8);\n\ @@ -2308,41 +2782,46 @@ input[name=\"Default Volume\"] {\n\ .qphl {\n\ outline: 2px solid rgba(216, 94, 49, .8);\n\ }\n\ -:root.highlight-you .quotesYou.opContainer,\n\ -:root.highlight-you .quotesYou > .reply {\n\ +:root.highlight-you .quotesYou$site$highlightable$op,\n\ +:root.highlight-you .quotesYou$site$highlightable$reply {\n\ border-left: 3px solid rgba(221, 0, 0, .8);\n\ }\n\ -:root.highlight-own .yourPost.opContainer,\n\ -:root.highlight-own .yourPost > .reply {\n\ +:root.highlight-own .yourPost$site$highlightable$op,\n\ +:root.highlight-own .yourPost$site$highlightable$reply {\n\ border-left: 3px dashed rgba(221, 0, 0, .8);\n\ }\n\ -.filter-highlight.opContainer,\n\ -.filter-highlight > .reply {\n\ +.filter-highlight$site$highlightable$op,\n\ +.filter-highlight$site$highlightable$reply {\n\ box-shadow: inset 5px 0 rgba(221, 0, 0, .5);\n\ }\n\ -:root.highlight-own .yourPost > div.sideArrows,\n\ -:root.highlight-you .quotesYou > div.sideArrows,\n\ -.filter-highlight > div.sideArrows {\n\ +:root.highlight-own .yourPost > $site$sideArrows,\n\ +:root.highlight-you .quotesYou > $site$sideArrows,\n\ +.filter-highlight > $site$sideArrows {\n\ color: rgba(221, 0, 0, .8);\n\ }\n\ -:root.highlight-own .yourPost.opContainer::after,\n\ -:root.highlight-you .quotesYou.opContainer::after,\n\ -.filter-highlight.opContainer::after {\n\ +:root.highlight-own .yourPost$site$highlightable$op::after,\n\ +:root.highlight-you .quotesYou$site$highlightable$op::after,\n\ +.filter-highlight$site$highlightable$op::after {\n\ content: \"\";\n\ display: block;\n\ clear: both;\n\ }\n\ -.filter-highlight .catalog-thumb,\n\ -.filter-highlight .werkTyme-filename {\n\ +:root:not(.werkTyme) .catalog-thread.filter-highlight .catalog-thumb,\n\ +:root.werkTyme .catalog-thread.filter-highlight:not(:hover),\n\ +:root.werkTyme:not(.catalog-hover-expand) .catalog-thread.filter-highlight,\n\ +:root.werkTyme.catalog-hover-expand .catalog-thread.filter-highlight > .catalog-container:hover > .catalog-post,\n\ +:root.catalog $site$catalog$thread.filter-highlight$site$highlightable$catalog {\n\ box-shadow: 0 0 3px 3px rgba(255, 0, 0, .5);\n\ }\n\ -.catalog-thread.watched .catalog-thumb,\n\ -.catalog-thread.watched .werkTyme-filename {\n\ +:root:not(.werkTyme) .catalog-thread.watched .catalog-thumb,\n\ +:root:root.werkTyme .catalog-thread.watched:not(:hover),\n\ +:root:root.werkTyme:not(.catalog-hover-expand) .catalog-thread.watched,\n\ +:root.werkTyme.catalog-hover-expand .catalog-thread.watched > .catalog-container:hover > .catalog-post {\n\ border: 2px solid rgba(255, 0, 0, .75);\n\ }\n\ /* Spoiler text */\n\ -:root.reveal-spoilers s,\n\ -:root.reveal-spoilers s > a {\n\ +:root.reveal-spoilers $site$spoiler,\n\ +:root.reveal-spoilers $site$spoiler > a {\n\ color: white !important;\n\ }\n\ :root.reveal-spoilers .removed-spoiler::before {\n\ @@ -2358,6 +2837,13 @@ input[name=\"Default Volume\"] {\n\ margin-right: 4px;\n\ padding: 2px;\n\ }\n\ +$site$infoRoot a.hide-reply-button {\n\ + margin-right: 6px;\n\ + padding: 0;\n\ +}\n\ +.replacedSideArrows {\n\ + float: left;\n\ +}\n\ .hide-thread-button:not(:hover),\n\ .hide-reply-button:not(:hover) {\n\ opacity: 0.4;\n\ @@ -2369,19 +2855,41 @@ input[name=\"Default Volume\"] {\n\ }\n\ .hide-thread-button {\n\ margin-top: -1px;\n\ + width: 11px;\n\ }\n\ -.stub ~ * {\n\ +.stub ~ :not(.threadDivider) {\n\ display: none !important;\n\ }\n\ .stub input {\n\ display: inline-block;\n\ }\n\ -.thread[hidden] + hr {\n\ +$site$thread[hidden] + hr {\n\ + display: none;\n\ +}\n\ +:root.reply-hide $site$sideArrows {\n\ display: none;\n\ }\n\ -:root.reply-hide div.sideArrows {\n\ +:root.sw-yotsuba.thread-hide .party-hat {\n\ + left: 19px;\n\ +}\n\ +/* Anonymize */\n\ +:root.anonymize $site$info$name,\n\ +:root.sw-yotsuba.anonymize .post-author:not([class*=capcode]) {\n\ + font-size: 0;\n\ +}\n\ +:root.anonymize $site$info$tripcode,\n\ +:root.sw-yotsuba.anonymize .n-pu {\n\ display: none;\n\ }\n\ +:root.anonymize $site$info$name::before,\n\ +:root.sw-yotsuba.anonymize .post-author:not([class*=capcode])::before {\n\ + content: \"Anonymous\";\n\ + font-size: 10pt;\n\ +}\n\ +:root.sw-yotsuba.anonymize .flashListing .name::before,\n\ +:root.sw-yotsuba.anonymize .post-last > .post-author:not([class*=capcode])::before {\n\ + font-size: 9pt;\n\ +}\n\ /* QR */\n\ :root.hide-original-post-form #togglePostFormLink,\n\ #qr.autohide:not(.focus):not(:hover):not(:active) > form,\n\ @@ -2464,8 +2972,8 @@ input[name=\"Default Volume\"] {\n\ #qr.reply-to-thread input[data-name=\"sub\"]:not(.force-show),\n\ body:not(.board_f) #qr select[name=\"filetag\"],\n\ #qr.reply-to-thread select[name=\"filetag\"],\n\ -body:not(.board_jp) #sjis-toggle,\n\ -body:not(.board_sci) #tex-preview-button,\n\ +#qr:not(.has-sjis) #sjis-toggle,\n\ +#qr:not(.has-math) #tex-preview-button,\n\ #qr.tex-preview .textarea > :not(#tex-preview),\n\ #qr:not(.tex-preview) #tex-preview {\n\ display: none;\n\ @@ -2506,11 +3014,12 @@ input.field.tripped:not(:hover):not(:focus) {\n\ text-shadow: none !important;\n\ }\n\ #qr textarea {\n\ - min-width: 100%;\n\ + min-width: 300px;\n\ resize: both;\n\ }\n\ .field {\n\ -moz-box-sizing: border-box;\n\ + box-sizing: border-box;\n\ margin: 0px;\n\ padding: 2px 4px 3px;\n\ }\n\ @@ -2518,22 +3027,6 @@ input.field.tripped:not(:hover):not(:focus) {\n\ position: relative;\n\ top: 2px;\n\ }\n\ -/* Recaptcha v1 */\n\ -.captcha-img {\n\ - margin: 0px;\n\ - text-align: center;\n\ - background-image: #fff;\n\ - font-size: 0px;\n\ - min-height: 59px;\n\ - min-width: 302px;\n\ -}\n\ -.captcha-input {\n\ - width: 100%;\n\ - margin: 1px 0 0;\n\ -}\n\ -#qr.captcha-v1 #qr-captcha-iframe {\n\ - display: none;\n\ -}\n\ /* Recaptcha v2 */\n\ #qr .captcha-root {\n\ position: relative;\n\ @@ -2542,14 +3035,18 @@ input.field.tripped:not(:hover):not(:focus) {\n\ margin: auto;\n\ width: 304px;\n\ }\n\ -/* scrollable with scroll bar hidden; prevents scroll on space press */\n\ -:root.ua-blink #qr .captcha-container > div {\n\ +/* XXX scrollable with scroll bar hidden; prevents scroll on space press */\n\ +:root.ua-blink #qr .captcha-container > div,\n\ +:root.ua-edge #qr .captcha-container > div {\n\ overflow: hidden;\n\ }\n\ -:root.ua-blink #qr .captcha-container > div > div:first-of-type {\n\ +:root.ua-blink #qr .captcha-container > div > div:first-of-type,\n\ +:root.ua-edge #qr .captcha-container > div > div:first-of-type {\n\ overflow-y: scroll;\n\ overflow-x: hidden;\n\ - padding-right: 15px;\n\ + padding-right: 30px;\n\ + height: 99%;\n\ + width: 100%;\n\ }\n\ #qr .captcha-counter {\n\ display: block;\n\ @@ -2563,6 +3060,7 @@ input.field.tripped:not(:hover):not(:focus) {\n\ }\n\ #qr .captcha-counter > a {\n\ pointer-events: auto;\n\ + display: inline-block; /* XXX https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8851747/ */\n\ }\n\ #qr:not(.captcha-open) .captcha-counter > a {\n\ display: block;\n\ @@ -2776,6 +3274,7 @@ input[type=\"checkbox\"]:checked ~ .checkbox-letter {\n\ }\n\ .qr-preview {\n\ -moz-box-sizing: border-box;\n\ + box-sizing: border-box;\n\ counter-increment: thumbnails;\n\ cursor: move;\n\ display: inline-block;\n\ @@ -2856,7 +3355,8 @@ a:only-of-type > .remove {\n\ position: absolute;\n\ bottom: 20px;\n\ right: 10px;\n\ - -moz-transform: translateY(-50%);\n\ + -webkit-transform: translateY(-50%);\n\ + transform: translateY(-50%);\n\ }\n\ .textarea {\n\ position: relative;\n\ @@ -2875,6 +3375,13 @@ a:only-of-type > .remove {\n\ #char-count.warning {\n\ color: red;\n\ }\n\ +#split-post {\n\ + font-size: 8pt;\n\ + position: absolute;\n\ + bottom: 2px;\n\ + left: 2px;\n\ + cursor: pointer;\n\ +}\n\ /* Menu */\n\ .menu-button:not(.fa-bars) {\n\ display: inline-block;\n\ @@ -2889,7 +3396,7 @@ a:only-of-type > .remove {\n\ margin: 2px;\n\ vertical-align: middle;\n\ }\n\ -.post .menu-button,\n\ +.postInfo > .menu-button,\n\ #thread-watcher .menu-button {\n\ width: 18px;\n\ height: 15px;\n\ @@ -2898,6 +3405,7 @@ a:only-of-type > .remove {\n\ #menu {\n\ position: fixed;\n\ outline: none;\n\ + font-weight: normal;\n\ }\n\ #menu, .submenu {\n\ border-radius: 3px;\n\ @@ -2981,6 +3489,9 @@ a:only-of-type > .remove {\n\ cursor: text !important;\n\ }\n\ /* Embedding */\n\ +.embedder:not(.embedded) > span {\n\ + display: none;\n\ +}\n\ #embedding {\n\ padding: 1px 4px 1px 4px;\n\ position: fixed;\n\ @@ -3116,6 +3627,10 @@ a:only-of-type > .remove {\n\ overflow-x: scroll !important;\n\ }\n\ .gal-image a {\n\ + display: -webkit-flex;\n\ + display: flex;\n\ + -webkit-align-items: flex-start;\n\ + align-items: flex-start;\n\ margin: auto;\n\ line-height: 0;\n\ max-width: 100%;\n\ @@ -3124,6 +3639,11 @@ a:only-of-type > .remove {\n\ width: 100%;\n\ height: 100%;\n\ }\n\ +.gal-image img,\n\ +.gal-image video {\n\ + -webkit-flex: none;\n\ + flex: none;\n\ +}\n\ .gal-fit-width .gal-image img,\n\ .gal-fit-width .gal-image video {\n\ max-width: 100%;\n\ @@ -3180,59 +3700,86 @@ a:only-of-type > .remove {\n\ bottom: 2px;\n\ vertical-align: baseline;\n\ }\n\ -.gal-buttons,\n\ -.gal-name,\n\ -.gal-count {\n\ +.gal-labels {\n\ position: fixed;\n\ - right: 195px;\n\ + bottom: 6px;\n\ + display: -webkit-flex;\n\ + display: flex;\n\ + -webkit-flex-direction: column;\n\ + flex-direction: column;\n\ + -webkit-align-items: flex-end;\n\ + align-items: flex-end;\n\ }\n\ -.gal-hide-thumbnails .gal-buttons,\n\ -.gal-hide-thumbnails .gal-count,\n\ -.gal-hide-thumbnails .gal-name {\n\ - right: 44px;\n\ +:root:not(.show-sauce) .gal-sauce {\n\ + display: none;\n\ }\n\ -.gal-name {\n\ - bottom: 6px;\n\ +.gal-name,\n\ +.gal-count,\n\ +.gal-sauce {\n\ background: rgba(0,0,0,0.6) !important;\n\ border-radius: 3px;\n\ padding: 1px 5px 2px 5px;\n\ + margin-top: 3px;\n\ + color: #ffffff !important;\n\ text-decoration: none !important;\n\ - color: white !important;\n\ +}\n\ +.gal-sauce a {\n\ + color: #ffffff !important;\n\ }\n\ .gal-name:hover,\n\ -.gal-buttons a:hover {\n\ +.gal-buttons a:hover,\n\ +.gal-sauce a:hover {\n\ color: rgb(95, 95, 101) !important;\n\ }\n\ :root.gal-pdf .gal-buttons a:hover {\n\ color: rgb(204, 204, 204) !important;\n\ }\n\ -.gal-count {\n\ - bottom: 27px;\n\ - background: rgba(0,0,0,0.6) !important;\n\ - border-radius: 3px;\n\ - padding: 1px 5px 2px 5px;\n\ - color: #ffffff !important;\n\ +.gal-buttons,\n\ +.gal-labels {\n\ + position: fixed;\n\ + right: 195px;\n\ }\n\ -:root:not(.gal-fit-width):not(.gal-pdf) .gal-name {\n\ - bottom: 23px !important;\n\ +.gal-hide-thumbnails .gal-buttons,\n\ +.gal-hide-thumbnails .gal-labels {\n\ + right: 44px;\n\ }\n\ -:root:not(.gal-fit-width):not(.gal-pdf) .gal-count {\n\ - bottom: 44px !important;\n\ +:root:not(.gal-fit-width):not(.gal-pdf) .gal-labels {\n\ + bottom: 23px !important;\n\ }\n\ :root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-buttons,\n\ -:root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-name,\n\ -:root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-count {\n\ +:root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-labels {\n\ right: 178px !important;\n\ }\n\ -:root.gal-hide-thumbnails:.gal-fit-height:not(.gal-pdf) .gal-buttons,\n\ -:root.gal-hide-thumbnails:.gal-fit-height:not(.gal-pdf) .gal-name,\n\ -:root.gal-hide-thumbnails:.gal-fit-height:not(.gal-pdf) .gal-count {\n\ +:root.gal-hide-thumbnails.gal-fit-height:not(.gal-pdf) .gal-buttons,\n\ +:root.gal-hide-thumbnails.gal-fit-height:not(.gal-pdf) .gal-labels {\n\ right: 28px !important;\n\ }\n\ :root.gallery-open.fixed #header-bar:not(.autohide),\n\ :root.gallery-open.fixed #header-bar:not(.autohide) #shortcuts .fa::before {\n\ visibility: hidden;\n\ }\n\ +/* Mod Contact Links */\n\ +.contact-links {\n\ + margin-left: 2px;\n\ +}\n\ +.move-note > a {\n\ + text-decoration: underline;\n\ +}\n\ +.invisible {\n\ + font-size: 0;\n\ +}\n\ +/* PostJumper */\n\ +.postJumper > .prev,\n\ +.postJumper > .next {\n\ + font-size: 120%;\n\ +}\n\ +/* PSA */\n\ +.fcx-announcement {\n\ + text-align: center;\n\ +}\n\ +.fcx-announcement a {\n\ + text-decoration: underline;\n\ +}\n\ /* General */\n\ :root.yotsuba .dialog {\n\ background-color: #F0E0D6;\n\ @@ -3242,6 +3789,13 @@ a:only-of-type > .remove {\n\ :root.yotsuba .field.focus {\n\ border-color: #EA8;\n\ }\n\ +/* 4chan style fixes */\n\ +:root.yotsuba.highlight-you .quotesYou$site$highlightable$reply {\n\ + border-left: 3px solid rgba(221, 0, 0, .8) !important;\n\ +}\n\ +:root.yotsuba.highlight-own .yourPost$site$highlightable$reply {\n\ + border-left: 3px dashed rgba(221, 0, 0, .8) !important;\n\ +}\n\ /* Header */\n\ :root.yotsuba #header-bar.dialog {\n\ background-color: rgba(240,224,214,0.98);\n\ @@ -3262,6 +3816,16 @@ a:only-of-type > .remove {\n\ :root.yotsuba .suboption-list > div:last-of-type {\n\ background-color: #F0E0D6;\n\ }\n\ +/* Catalog */\n\ +:root.yotsuba.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #F0E0D6;\n\ +}\n\ +:root.yotsuba.werkTyme .catalog-thread:not(:hover),\n\ +:root.yotsuba.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.yotsuba.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.yotsuba.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #D9BFB7;\n\ +}\n\ /* Quote */\n\ :root.yotsuba .backlink.deadlink {\n\ color: #00E !important;\n\ @@ -3299,8 +3863,12 @@ a:only-of-type > .remove {\n\ :root.yotsuba .focused.entry {\n\ background: rgba(255, 255, 255, .33);\n\ }\n\ +/* Unread */\n\ +:root.yotsuba .unread-mark-read {\n\ + background-color: rgba(240,224,214,0.5);\n\ +}\n\ /* Thread Watcher */\n\ -:root.yotsuba .replies-quoting-you > a, :root.yotsuba #watcher-link.disabled.replies-quoting-you {\n\ +:root.yotsuba .replies-quoting-you > a, :root.yotsuba #watcher-link.replies-quoting-you, :root.yotsuba .last-page > a > .watcher-page {\n\ color: #F00;\n\ }\n\ /* Watcher Favicon */\n\ @@ -3317,6 +3885,13 @@ a:only-of-type > .remove {\n\ :root.yotsuba-b .field.focus {\n\ border-color: #98E;\n\ }\n\ +/* 4chan style fixes */\n\ +:root.yotsuba-b.highlight-you .quotesYou$site$highlightable$reply {\n\ + border-left: 3px solid rgba(221, 0, 0, .8) !important;\n\ +}\n\ +:root.yotsuba-b.highlight-own .yourPost$site$highlightable$reply {\n\ + border-left: 3px dashed rgba(221, 0, 0, .8) !important;\n\ +}\n\ /* Header */\n\ :root.yotsuba-b #header-bar.dialog {\n\ background-color: rgba(214,218,240,0.98);\n\ @@ -3337,6 +3912,16 @@ a:only-of-type > .remove {\n\ :root.yotsuba-b .suboption-list > div:last-of-type {\n\ background-color: #D6DAF0;\n\ }\n\ +/* Catalog */\n\ +:root.yotsuba-b.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #D6DAF0;\n\ +}\n\ +:root.yotsuba-b.werkTyme .catalog-thread:not(:hover),\n\ +:root.yotsuba-b.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.yotsuba-b.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.yotsuba-b.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #B7C5D9;\n\ +}\n\ /* Quote */\n\ :root.yotsuba-b .backlink.deadlink {\n\ color: #34345C !important;\n\ @@ -3374,8 +3959,12 @@ a:only-of-type > .remove {\n\ :root.yotsuba-b .focused.entry {\n\ background: rgba(255, 255, 255, .33);\n\ }\n\ +/* Unread */\n\ +:root.yotsuba-b .unread-mark-read {\n\ + background-color: rgba(214,218,240,0.5);\n\ +}\n\ /* Thread Watcher */\n\ -:root.yotsuba-b .replies-quoting-you > a, :root.yotsuba-b #watcher-link.disabled.replies-quoting-you {\n\ +:root.yotsuba-b .replies-quoting-you > a, :root.yotsuba-b #watcher-link.replies-quoting-you {\n\ color: #F00;\n\ }\n\ /* Watcher Favicon */\n\ @@ -3412,6 +4001,16 @@ a:only-of-type > .remove {\n\ :root.futaba .suboption-list > div:last-of-type {\n\ background-color: #F0E0D6;\n\ }\n\ +/* Catalog */\n\ +:root.futaba.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #F0E0D6;\n\ +}\n\ +:root.futaba.werkTyme .catalog-thread:not(:hover),\n\ +:root.futaba.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.futaba.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.futaba.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #D9BFB7;\n\ +}\n\ /* Quote */\n\ :root.futaba .backlink.deadlink {\n\ color: #00E !important;\n\ @@ -3424,6 +4023,10 @@ a:only-of-type > .remove {\n\ :root.futaba .indicator {\n\ color: #F0E0D6;\n\ }\n\ +/* Anonymize */\n\ +:root.futaba.anonymize $site$info$name::before {\n\ + font-size: 12pt;\n\ +}\n\ /* QR */\n\ .futaba #dump-list::-webkit-scrollbar-thumb {\n\ background-color: #F0E0D6;\n\ @@ -3449,8 +4052,12 @@ a:only-of-type > .remove {\n\ :root.futaba .focused.entry {\n\ background: rgba(255, 255, 255, .33);\n\ }\n\ +/* Unread */\n\ +:root.futaba .unread-mark-read {\n\ + background-color: rgba(240,224,214,0.5);\n\ +}\n\ /* Thread Watcher */\n\ -:root.futaba .replies-quoting-you > a, :root.futaba #watcher-link.disabled.replies-quoting-you {\n\ +:root.futaba .replies-quoting-you > a, :root.futaba #watcher-link.replies-quoting-you, :root.futaba .last-page > a > .watcher-page {\n\ color: #F00;\n\ }\n\ /* Watcher Favicon */\n\ @@ -3487,6 +4094,16 @@ a:only-of-type > .remove {\n\ :root.burichan .suboption-list > div:last-of-type {\n\ background-color: #D6DAF0;\n\ }\n\ +/* Catalog */\n\ +:root.burichan.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #D6DAF0;\n\ +}\n\ +:root.burichan.werkTyme .catalog-thread:not(:hover),\n\ +:root.burichan.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.burichan.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.burichan.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #B7C5D9;\n\ +}\n\ /* Quote */\n\ :root.burichan .backlink.deadlink {\n\ color: #34345C !important;\n\ @@ -3499,6 +4116,10 @@ a:only-of-type > .remove {\n\ :root.burichan .indicator {\n\ color: #D6DAF0;\n\ }\n\ +/* Anonymize */\n\ +:root.burichan.anonymize $site$info$name::before {\n\ + font-size: 12pt;\n\ +}\n\ /* QR */\n\ .burichan #dump-list::-webkit-scrollbar-thumb {\n\ background-color: #D6DAF0;\n\ @@ -3524,8 +4145,12 @@ a:only-of-type > .remove {\n\ :root.burichan .focused.entry {\n\ background: rgba(255, 255, 255, .33);\n\ }\n\ +/* Unread */\n\ +:root.burichan .unread-mark-read {\n\ + background-color: rgba(214,218,240,0.5);\n\ +}\n\ /* Thread Watcher */\n\ -:root.burichan .replies-quoting-you > a, :root.burichan #watcher-link.disabled.replies-quoting-you {\n\ +:root.burichan .replies-quoting-you > a, :root.burichan #watcher-link.replies-quoting-you, :root.burichan .last-page > a > .watcher-page {\n\ color: #F00;\n\ }\n\ /* Watcher Favicon */\n\ @@ -3538,6 +4163,16 @@ a:only-of-type > .remove {\n\ background-color: #282A2E;\n\ border-color: #111;\n\ }\n\ +/* 4chan style fixes */\n\ +:root.tomorrow #arc-list span.quote {\n\ + color: #B5BD68;\n\ +}\n\ +:root.tomorrow.highlight-you .quotesYou$site$highlightable$reply {\n\ + border-left: 3px solid rgba(145, 182, 214, .8) !important;\n\ +}\n\ +:root.tomorrow.highlight-own .yourPost$site$highlightable$reply {\n\ + border-left: 3px dashed rgba(145, 182, 214, .8) !important;\n\ +}\n\ /* Header */\n\ :root.tomorrow #header-bar.dialog {\n\ background-color: rgba(40,42,46,0.9);\n\ @@ -3562,13 +4197,16 @@ a:only-of-type > .remove {\n\ background-color: #282A2E;\n\ }\n\ /* Catalog */\n\ -:root.tomorrow .catalog-code {\n\ - background-color: rgba(255, 255, 255, 0.1);\n\ +:root.tomorrow.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #282A2E;\n\ }\n\ -/* Quote */\n\ -:root.tomorrow .catalog-thread > .comment > span.quote, :root.tomorrow #arc-list span.quote {\n\ - color: #B5BD68;\n\ +:root.tomorrow.werkTyme .catalog-thread:not(:hover),\n\ +:root.tomorrow.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.tomorrow.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.tomorrow.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #111;\n\ }\n\ +/* Quote */\n\ :root.tomorrow .backlink.deadlink {\n\ color: #81A2BE !important;\n\ }\n\ @@ -3584,29 +4222,33 @@ a:only-of-type > .remove {\n\ :root.tomorrow .qphl {\n\ outline: 2px solid rgba(145, 182, 214, .8);\n\ }\n\ -:root.tomorrow.highlight-you .quotesYou.opContainer,\n\ -:root.tomorrow.highlight-you .quotesYou > .reply {\n\ +:root.tomorrow.highlight-you .quotesYou$site$highlightable$op,\n\ +:root.tomorrow.highlight-you .quotesYou$site$highlightable$reply {\n\ border-left: 3px solid rgba(145, 182, 214, .8);\n\ }\n\ -:root.tomorrow.highlight-own .yourPost.opContainer,\n\ -:root.tomorrow.highlight-own .yourPost > .reply {\n\ +:root.tomorrow.highlight-own .yourPost$site$highlightable$op,\n\ +:root.tomorrow.highlight-own .yourPost$site$highlightable$reply {\n\ border-left: 3px dashed rgba(145, 182, 214, .8);\n\ }\n\ -:root.tomorrow .opContainer.filter-highlight,\n\ -:root.tomorrow .filter-highlight > .reply {\n\ +:root.tomorrow .filter-highlight$site$highlightable$op,\n\ +:root.tomorrow .filter-highlight$site$highlightable$reply {\n\ box-shadow: inset 5px 0 rgba(145, 182, 214, .5);\n\ }\n\ -:root.tomorrow.highlight-own .yourPost > div.sideArrows,\n\ -:root.tomorrow.highlight-you .quotesYou > div.sideArrows,\n\ -:root.tomorrow .filter-highlight > div.sideArrows {\n\ +:root.tomorrow.highlight-own .yourPost > $site$sideArrows,\n\ +:root.tomorrow.highlight-you .quotesYou > $site$sideArrows,\n\ +:root.tomorrow .filter-highlight > $site$sideArrows {\n\ color: rgb(155, 185, 210);\n\ }\n\ -:root.tomorrow .filter-highlight .catalog-thumb,\n\ -:root.tomorrow .filter-highlight .werkTyme-filename {\n\ +:root.tomorrow .catalog-thread.filter-highlight .catalog-thumb,\n\ +:root.tomorrow.werkTyme .catalog-thread.filter-highlight:not(:hover),\n\ +:root.tomorrow.werkTyme:not(.catalog-hover-expand) .catalog-thread.filter-highlight,\n\ +:root.tomorrow.werkTyme.catalog-hover-expand .catalog-thread.filter-highlight > .catalog-container:hover > .catalog-post {\n\ box-shadow: 0 0 3px 3px rgba(64, 192, 255, .7);\n\ }\n\ :root.tomorrow .catalog-thread.watched .catalog-thumb,\n\ -:root.tomorrow .catalog-thread.watched .werkTyme-filename {\n\ +:root.tomorrow.werkTyme .catalog-thread.watched:not(:hover),\n\ +:root.tomorrow.werkTyme:not(.catalog-hover-expand) .catalog-thread.watched,\n\ +:root.tomorrow.werkTyme.catalog-hover-expand .catalog-thread.watched > .catalog-container:hover > .catalog-post {\n\ border: 2px solid rgb(64, 192, 255);\n\ }\n\ /* QR */\n\ @@ -3669,11 +4311,14 @@ a:only-of-type > .remove {\n\ background: rgba(0, 0, 0, .33);\n\ }\n\ /* Unread */\n\ -:root.tomorrow #unread-line {\n\ +:root.tomorrow .unread-line {\n\ border-color: rgb(197, 200, 198);\n\ }\n\ +:root.tomorrow .unread-mark-read {\n\ + background-color: rgba(40,42,46,0.5);\n\ +}\n\ /* Thread Watcher */\n\ -:root.tomorrow .replies-quoting-you > a, :root.tomorrow #watcher-link.disabled.replies-quoting-you {\n\ +:root.tomorrow .replies-quoting-you > a, :root.tomorrow #watcher-link.replies-quoting-you, :root.tomorrow .last-page > a > .watcher-page {\n\ color: #F00 !important;\n\ }\n\ /* Watcher Favicon */\n\ @@ -3690,6 +4335,16 @@ a:only-of-type > .remove {\n\ :root.photon .field.focus {\n\ border-color: #EA8;\n\ }\n\ +/* 4chan style fixes */\n\ +:root.photon #arc-list tr:nth-of-type(odd) span.quote {\n\ + color: #C0E17A;\n\ +}\n\ +:root.photon.highlight-you .quotesYou$site$highlightable$reply {\n\ + border-left: 3px solid rgba(221, 0, 0, .8) !important;\n\ +}\n\ +:root.photon.highlight-own .yourPost$site$highlightable$reply {\n\ + border-left: 3px dashed rgba(221, 0, 0, .8) !important;\n\ +}\n\ /* Header */\n\ :root.photon #header-bar.dialog {\n\ background-color: rgba(221,221,221,0.98);\n\ @@ -3711,13 +4366,16 @@ a:only-of-type > .remove {\n\ background-color: #DDD;\n\ }\n\ /* Catalog */\n\ -:root.photon .catalog-code {\n\ - background-color: rgba(150, 150, 150, 0.2);\n\ +:root.photon.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #DDD;\n\ }\n\ -/* Quote */\n\ -:root.photon #arc-list tr:nth-of-type(odd) span.quote {\n\ - color: #C0E17A;\n\ +:root.photon.werkTyme .catalog-thread:not(:hover),\n\ +:root.photon.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.photon.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.photon.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #CCC;\n\ }\n\ +/* Quote */\n\ :root.photon .backlink.deadlink {\n\ color: #F60 !important;\n\ }\n\ @@ -3754,8 +4412,12 @@ a:only-of-type > .remove {\n\ :root.photon .focused.entry {\n\ background: rgba(255, 255, 255, .33);\n\ }\n\ +/* Unread */\n\ +:root.photon .unread-mark-read {\n\ + background-color: rgba(221,221,221,0.5);\n\ +}\n\ /* Thread Watcher */\n\ -:root.photon .replies-quoting-you > a, :root.photon #watcher-link.disabled.replies-quoting-you {\n\ +:root.photon .replies-quoting-you > a, :root.photon #watcher-link.replies-quoting-you, :root.photon .last-page > a > .watcher-page {\n\ color: #00F !important;\n\ }\n\ /* Watcher Favicon */\n\ @@ -3763,72 +4425,271 @@ a:only-of-type > .remove {\n\ {\n\ background-image: url(\"data:image/svg+xml,\");\n\ }\n\ +/* General */\n\ +:root.spooky .dialog {\n\ + background-color: #171526;\n\ + border-color: #707070;\n\ +}\n\ +:root.spooky .field:focus,\n\ +:root.spooky .field.focus {\n\ + border-color: #98E;\n\ +}\n\ +/* 4chan style fixes */\n\ +:root.spooky #arc-list span.quote {\n\ + color: #634C2C;\n\ +}\n\ +:root.spooky.highlight-you .quotesYou$site$highlightable$reply {\n\ + border-left: 3px solid rgba(145, 182, 214, .8) !important;\n\ +}\n\ +:root.spooky.highlight-own .yourPost$site$highlightable$reply {\n\ + border-left: 3px dashed rgba(145, 182, 214, .8) !important;\n\ +}\n\ +/* Header */\n\ +:root.spooky #header-bar.dialog {\n\ + background-color: rgba(23,21,38,0.98);\n\ +}\n\ +:root.spooky:not(.fixed) #header-bar, :root.spooky #notifications {\n\ + font-size: 9pt;\n\ +}\n\ +:root.spooky #header-bar, :root.spooky #notifications {\n\ + color: #C49756;\n\ +}\n\ +:root.spooky #board-list a, :root.spooky #shortcuts a {\n\ + color: #FE9600;\n\ +}\n\ +:root.spooky.shortcut-icons .native-settings {\n\ + background-image: url('//s.4cdn.org/image/favicon-ws.ico');\n\ +}\n\ +/* Settings */\n\ +:root.spooky #fourchanx-settings fieldset, :root.spooky .section-main div::before {\n\ + border-color: #707070;\n\ +}\n\ +:root.spooky .suboption-list > div:last-of-type {\n\ + background-color: #171526;\n\ +}\n\ +/* Catalog */\n\ +:root.spooky.catalog-hover-expand .catalog-container:hover > .post {\n\ + background-color: #171526;\n\ +}\n\ +:root.spooky.werkTyme .catalog-thread:not(:hover),\n\ +:root.spooky.werkTyme:not(.catalog-hover-expand) .catalog-thread,\n\ +:root.spooky.catalog-hover-expand .catalog-container:hover > .post,\n\ +:root.spooky.catalog-hover-expand .catalog-container:hover .catalog-reply {\n\ + border-color: #707070;\n\ +}\n\ +/* Quote */\n\ +:root.spooky .backlink.deadlink {\n\ + color: #FE9600 !important;\n\ +}\n\ +:root.spooky .inline {\n\ + border-color: #707070;\n\ + background-color: rgba(255, 255, 255, .14);\n\ +}\n\ +/* Fappe and Werk Tyme */\n\ +:root.spooky .indicator {\n\ + color: #171526;\n\ +}\n\ +/* Highlighting */\n\ +:root.spooky .qphl {\n\ + outline: 2px solid rgba(145, 182, 214, .8);\n\ +}\n\ +:root.spooky.highlight-you .quotesYou$site$highlightable$op,\n\ +:root.spooky.highlight-you .quotesYou$site$highlightable$reply {\n\ + border-left: 3px solid rgba(145, 182, 214, .8);\n\ +}\n\ +:root.spooky.highlight-own .yourPost$site$highlightable$op,\n\ +:root.spooky.highlight-own .yourPost$site$highlightable$reply {\n\ + border-left: 3px dashed rgba(145, 182, 214, .8);\n\ +}\n\ +:root.spooky .filter-highlight$site$highlightable$op,\n\ +:root.spooky .filter-highlight$site$highlightable$reply {\n\ + box-shadow: inset 5px 0 rgba(145, 182, 214, .5);\n\ +}\n\ +:root.spooky.highlight-own .yourPost > $site$sideArrows,\n\ +:root.spooky.highlight-you .quotesYou > $site$sideArrows,\n\ +:root.spooky .filter-highlight > $site$sideArrows {\n\ + color: rgb(155, 185, 210);\n\ +}\n\ +/* QR */\n\ +.spooky #dump-list::-webkit-scrollbar-thumb {\n\ + background-color: #171526;\n\ + border-color: #707070;\n\ +}\n\ +:root.spooky .qr-preview {\n\ + background-color: rgba(0, 0, 0, .15);\n\ +}\n\ +:root.spooky #qr .field {\n\ + background-color: rgb(26, 27, 29);\n\ + color: rgb(197,200,198);\n\ + border-color: rgb(40, 41, 42);\n\ +}\n\ +:root.spooky #qr .field:focus,\n\ +:root.spooky #qr .field.focus {\n\ + border-color: rgb(254, 150, 0) !important;\n\ + background-color: rgb(30,32,36);\n\ +}\n\ +:root.spooky .persona button {\n\ + background: linear-gradient(to bottom, #2E3035, #222427) no-repeat;\n\ + color: rgb(197,200,198);\n\ + border-color: rgb(40, 41, 42);\n\ + outline: none;\n\ +}\n\ +:root.spooky .persona button::-moz-focus-inner {\n\ + border: none;\n\ +}\n\ +:root.spooky .persona button:focus {\n\ + border-color: rgb(254, 150, 0);\n\ +}\n\ +:root.spooky #qr.sjis-preview #sjis-toggle,\n\ +:root.spooky #qr.tex-preview #tex-preview-button {\n\ + background: rgb(26, 27, 29);\n\ +}\n\ +:root.spooky #qr select,\n\ +:root.spooky #file-n-submit > input,\n\ +:root.spooky #qr-draw-button {\n\ + border-color: rgb(40, 41, 42);\n\ +}\n\ +:root.spooky #qr-filename {\n\ + color: rgb(197,200,198);\n\ +}\n\ +:root.spooky .qr-link {\n\ + border-color: rgb(8, 6, 23) rgb(8, 6, 23) rgb(0, 0, 8);\n\ + background: linear-gradient(#262435, #171526) repeat scroll 0% 0% transparent;\n\ +}\n\ +:root.spooky .qr-link:hover {\n\ + background: #1A1829;\n\ +}\n\ +/* Menu */\n\ +:root.spooky #menu {\n\ + color: #FE9600;\n\ +}\n\ +:root.spooky .entry {\n\ + font-size: 10pt;\n\ +}\n\ +:root.spooky .focused.entry {\n\ + background: rgba(255, 255, 255, .33);\n\ +}\n\ +/* Unread */\n\ +:root.spooky .unread-line {\n\ + border-color: rgb(197, 200, 198);\n\ + visibility: visible;\n\ + opacity: 1;\n\ +}\n\ +:root.spooky .unread-mark-read {\n\ + background-color: rgba(23,21,38,0.5);\n\ +}\n\ +/* Thread Watcher */\n\ +:root.spooky .replies-quoting-you > a, :root.spooky #watcher-link.replies-quoting-you, :root.spooky .last-page > a > .watcher-page {\n\ + color: #F00 !important;\n\ +}\n\ +/* Watcher Favicon */\n\ +:root.spooky .watch-thread-link\n\ +{\n\ + background-image: url(\"data:image/svg+xml,\");\n\ +}\n\ /* Link Title Favicons */\n\ -.linkify.audio {\n\ +.linkify.audio::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAitJREFUOE9jYCAWKJWwavr0KyXWb/FIbDtUFFyzJx6nVofE2Xo5nXsj0rqPNSR0nVkR2Hjmgmfd+U9Otdf+m5Vf/6+SfeU/R9ChVVgNYDRtlfJuuPA/rPfe/4QpD/6nznj0P27Kw/9unff/69Xf+69c/+C/SO7N/0z+OAxgMmmRCe++/r9i3ev/KWvf/vdY8PK/bt/9/wrNV3/IN5y/IVt1YqNg4pGTTP4HsbuA2bhZ2qvpyn+xjIObxAp3VwqlrgngLFyryVy5nhPmZJHANS2cwYexG8BmVC/pWn3hP4NZlzWuQDJI3dIiFnUUuwEsQAOcq87jNcC7fHeLUtJxHF4AGmBWeAavAWH1+1rUUk7giAWjOknllON4DXAs2NEiG4/DBQxAF/CFHfrPYI4jDFSLuJVjNrUJhB/B7gIGo1pJRt99GAZYJK7wLJ1z7Xzl4vu/7aqv/GRBj0bjqAX2qb0nJ7mXH17C4HcUxQA+hymWtSue/C5a9up/9Ozn/7Vr7v1nRY7GqMb91T3b3v6vWvPmf/S0p/9ZQk+DDLCBRSOz06Jqk+o7/21nvfqvsebDf7kZL/5zBaxphkezd+OFn7HzXvz3Wvjmv9a8N//5Ek//ZTBpVYUrMG2X5wjcdl68+uI/wa5Lr3hSNjczGFeywOVZ/bbcVGp//F9izfv/Ql03f3P4LC/HSEQquYwMFnUCDJ7dzBhyjGZNQpye89M5gpfnMvtNUyE2h4PUAQBovvT7lyNljwAAAABJRU5ErkJggg==') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.clyp {\n\ +.linkify.bitchute::before {\n\ + content: \"\";\n\ + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAadQTFRFAAAAwzw8xDs7cY6O0iws0ysrtF9f0Sws1CwsyzU1zTIy1igoyzQ01icnY7i4t0hI0S4u0ysr1Soq1ikp1ikp1Soq0ysr0S4uu0VFzjEx1Csr1ygo2Ccn2Ccn1ygo1CoqzjExzjAw1Skp2Ccn2Ccn1ikp0TAwxzY21Soq1SoqyzQ00iws1ygo1ygo0yws1Soq1Skp1igo1igo1igo1igo1igo1igo1Coq1Soq0C0t1ygo1ygo0S4unV5e1Csr1ygo1ygo1CsruUdHxzg41Skp1ygo1CsryTU10C4u1igo1ycn1ygo0i0txjo60S0t1ikp1ygo1ikp1Cws1Coq1Coq0yws0S0tyzQ00iws1Soq0ysr0i0txDs72Ccn2CUl2CYm2CQk3EFB2S8v2zw82jY24FZW3D0931FR3EBA3UND8LS04FVV7qys4V9f4WBg+erq766u9t7e7qqq2Ckp54KC9+Pj6pSU+Ojo5XNz9NHR6YqK8bu765ub5G5u9M3N6ImJ88vL5XV165eX3UVF6pWV3UhI2Soq2jU12Coq2jQ02Cgo2Sws////FaxLuAAAAF10Uk5TAAAAAAAAAAAAAAAAAAAAASJnoLy9oWolAhBz1vr72XgTGKf8/a4cCpuiDVvz9mS6xOvy9vzg6aGsPOToRAFv9fh2Awm07XgIMd765UEDOsfemVhhY00nBommbCkEI8horgAAAL5JREFUKBUFwbFKA0EUQNF7387sMq4EmzRpLSSdIBYKFv6Af2prnSYkRT4gWFgkCBJQ0EIFdcZzBCeqqh4qdk7VW2ChPusw02sAYKU7z7wEAAA2piQKFbrWSHazc1J0XWs5pdxPDykcVX+7Y9UxUsSo+s7PibqPFBRV/C5qi4i/UkrJrc7L47Bt4ZWnUaMCAE9GSrtKBQD2fR+bnAEAeOn7dUTOwApe35bDsPz0zsniQlV98IN0tJ3f6P0XAMA/kxou7OXCdnoAAAAASUVORK5CYII=') center left no-repeat!important;\n\ + padding-left: 18px;\n\ +}\n\ +.linkify.clyp::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAwUExURTSY22ey5E2l4KbS75rM7Y3F64C/6f///8zl9nS45r/f9PL5/UGe3bPY8Vqr4v///wNjrzUAAAABYktHRA8YugDZAAAAB3RJTUUH4AINEi85AIH95AAAAE9JREFUCNdjYMAGGBWgDGYHCM2a3hkAZmi0dzSBaKaO9o5moCqmLiCjYzNQyw4QowIodQzI6E0AKcpo72gE6+Jyb1kAMehUA9RktgdYbQYAjGIVNGGXBJkAAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.dailymotion {\n\ +.linkify.dailymotion::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAnUExURQBk3ff6/trp+kKO5wZt3xx54q7P9Ozz/IS17zOG5WKh653E8sbc9/GbbcoAAABZSURBVAjXY2BAASyhDhAGc9oECMOjyAAiESEEYrBYpLWBGcwHxcvBjDDxHelghpF0yDQwY3kVgweEUeEQDWbMEepqAjO8FMsLIeYsU8o+BrbCdWboTAe4AwALXxWGjW41FwAAAABJRU5ErkJggg==') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.gfycat {\n\ +.linkify.gfycat::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAjVBMVEWn3gCo3gSr3w2t4BSu4Bav4Ri35C+45DK45DO55DXA50rA50vB50zC6E/D6FTF6VjG6VvL62vN7G/P7XbQ7XfW74vY8JDa8ZTe8qDe8qLf86Pi9Kzj9K7k9LHp9sDp98Lq98Ps+Mr0++L5/O75/fD6/fH6/fL6/fP7/fT7/fb8/ff8/vj8/vn+/v7///91X4cfAAAAcklEQVR42o3M2xKBUACF4aVQckrIuRJK6H//x2sme4/MuPDfre9i6c/Cc3U5Dj87BuAxsXvGu6JvIIXEHRWwNHCHQNrCzkAFkbSBg4EM8i+Yw7PXBa3zRfuxVyf/Bis7nKwGKAcWxgC8prI5Sc315OlnDfzpDar2S9/oAAAAAElFTkSuQmCC') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.gist {\n\ +.linkify.gist::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABblBMVEXc3NykpKTW1tbb29ugoKCdnZ0AAAACAgIEDRcKCgoMDAwODg4QIzYRDAoTExMUDwwVAg0WICsaEw8aGhoiCBklGxUmERwwKCQ7LSU7Ozs8LSZFLyNINi1JNyxJNy1KSklMOi5VR1FXV1daQTRkZGRseYZwU0F4eHh7dnR8bWV/YE6IdGiKcGCKkJaNgYeNjY2RdGOScWCUcWCZmZmhoaGkpKSoqKirfmaurq6xsbG1tbW6urq+vr7AbmzBb23CwsLGxsbHx8fHyMjJycnJysrMzMzOiYbPi4fQ0NDRoYbT09PU1NTW1tbY2NjZqIzZ2dnb29vd3d3f39/i4uLktZrk5OTl5eXm5ubn5+fo6Ojq6urs7OzttKLu7u7wuqbw8PDx8fHz8/P4+Pj5+fn7uZj8vpz9ya79ybD/tZf/upr/wZ//w6H/xKH/xaL/xrH/yqj/y7T/zqv/z7D/07D/17n/2Lv/2Lz/3L//38n/4Mk3Q/ZuAAAABnRSTlMSFcbGzc5MNKFvAAAA1klEQVQoz2NgYPZHAswMDEwRSclwkBTBxOARn4gE4j0YXBOiJNUDg7y8Ar1UlOITXBkcY73Z2Li42dg42dn4wmIdGeyjQ7nZoEA4PNqewSZKlw0O9KJsGKwjBdl4ZeWkJGQUhNjEIq0ZrMI5+D0ri7Jz8itCRAXCrRgsQ3mUy+xicrPSbfO0REItGSyCVaVL3ONSU9LcCtQUgy0YzIJ85M1LizMzCsv9xF2CzBhMAwN99TV1DI0MtDWcAgNNGUycA5CAswkDi5kDwrMOZiwMjKzGSICVEQDhZj0UQV7PewAAAABJRU5ErkJggg==') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.image {\n\ +.linkify.image::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAs5JREFUOE+lk/tvi1EYx98/xT8gW4REIpGFMEQWl2FiM9ZMZhm2xRAyOsmujFFmdFRHu0tWm87UypxStr69zPauN5e5rHVp3IYhbOvHy+wHEQlxkm+ek+d8nm9OznkeSfrfldmgJC7QyUlTymsJTfuTZ25z4HdWYwyLreYhtpgekGPw0+kKvo1Eo+IXRSIiEhkWZuc9tqnsJD9EqTUopCxjSGTpB0iueczSo1HyW8cpsExQ1DbxI2pt45j9cXpexul4FEd79RnZphAa/SD7WvuFtO6UItbU9LC+YQxNI2w0wwYT5LRAdhOU3oBTIXC9gXP3oUSGgz2vST3gYHejR0jptT1C332f8yrUEYHrz8CgxDnpm6DKCUfc0KnmXa/AEVPPwnDcD0cvetA2uYRk67Ive/lpjO7YBO1PPuF8Df3vwf4cbNE4tqdw7YVq8HYyHx6FvhE1hkMEg8HDUqvFkjT4aIjMqkqyqkswDSrcfBfH+Q561YLAZ/B+BLda6FXlU/cPv0AoEPhuoP1h4Av7Wbh9E/Py15NWWUjeSR3nZDfeN+N0DY9hG/7K1eGP3P0S5/EYRFUF/IOTBrUXHPm9fT6mr1xEwupkZqxbzLyiDJYUZ5NSnkdqdSHpxyrYdFpPgdmAsdfJwPMI/Yr65bf7tZLGGBQ7DNdJWFtIYvoOZmbuZE7OXpIKKli86zAr9p9gTVktWTVnKTI2U95uRWe3U2IJUDbVB5p6hVm5x5m9Vc/cnedZUNzC8lILaQesZBy6hEZ3maKzgvJWFzVWD9XtXvVGQbSWASFtMATVRlJIKbOTWtlJXaeXepuPM1f6MNp9GLt8mLvvYLmp0OhQ2Fwvk6m7xaqDTvY0eYWUVtcnllXfYlGpnfklVuraHHg8HjxuN+6fktUHlWWZPaZeUo/ILK0UKttBcbNbSB9GP0yLxWJJUxoZGUn80zD9C/vXQ/4NHY10h3M1zmQAAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.installgentoo {\n\ +.linkify.installgentoo::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABcVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB3dIYAAAAAAAAAAAAbGh4BBAcCBgoBBgoCBwsCCQ/QzucCCA7MyuXZ1eUBBQmTh8fo5/i9svIAAADh3vQAAAACCA0CCQ8CCQ4DDBQbGCUDChDr6vgAAAAAAAAREBIDCxK6tdfe2fTv7/cDCxIDDBQEDRUHDhgMJjXk4PZdXWdLUFoUNEYOKDgSMUMRLUBneI4eTGj08/QmW3onW3rTzvfOx/giU3IiVHMkWHdEaYJobHv3+PokWHpua6TNy9xZgZ+1quz8/foQKj0XPFInWn0nW38tZ4o6fqg8gq48grA9hrU/i7pAhrNAiLdBjLtEjr1FksNIjr5Il8pImMtKWnNqhL97odKFqti5q/q5rPq60+nCt/vLw/vPx/jV0vHY0/rc1/rg2/vh3fzn4fzu6/vx8vf19Pv19Pz49/v5+Pv8/Pv8/fr9/vv+/frziVtUAAAAT3RSTlMABQYHCAoNDhARGRobL0ZOV1xdXV5fYGBmZnB0eX2MjZSaoaGio6mqqqustLq7zubo6Ojo6evt7u/x8fLy9/f4+Pj5+vr6+vr6+/39/v7+XKgUSwAAAMhJREFUKM9jYGDg4OZmZgABKINT1dBAhBHIYFMxMBIDisjbhoZbCTExsCu5hoeY8DEwcOkEx8fY6MqpucTGB0izglVEplcU5/gmRYWBVQDNMK+s0hN3SvMyBpsBNJxXw0NfwTEjVQZqHQMHj5RfWW5mliSEC7TPzK6yJD/bXZQRzGdXcisqLy309okA2Q4Eis4peQWmstqBCdGW/CABraC45ERBBs3A6Fh/AbAKTwsHa34QZW8NVsGuLqwswQSjQICTmYMFQaEDAAF8JHLfKGswAAAAAElFTkSuQmCC') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.liveleak {\n\ +.linkify.liveleak::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAlNJREFUGBkFwU2LVmUYAODrPu8Z5x1xSpRBXQyFoLsBE+wfiO5atJOgnf9DUPwFgtGinUgEaQsRhHYuMtpEiEWuG5iNjuOcj+c8z911xXcXL/68c3Dw1fzhg0QgEQAAEYGUKXFie9vxlSs/xk/rdavjGEkmkWSih65z4osv9GfOiK6LzEyZ2uGh4dUrmzs72ddlUUhkoiMr4PT167589Mh6c1N0nSRlqrX67dat+PDyZXRT19m5edPnt28rGFHxMcJ6d9fprS1/37tneP3aemPD1uamUydPOru3p5DdGOH0tWsu3LhhxIQJM2qEpRT/Pn3q/du3AhARSmvGTH0lplKMrVkiYpVpQaJlighzhDkzhmEA0fcWoqAfyaFW4zTlgCABxlrNmY4ylUzLsiREprFWc0T2M+ZSjKWY0AEaltZUjJixZJIpuk5pTWlNP2BYFvOyKJkCAKU1tTXHrZlqVWolUxdhxsfVSj9FmJfFMM9GdICGGa01HyMstYpMIFPJVNDPmYZSTOPoOEKHzNRlKpmWWh1j6TpLa2SKTKVWU6Z+Qolwdm/P9QcPZKa2LH69e9eIMs+WCL/cv2/98CGZPrt61am+V9APq1X89eyZ/968obVYaiXT4dGREgG+vnPHeHgYMsH2+fP+efEihtVKv7SWw/6+9/v7KYLMhIywTJPamvOXLomukyRsrNf+ePzYkpl9dJ3SWgSCSCQCfz5/7pMLF2yfO6eLiAQcHRz4/cmT+HR7O+Ob3d0fNt69+7a2BiICQCJbA0EgE5lpvbXl1OXL3/8Pfax4+6SjSukAAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.pastebin {\n\ +.linkify.pastebin::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAB1FBMVEUAAAAAAAAAAABWYWwAAABbY3BbYm5dZnFdZXJeZnMEBAQHCAhYYGpdZnFdZnBgaHIlJyomKCooKi09QkdESU5eZGtdYmhdYmleY2lrcXdqb3Rqb3Rqb3SSmJ+SlJeWmJutr7GtrrCWm6ChpKhbW1tmZmZvb290dHR3d3d4eHh5eXl6enp8fHx+gIJ/f3+CgoKDg4OEhISFhYWHh4eKioqKjI2Li4uMjIyOjo6Pj4+QkJCRkZGSkpKUlJSVl5mWlpaYmZqZm52ampqbm5ucnJydnZ2enp6fn5+hoaGioqKkpKSkpaalpaWmp6mmp6qnqauoqKioqquoqq2qqqqrrK2srKysra6srrCsrrGurq6vr6+wsLCxsbGysrKztLa0tLS1t7m2tra3t7e4uLi5ubm6urq7u7u8vLy9vb2+vr7AwMDAwsTBwcHExcfFxcXFxsnGxsbHx8fIyMjJycnMzMzNzc3Ozs7O0NLPz8/Q0NDR0dHR09XT09PV1dXV1dbV1tfV19rW1tbX19fX19jY2tzZ2dnZ2tva2tra3N3a3N7c3Nze3t7f39/f4OHg4ODi4uLl5+jm5ubs7Ozs7e3u7u7v7+/v8PDw8PDx8fHy8vLz8/P29vYSoLMZAAAAJHRSTlMABAUGCwsNHCAiLzMzMzZEYGJwgIuOnJycnqmqq9bc3+/w8fkZ0N/uAAAA/klEQVQoU2NgYGDl5YMDdgYGBmZZ3964CYFtIR3e9Q7K/AwMHI55KfaFmcHWMy3K3MwlGRg4wz0zdYpcorRbNbL0LaWAAp3ts2umV8wo6MupTauQBgqUG03VL7W3sfZSb1erAgm02M+yzYrVCXUy6zapAQlUx/dEdyX3J3ZHVUYVywAF8o2rDNN1Go2jzGLMokAC2QbuSc42mXmaOXop9iAtCXrJ5qXWjT59Abl2ESJAAX/tSIMMiyrrqQ3T6uS5gQK6kSqpqkUermGTexQFmYACflqR+hlWZSamzQpCLEDPsSmVVDT1TJw0JUhOAMRnYOARFRMTE5cQF+ZiBPIAII5B3EVG0b4AAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.soundcloud {\n\ - background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABsklEQVQ4y5WTy2pUQRCGv2rbzDjJeAlIBmOyipGIIJqFEBDElwh4yULGeRFXPoEIBl/AvQ/gC2RnxCAoxijiwks852S6+3dxzslcHJCpTXVX11/Xv0097gLPgVNMJxnQNfX4zsqleWbnpoMf/oa9d988MM9MC/rp+E0a+A0dsVobMNMCOO8B6McRoABJI+A6gJmN3D2A8jgEBCEkSEMBrcrsDAzDWWn3AjgKFaDMmgRqniGFgsaDp1jrLOngDf1XT1D+A1dFc4MKAkkiCVKjjVu7g9+4Rzx4i1u6hjXbuMWr0O5QPNvCu7IaCZwEKQukLGDrm5x8uI0tr6MkiGlkiv7yLfzN+6S5i6QsIMABkEfcxhbWWYMkVAOjxvYAjc3HNHrbKI9VBQBFwF25XQKSBjqIf1YBuAurEMrczgDygD6/x2LCpFLXLUyQ+PoldphhBhYfIX09XU1+Flaukz7uYqs3SHs7cG4BmTsmkBUF9mmXEwa28BNLPaQPLepuNcbGSWQquQC2/Kdcox1FUGkcB0ykck1nA2+wTzMs8stGnP4rbWGw74EuS/GFQWfK7/wF6P4F7fzIAYkdmdEAAAAASUVORK5CYII=') center left no-repeat!important;\n\ +.linkify.peertube::before {\n\ + content: \"\";\n\ + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABIFBMVEUhHyAAAABzPBnxaA3CWBEnJSYbGRptbW16enpzc3PTayWhb04hHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyAhHyApIh+0UhMfHiBWMhvsZg7zaQ0hHyAhHyAXHCHzaQ3xaA3xaA3xaA3xaA0hHyAhHyDxaA3xaA3xaA3xaA3xaA3xaA0oJickIiMdGxwUEhPxaA3xaA3xaA1sbGxwcHB3d3eFhYXxaA3xaA3xaA1zc3Nzc3Nzc3Nzc3Nzc3PxaA3xaA3xaA1zc3Nzc3NtdHjxaA3xaA1yc3STcFnvaA/yaAxzc3N4c2/FbDFzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3MhHyDxaA1zc3MAAAAfljyVAAAAXHRSTlMAAAAAAAAAAAAAAAAZkjMBHOLXYArj8p0u2VsJ1XaGL/OhKyXc1WEN2gwk2/SjKgEYiS4B/tYFGosqAdleAxzj12ML9Z8s850rJWbYeYMs1F8Koiri1V0MGZY0AYbIBFIAAAABYktHRAH/Ai3eAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4wYXFBUVX81QWQAAAKxJREFUGNNVz9UWgkAQANDBtdbu7lZsxe7ubpH//wxBPKDzNvdMAmi0Oj0QQgAYjCazBX7BStvsDqHoAzTtdLklf+Dx+vwICRAIhsKRaCyOvpAwJ6Up8pXOZHOIAFm+UCzJEQuvMhWrIFBUa/WGkodmq40Ad7q9/kDFwnA05lpYYCbT2ZykFvxQDhhmuVpvcvxaHra7vfp72KflcMSYEOB0vlyx+By+3R9PMSfe+P0enM1454kAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDYtMjRUMDM6MjE6MjEtMDc6MDDse6MAAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTA2LTI0VDAzOjIxOjIxLTA3OjAwnSYbvAAAAABJRU5ErkJggg==') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.twitchtv {\n\ - background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAYUExURf///2RBpWRBpWRBpWRBpWRBpWRBpf///+zQyUYAAAAGdFJOUwFdZX0lTzs4r5oAAAABYktHRAcWYYjrAAAAB3RJTUUH4AINEi42iSXRNAAAAD1JREFUCNdjYEiDAAZGGIMtjQEEUBlMCWoEGci6mGEMsxQgIy0BiB3AjLS0FAYQIw0kwABipoI1AhkBQBIAFCIXxiHgq80AAAAASUVORK5CYII=') center left no-repeat!important;\n\ +.linkify.soundcloud::before {\n\ + content: \"\";\n\ + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABsklEQVQ4y5WTy2pUQRCGv2rbzDjJeAlIBmOyipGIIJqFEBDElwh4yULGeRFXPoEIBl/AvQ/gC2RnxCAoxijiwks852S6+3dxzslcHJCpTXVX11/Xv0097gLPgVNMJxnQNfX4zsqleWbnpoMf/oa9d988MM9MC/rp+E0a+A0dsVobMNMCOO8B6McRoABJI+A6gJmN3D2A8jgEBCEkSEMBrcrsDAzDWWn3AjgKFaDMmgRqniGFgsaDp1jrLOngDf1XT1D+A1dFc4MKAkkiCVKjjVu7g9+4Rzx4i1u6hjXbuMWr0O5QPNvCu7IaCZwEKQukLGDrm5x8uI0tr6MkiGlkiv7yLfzN+6S5i6QsIMABkEfcxhbWWYMkVAOjxvYAjc3HNHrbKI9VBQBFwF25XQKSBjqIf1YBuAurEMrczgDygD6/x2LCpFLXLUyQ+PoldphhBhYfIX09XU1+Flaukz7uYqs3SHs7cG4BmTsmkBUF9mmXEwa28BNLPaQPLepuNcbGSWQquQC2/Kdcox1FUGkcB0ykck1nA2+wTzMs8stGnP4rbWGw74EuS/GFQWfK7/wF6P4F7fzIAYkdmdEAAAAASUVORK5CYII=') center left no-repeat!important;\n\ + padding-left: 18px;\n\ +}\n\ +.linkify.streamable::before {\n\ + content: \"\";\n\ + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABXFBMVEUPkPoNj/qExv0PkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoNj/oPkPoNj/oNj/qExv0PkPpruvwPkPornfoVk/opnPpnufwPkPqExv0Nj/oPkPoNj/oPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoPkPoOj/opnPsVk/oMjvoOkPoTkfo6pPsblfo3ovva7v7////v9/5Sr/whmPry+f5htvze8P7W7P5itvyl1v0imPu84P3o9P50v/zN6P73+/8lmvs8pfs+pfsKjvr9/v9EqfsNj/oom/v8/v9nufxAp/tJq/sQkPrb7v6t2f0IjPoclvr6/f9luPwUkvrp9f7h8f5ruvy/4f4kmftpuvwxoPum1v32+/8jmfpMrPvu9/7z+f9UsPs7pPv8/f/4/P9oufwalfpDqPsMj/ounvtVsPsnm/qzfQQ9AAAALXRSTlMAAAAggMzw0IYkBPb4iAamsgZ+jPwogpDO1vTYlPoulL4KivyUCiqO1PL01i67tUAWAAAAAWJLR0Q4oAel1gAAAAd0SU1FB+MGFxMuDXVcMbIAAADdSURBVBjTY2AAAmYWVjY2dg5OBgZGJiCXi4VbFwx4ePlAAlz8unAgIAgUENJFAsJMDMw8unp6+gaGRsYmpoa6IqIMYrp6ZuYWllbW5hY2toZ64gwSurp29g6OTs4urm7uHrqSDGy6nhZeet5WPr5+/gGBelJAgSCLYL+Q0LBw3YjIKKAAu250TGxcvE1CYlJySqquNAOHrl9aukVGZla2RU6uoZ4MA6esrl9evnWBYWFRMdBaOQYGXmSHyQNdyieA4CsogjzHpyQL4SqrqIJ9y8Cgpq6hqaWtogPyPgDmvSxRxBWM9AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxOS0wNi0yNFQwMjo0NjoxMy0wNzowMCKUvXUAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTktMDYtMjRUMDI6NDY6MTMtMDc6MDBTyQXJAAAAAElFTkSuQmCC') center left no-repeat!important;\n\ + padding-left: 18px;\n\ +}\n\ +.linkify.twitchtv::before {\n\ + content: \"\";\n\ + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAYUExURf///2RBpWRBpWRBpWRBpWRBpWRBpf///+zQyUYAAAAGdFJOUwFdZX0lTzs4r5oAAAABYktHRAcWYYjrAAAAB3RJTUUH4AINEi42iSXRNAAAAD1JREFUCNdjYEiDAAZGGIMtjQEEUBlMCWoEGci6mGEMsxQgIy0BiB3AjLS0FAYQIw0kwABipoI1AhkBQBIAFCIXxiHgq80AAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.twitter {\n\ +.linkify.twitter::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAEsUExURf///1Cf21Gg3FGi31Gh3VKj4FGh3lKj4VKk4lKl41Ol5FOn51Sp6VSo6FOn5lCf21Gg3FGh3VGi31Gi31Gh3lGg3FGg3FGg3FGg3FGh3lGg3FGi31Kk4lKj4FGh3lGi31Kk4lGh3lGg3FGh3lOm5FOm5VGi31Kj4VSo6FGi31Gh3VGg3FKj4FOn51Gi31So6FWr7VOl5FGi31On51Sq6lKk4lOo51Sp6VOm5FSq61Ws7VOn51Oo51Sq61Ol5FOm5FSq61Wr7VOo51On51Sr7FWs7VSp6lGg3FGh3VOm5FWr7VSp6lKj4VOm5FSo6FSr7FWs7VWs7VWr7VSq6lOo51Om5FOo51So6FOm5VOl5FSq61Ws7VSr7FSp6lSp6VWs7lWr7VKk4lSq6v///6E3MNsAAABVdFJOUwAAAAAAAAAAAAAAAAAAAB0Ii+3xnBVTJhfsMKb+qTEp9GwBF/7lLAbo0m4pLkUTdvk2Ev3+EZnOBo/3Z8ffCRzH/D0OqPxiLnvx3UI8m9n1++GwXQZNS29BAAAAAWJLR0QAiAUdSAAAAAd0SU1FB+ACDRIwBwy67tEAAADKSURBVBjTY2BAB4xogIGRH8IQEBQSFhEVE2eQkJQC8ZmkQ8PCI2Rk5RjkIxUUlRgZlVWioqNjYlXVGNQ14iI1tbR14qLj4+MTdJkZ9PQNosJCE0OjgPz4KEMWBiPjhPiEmKQokIJ4E1MmBmazhHg4MGdlYmCzsLSC8ROsmRkZmFht4Eps7ViADmOzd4DyHZ2YmYACTOzOLmATXd04mIBOd/eQ9owFCXh5c7KB/MLi4+vnHxAYFBzCwcYEEmBi5uLm4eHl42RmAnsSAMZBLgZiFUQ5AAAAAElFTkSuQmCC') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.video {\n\ +.linkify.video::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAxgDGAP8nNqN7AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gMZBjQQLEEqGwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAA5SURBVDjLY2AYaMDIwMDwn1JD/lPCZhpwL+B1wf///ykzgBhDiAoDfIYQZQAjIyP5BuDTPJqQqAQAvW0ZAMk8+EEAAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.vimeo {\n\ +.linkify.vidlii::before {\n\ + content: \"\";\n\ + background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAACvlBMVEUCWv8HXf8AWv8AAAD///8AVP+bqP8AWv8AVO4AOqUAGkgAyf8APa0AL4QABAsASdEAVv8AUv8AUv8AVP8AWP8BWv8JXv8RYv8QYv8DW/8DXP8xdv9RiP9Af/8IXf8AKP8KXv8JXf8NYf8aaf8ATP0UZP0AVf8AT/8AT/8AVv8ATedvnPVAf/8AT/sYZvl0o/8PYf8udf8aa/8FXf8AVf8AOrRBe/Nvn/8AUv0aaPkXZ/8ATv8AKYQZYuwIXf8ca/wTZP8ASP8AED0HUNwZaf8xdPwDWv8AAAAAQMRcjvQAU/8AMZssb/Jmmf8AU/8AJXsRW+dSif8AUv8AAAAASdQtdP8ATv8AQ/8AQv8APbtKgfQud/8XZ/8TZP8FXP8AKIIcZO4wdP8AF08KU95tnv4gafhZi/Rnl/ZzofcocP8AAAAAQ8Q4efRwnvVmlvVcjvgrcfsAQsQAOK0APrwAQcUEStMLXPgDWv8AHE8APLEARdIAQ80ASeEAVf8AOJkAAAAAAAAAAAAABBMAJJIAY/9rmP+vxv90n/+buPv29/7C1P+zx/n///2Crv/7+fjs8f++z/f///3l6fX9/f/L2fj9/P5ilv9Nh/3h6f6vyf/D0vT///2lwP/Z5Pf3+P9OiP9klvr9/Puzyf+QsPX//fnW4v/k6vfv8/86ev94pfj///uRtf/y8vby9f9Fgv9EgPzt7/jj7f8mcf+eufj///x1pP/Z4fT///52pf9Uivv09fnV4v8ncf64yvj7+/6vxPX///yyyf9ynvr6+vvG1/8ocv3O2/fz9v53ofX8+/nb5v+YuPz//vy0yv8vdP3e5/fn7v/p7PX09//b5P7///6eu/9Df/zq7vjc5//I1vT//v3+/v////9+q/9Tivnn6fPy8/rW4fzI1/2qwv6YtPT8+fX39/jz9PqJrveTsvqfpuxrAAAAhXRSTlMAAAAAAAAAAAAAAAAAAAAABSlERA45nrSzP3TZ7e12Ao2LusMcrJYhFwaR/uhCwP/x5tZzBWHy+n3OvA8u17jmpwgPrOz5jAF2+3FA7PdYG8fuPQaX5jAGAV/39MCmdy/e/RGz/vj5/f/rAXj4//z13n52i5qmmFQ1lqOQaTgIBAYKEAYAKGjtAgAAAKNJREFUGBkFwT0uRGEYBtD3ZJ77uT8iGrXCAixCr7OCyRA2oCKqiUYkOgoJwhqUbMAKLEChVYhk4pxswvcWfFGVEbYtuJutqir9Ibc0uh0+V+mf5gY69yN2PzKJiTjCg8qa3uLRAJpKM9AMoL1VOi9zJ4CQ9z0jwHX+RAwAURUxAMSB/L7u35wCGlKaHrDkPGVmwhlc6FN6l1iHKxupn+djAPgHrEwa+qrzy0oAAAAASUVORK5CYII=') center left no-repeat!important;\n\ + padding-left: 18px;\n\ +}\n\ +.linkify.vimeo::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAYFBMVEUAAAAIdZUKh6sLlLkLmr4LmsAMp88NrdYVW3MZj7Acstkrt9s1e5E7vN5EfI9JvdtKwuBijp5kpbl30eiDt8aG1uqRr7qTyNehxM+k4PCy3enB3OTg6Ovv9PXw+fz////L9U5WAAAAAXRSTlMAQObYZgAAAIFJREFUeNplz90OwiAMBWAQpAoyxclkP3je/y0H2AQXz0WT8100rRD6kNI9/cRroemQL3hXhoujZYj4OHoAmBvYGcBISwbWBvfXCrytnIDUQMkbsBpagMA7zhtQdyTFQAmIG7IkYniiZuh3XGsPqoOZkMOJOpAcLqUzNFGGu/57fwc1hgtp0mVSyQAAAABJRU5ErkJggg==') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.vine {\n\ +.linkify.vine::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAHCUExURQAAAAC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+jwC+j////54tRLAAAACUdFJOUwAAAEK+9/e+QQIDAwEqzskfUZmUNHz2mrT++V1w+f5tCanNFUDwfEjtjAyyvg027Hki27QMBJzaHE/1+FkNsN0iZvv6bxyAlB589lQeyud0KB8PQO+ZBUrc+eXgcRG/3CoATe316Wxw/P6BAgBt+fp4IAwh0d4zM9q7Fm76qi605EMSrvfX/PRtAivF9IAJNMLxhA2KYlJ9AAAAAWJLR0SVCGB6gwAAAAd0SU1FB+ACDRI2MOJd7FgAAADrSURBVBjTLY9VWwJgGEPfiYWBha2YYHcHditgd3d3odjdivvBfgK727nYsyPiCrw03j6+fv6AaAMCgyAI1lElJBQSFh6hBxDJqOiY2Lh4SEKiIQlITmFqWrqRJkhGJrOA7Bzm5uUXsBBSVMySUpSVs6KyqrqmFmKuY30D0NjU3NLa1t6h9jvZ1Q30WGi19fb1KzAwyKFhYGSUY+MTkwpMTXNmFpibX+Di0rICWFldW9/A5tb2zu7ePtTrg0MeHePklPYzuDRw7uDF5RWvbwC32O0d7x8en55f4DHF6xv5/vHp6f/k6/vH+evuf1LAObptvSvrAAAAAElFTkSuQmCC') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.vocaroo {\n\ +.linkify.vocaroo::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAw9JREFUOE9jYMABuMwYmCyTJKUCGlSnFSy02TTzeOyCiQcDViX26qVz2TAyYtWmEMwuoZ3M7V40LcB79pHkc0svpvzY8jD//87nxf+3Pyn8v/ZO8v+VNyP/2mZJumI1QCWSI8232Hjumitlfw5+qPp/9l8TCt76JP//xkdx/wsXWCzjtWFkwTCkbWFe9plPk/+ga4Txz/xt/D/hkN//gMXif21a+NbyWjIwoRiy6GDT5rP/mlFsPfyp5n/NpOj/22+0gMUXXIz/H7hC/L/bFKFbPDZMrHAD5H35OPt2J9zacDv/f3V7xv9FhwrBGubsT/1//Pjx/1GJ/mD+/nfl/1v3Ovy3KRJNQbHdOlXCvOO03/+pm1P/v3v37n90hhtYw9HPtf8Xb2v937cmHswHeWPRxYj/LvkK3igGKARwicTO07118H3V/5kbi/4vPZMJtK3s/6YH2f+Pfq1B8VbjWrdnMu5s4nAD9CNFhKwz5DTUvLl419zKvAcLtG1P84BRl/b/5M/6/6f/NPzf/qzo84yj0Uus0xUU4Zor54bm9+4OfZG02OCuoAMTb9ZkC9ull1Nvrr2Z+XvRpaRfc65H/68F+jl9svEhzyLFWoccWVc+eyTHq/twydjlKRln7jX9bNMkMJnbhoFRL1xCqmKx6/yi2fYXa/c5/e846PV/5fW0/7OPx/yfcjzop34ulxdGGvDuU8mMXaX507lBuiN6ueadmQeT/p/93vf/1O+G//sP5fw/eL3o/5JLif8zVxs+Tlir9S26UyeFQQvJGBE7FvaFZ9LfN+1y+WjbItSb3GmXvXd15v8zroH/HxgE/D+aGPx/18vi/z07PeZNPRKxe/Kh0Ae8toxscCO4zBkYXArk9C1SxJUYjBkYPPIVtbbuTftz3cz//2O9wP/75iSAXdO72/dt2HL5F6YlfBW4MiJYXMiBiW3t7azHBx+V/t89N+H/8a+1//e9K/9attDp5LQjYX8SuvVL8RoAkmxa65299Erq1FnHo0qrl7t4BddriIs4MrM3rfWcFd+pGwVSAwBZ0bKP8yrZPAAAAABJRU5ErkJggg==') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ -.linkify.youtube {\n\ +.linkify.youtube::before {\n\ + content: \"\";\n\ background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAMCAYAAABr5z2BAAABIklEQVQoz53LvUrDUBjG8bOoOammSf1IoBSvoCB4JeIqOHgBLt6AIMRBBQelWurQ2kERnMRBsBUcIp5FJSBI5oQsJVkkUHh8W0o5nhaFHvjBgef/Mq+Q46RJBMkI/vE+aOus956tnEswIZe1LV0QyJ5sE2GzgZfVMtRNIdiDpccEssdlB1mW4bvTwdvWJtRdErM7U+8S/FJykCRJX5qm+KpVce8UMNLRLbulz4iSjTAMh6Iowsd5BeNadp3nUF0VlxAEwZBotXC0Usa4ll3meZdA1iguwvf9vpvDA2wvmKgYGtSud8suDB4TyGr2PF49D/vra9jRZ1BVdknMzgwuCGSnZEObwu6sBnVTCHZiaC7BhFx2PKdxUidiAH/4lLo9Mv0DELVs9qsOHXwAAAAASUVORK5CYII=') center left no-repeat!important;\n\ padding-left: 18px;\n\ }\n\ @@ -3850,12 +4711,56 @@ report: }\n\ #captchaContainerAlt td:nth-child(2) {\n\ display: table-cell !important;\n\ -}\n", +}\n\ +/* Archive reports */\n\ +#archive-report {\n\ + padding: 3px;\n\ +}\n\ +#archive-report-enabled {\n\ + vertical-align: middle;\n\ +}\n\ +#archive-report > label {\n\ + display: block;\n\ +}\n\ +#archive-report-reason {\n\ + display: block;\n\ + width: 98%;\n\ +}\n\ +.archive-report-success {\n\ + color: green;\n\ +}\n\ +.archive-report-error {\n\ + color: red;\n\ +}", www: "#captcha-cnt {\n\ height: auto;\n\ -}\n" +}\n\ +:root:not(.js-enabled) #form {\n\ + display: block;\n\ +}\n\ +#bd > div[style], #bd > div[style] > * {\n\ + height: auto !important;\n\ + margin: 0 !important;\n\ + font-size: 0;\n\ +}\n", + +sub: function(css) { + var variables = { + site: g.SITE.selectors + }; + return css.replace(/\$[\w\$]+/g, function(name) { + var words = name.slice(1).split('$'); + var sel = variables; + for (var i = 0; i < words.length; i++) { + if (typeof sel !== 'object') return ':not(*)'; + sel = $.getOwn(sel, words[i]); + } + if (typeof sel !== 'string') return ':not(*)'; + return sel; + }); +} }; @@ -3917,104 +4822,180 @@ $ = (function() { } }; + $.dict = function() { + return Object.create(null); + }; + + $.dict.clone = function(obj) { + var arr, i, j, key, map, ref, val; + if (typeof obj !== 'object' || obj === null) { + return obj; + } else if (obj instanceof Array) { + arr = []; + for (i = j = 0, ref = obj.length; j < ref; i = j += 1) { + arr.push($.dict.clone(obj[i])); + } + return arr; + } else { + map = Object.create(null); + for (key in obj) { + val = obj[key]; + map[key] = $.dict.clone(val); + } + return map; + } + }; + + $.dict.json = function(str) { + return $.dict.clone(JSON.parse(str)); + }; + + $.hasOwn = function(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); + }; + + $.getOwn = function(obj, key) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + return obj[key]; + } else { + return void 0; + } + }; + $.ajax = (function() { - var lastModified; - lastModified = {}; - return function(url, options, extra) { - var err, event, form, i, len, r, ref, ref1, type, upCallbacks, whenModified; + var pageXHR; + try { + pageXHR = window.wrappedJSObject && !XMLHttpRequest.wrappedJSObject ? XPCNativeWrapper(window.wrappedJSObject.XMLHttpRequest) : XMLHttpRequest; + } catch (error) { + pageXHR = XMLHttpRequest; + } + return function(url, options) { + var err, form, headers, key, onloadend, onprogress, r, ref, responseType, timeout, type, value, withCredentials; if (options == null) { options = {}; } - if (extra == null) { - extra = {}; + if (options.responseType == null) { + options.responseType = 'json'; } - type = extra.type, whenModified = extra.whenModified, upCallbacks = extra.upCallbacks, form = extra.form; - url = url.replace(/^((?:https?:)?\/\/(?:\w+\.)?4c(?:ha|d)n\.org)\/adv\//, '$1//adv/'); - r = new XMLHttpRequest(); - type || (type = form && 'post' || 'get'); + options.type || (options.type = options.form && 'post' || 'get'); + url = url.replace(/^((?:https?:)?\/\/(?:\w+\.)?(?:4chan|4channel|4cdn)\.org)\/adv\//, '$1//adv/'); + onloadend = options.onloadend, timeout = options.timeout, responseType = options.responseType, withCredentials = options.withCredentials, type = options.type, onprogress = options.onprogress, form = options.form, headers = options.headers; + r = new pageXHR(); try { r.open(type, url, true); - if (whenModified) { - if (((ref = lastModified[whenModified]) != null ? ref[url] : void 0) != null) { - r.setRequestHeader('If-Modified-Since', lastModified[whenModified][url]); - } - $.on(r, 'load', function() { - return (lastModified[whenModified] || (lastModified[whenModified] = {}))[url] = r.getResponseHeader('Last-Modified'); - }); - } - if (/\.json$/.test(url)) { - if (options.responseType == null) { - options.responseType = 'json'; - } - } - $.extend(r, options); - $.extend(r.upload, upCallbacks); + ref = headers || {}; + for (key in ref) { + value = ref[key]; + r.setRequestHeader(key, value); + } + $.extend(r, { + onloadend: onloadend, + timeout: timeout, + responseType: responseType, + withCredentials: withCredentials + }); + $.extend(r.upload, { + onprogress: onprogress + }); $.on(r, 'error', function() { if (!r.status) { - return c.error("4chan X failed to load: " + url); + return c.warn("4chan X failed to load: " + url); } }); r.send(form); - } catch (_error) { - err = _error; + } catch (error) { + err = error; if (err.result !== 0x805e0006) { throw err; } - ref1 = ['error', 'loadend']; - for (i = 0, len = ref1.length; i < len; i++) { - event = ref1[i]; - r["on" + event] = options["on" + event]; - $.queueTask($.event, event, null, r); - } + r.onloadend = onloadend; + $.queueTask($.event, 'error', null, r); + $.queueTask($.event, 'loadend', null, r); } return r; }; })(); + $.lastModified = $.dict(); + + $.whenModified = function(url, bucket, cb, options) { + var ajax, headers, params, r, ref, t, timeout, url0; + if (options == null) { + options = {}; + } + timeout = options.timeout, ajax = options.ajax; + params = []; + if ($.engine === 'blink') { + params.push("s=" + bucket); + } + if (url.split('/')[2] === 'a.4cdn.org') { + params.push("t=" + (Date.now())); + } + url0 = url; + if (params.length) { + url += '?' + params.join('&'); + } + headers = $.dict(); + if ((t = (ref = $.lastModified[bucket]) != null ? ref[url0] : void 0) != null) { + headers['If-Modified-Since'] = t; + } + r = (ajax || $.ajax)(url, { + onloadend: function() { + var base; + ((base = $.lastModified)[bucket] || (base[bucket] = $.dict()))[url0] = this.getResponseHeader('Last-Modified'); + return cb.call(this); + }, + timeout: timeout, + headers: headers + }); + return r; + }; + (function() { var reqs; - reqs = {}; + reqs = $.dict(); $.cache = function(url, cb, options) { - var err, req, rm; - if (req = reqs[url]) { - if (req.readyState === 4) { + var ajax, onloadend, req; + if (options == null) { + options = {}; + } + ajax = options.ajax; + if ((req = reqs[url])) { + if (req.callbacks) { + req.callbacks.push(cb); + } else { $.queueTask(function() { - return cb.call(req, req.evt, true); + return cb.call(req, { + isCached: true + }); }); - } else { - req.callbacks.push(cb); } return req; } - rm = function() { - return delete reqs[url]; - }; - try { - if (!(req = $.ajax(url, options))) { - return; + onloadend = function() { + var fn1, j, len, ref; + if (!this.status) { + delete reqs[url]; } - } catch (_error) { - err = _error; - return; - } - $.on(req, 'load', function(e) { - var fn1, i, len, ref; - this.evt = e; ref = this.callbacks; fn1 = (function(_this) { return function(cb) { return $.queueTask(function() { - return cb.call(_this, e, false); + return cb.call(_this, { + isCached: false + }); }); }; })(this); - for (i = 0, len = ref.length; i < len; i++) { - cb = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + cb = ref[j]; fn1(cb); } return delete this.callbacks; + }; + req = (ajax || $.ajax)(url, { + onloadend: onloadend }); - $.on(req, 'abort error', rm); req.callbacks = [cb]; return reqs[url] = req; }; @@ -4030,12 +5011,16 @@ $ = (function() { $.cb = { checked: function() { - $.set(this.name, this.checked); - return Conf[this.name] = this.checked; + if ($.hasOwn(Conf, this.name)) { + $.set(this.name, this.checked); + return Conf[this.name] = this.checked; + } }, value: function() { - $.set(this.name, this.value.trim()); - return Conf[this.name] = this.value; + if ($.hasOwn(Conf, this.name)) { + $.set(this.name, this.value.trim()); + return Conf[this.name] = this.value; + } } }; @@ -4108,19 +5093,19 @@ $ = (function() { }; $.addClass = function() { - var className, classNames, el, i, len; + var className, classNames, el, j, len; el = arguments[0], classNames = 2 <= arguments.length ? slice.call(arguments, 1) : []; - for (i = 0, len = classNames.length; i < len; i++) { - className = classNames[i]; + for (j = 0, len = classNames.length; j < len; j++) { + className = classNames[j]; el.classList.add(className); } }; $.rmClass = function() { - var className, classNames, el, i, len; + var className, classNames, el, j, len; el = arguments[0], classNames = 2 <= arguments.length ? slice.call(arguments, 1) : []; - for (i = 0, len = classNames.length; i < len; i++) { - className = classNames[i]; + for (j = 0, len = classNames.length; j < len; j++) { + className = classNames[j]; el.classList.remove(className); } }; @@ -4150,13 +5135,13 @@ $ = (function() { }; $.nodes = function(nodes) { - var frag, i, len, node; + var frag, j, len, node; if (!(nodes instanceof Array)) { return nodes; } frag = $.frag(); - for (i = 0, len = nodes.length; i < len; i++) { - node = nodes[i]; + for (j = 0, len = nodes.length; j < len; j++) { + node = nodes[j]; frag.appendChild(node); } return frag; @@ -4195,19 +5180,19 @@ $ = (function() { }; $.on = function(el, events, handler) { - var event, i, len, ref; + var event, j, len, ref; ref = events.split(' '); - for (i = 0, len = ref.length; i < len; i++) { - event = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + event = ref[j]; el.addEventListener(event, handler, false); } }; $.off = function(el, events, handler) { - var event, i, len, ref; + var event, j, len, ref; ref = events.split(' '); - for (i = 0, len = ref.length; i < len; i++) { - event = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + event = ref[j]; el.removeEventListener(event, handler, false); } }; @@ -4230,6 +5215,7 @@ $ = (function() { } return root.dispatchEvent(new CustomEvent(event, { bubbles: true, + cancelable: true, detail: detail })); }; @@ -4243,8 +5229,8 @@ $ = (function() { return new CustomEvent('x', { detail: {} }); - } catch (_error) { - err = _error; + } catch (error) { + err = error; unsafeConstructors = { Object: unsafeWindow.Object, Array: unsafeWindow.Array @@ -4268,13 +5254,18 @@ $ = (function() { } return root.dispatchEvent(new CustomEvent(event, { bubbles: true, + cancelable: true, detail: clone(detail) })); }; } })(); - $.open = typeof GM_openInTab !== "undefined" && GM_openInTab !== null ? GM_openInTab : function(url) { + $.modifiedClick = function(e) { + return e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0; + }; + + $.open = (typeof GM !== "undefined" && GM !== null ? GM.openInTab : void 0) != null ? GM.openInTab : typeof GM_openInTab !== "undefined" && GM_openInTab !== null ? GM_openInTab : function(url) { return window.open(url, '_blank'); }; @@ -4324,23 +5315,23 @@ $ = (function() { } })(); - $.globalEval = function(code, data) { - var script; - script = $.el('script', { - textContent: code - }); - if (data) { - $.extend(script.dataset, data); - } - $.add(d.head || doc, script); - return $.rm(script); - }; - $.global = function(fn, data) { + var script; if (doc) { - return $.globalEval("(" + fn + ")();", data); + script = $.el('script', { + textContent: "(" + fn + ").call(document.currentScript.dataset);" + }); + if (data) { + $.extend(script.dataset, data); + } + $.add(d.head || doc, script); + $.rm(script); + return script.dataset; } else { - return fn(); + try { + fn.call(data); + } catch (error) {} + return data; } }; @@ -4363,6 +5354,34 @@ $ = (function() { return video.mozHasAudio || !!video.webkitAudioDecodedByteCount; }; + $.luma = function(rgb) { + return rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114; + }; + + $.unescape = function(text) { + if (text == null) { + return text; + } + return text.replace(/<[^>]*>/g, '').replace(/&(amp|#039|quot|lt|gt|#44);/g, function(c) { + return { + '&': '&', + ''': "'", + '"': '"', + '<': '<', + '>': '>', + ',': ',' + }[c]; + }); + }; + + $.isImage = function(url) { + return /\.(jpe?g|jfif|png|gif|bmp|webp|avif|jxl)$/i.test(url); + }; + + $.isVideo = function(url) { + return /\.(webm|mp4|ogv)$/i.test(url); + }; + $.engine = (function() { if (/Edge\//.test(navigator.userAgent)) { return 'edge'; @@ -4380,252 +5399,368 @@ $ = (function() { $.platform = 'userscript'; - try { - localStorage.getItem('x'); - $.hasStorage = true; - } catch (_error) { - $.hasStorage = false; - } + $.hasStorage = (function() { + try { + if (localStorage.getItem(g.NAMESPACE + 'hasStorage') === 'true') { + return true; + } + localStorage.setItem(g.NAMESPACE + 'hasStorage', 'true'); + return localStorage.getItem(g.NAMESPACE + 'hasStorage') === 'true'; + } catch (error) { + return false; + } + })(); $.item = function(key, val) { var item; - item = {}; + item = $.dict(); item[key] = val; return item; }; - $.syncing = {}; - - if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { - $.getValue = GM_getValue; - $.listValues = function() { - return GM_listValues(); - }; - } else if ($.hasStorage) { - $.getValue = function(key) { - return localStorage[key]; + $.oneItemSugar = function(fn) { + return function(key, val, cb) { + if (typeof key === 'string') { + return fn($.item(key, val), cb); + } else { + return fn(key, val); + } }; - $.listValues = function() { - var key, results; + }; + + $.syncing = $.dict(); + + $.securityCheck = function(data) { + if (location.protocol !== 'https:') { + return delete data['Redirect to HTTPS']; + } + }; + + if (((typeof GM !== "undefined" && GM !== null ? GM.deleteValue : void 0) != null) && window.BroadcastChannel && (typeof GM_addValueChangeListener === "undefined" || GM_addValueChangeListener === null)) { + $.syncChannel = new BroadcastChannel(g.NAMESPACE + 'sync'); + $.on($.syncChannel, 'message', function(e) { + var cb, key, ref, results, val; + ref = e.data; results = []; - for (key in localStorage) { - if (key.slice(0, g.NAMESPACE.length) === g.NAMESPACE) { - results.push(key); + for (key in ref) { + val = ref[key]; + if ((cb = $.syncing[key])) { + results.push(cb($.dict.json(JSON.stringify(val)), key)); } } return results; + }); + $.sync = function(key, cb) { + return $.syncing[key] = cb; }; - } else { - $.getValue = function() {}; - $.listValues = function() { - return []; - }; - } - - if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { - $.setValue = GM_setValue; - $.deleteValue = GM_deleteValue; - } else if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { - $.oldValue = {}; - $.setValue = function(key, val) { - GM_setValue(key, val); - if (key in $.syncing) { - $.oldValue[key] = val; - if ($.hasStorage) { - return localStorage[key] = val; - } + $.forceSync = function() {}; + $["delete"] = function(keys, cb) { + var key; + if (!(keys instanceof Array)) { + keys = [keys]; } - }; - $.deleteValue = function(key) { - GM_deleteValue(key); - if (key in $.syncing) { - delete $.oldValue[key]; - if ($.hasStorage) { - return localStorage.removeItem(key); + return Promise.all((function() { + var j, len, results; + results = []; + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + results.push(GM.deleteValue(g.NAMESPACE + key)); } - } - }; - if (!$.hasStorage) { - $.cantSync = true; - } - } else if ($.hasStorage) { - $.oldValue = {}; - $.setValue = function(key, val) { - if (key in $.syncing) { - $.oldValue[key] = val; - } - return localStorage[key] = val; - }; - $.deleteValue = function(key) { - if (key in $.syncing) { - delete $.oldValue[key]; - } - return localStorage.removeItem(key); + return results; + })()).then(function() { + var items, j, key, len; + items = $.dict(); + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + items[key] = void 0; + } + $.syncChannel.postMessage(items); + return typeof cb === "function" ? cb() : void 0; + }); }; - } else { - $.setValue = function() {}; - $.deleteValue = function() {}; - $.cantSync = $.cantSet = true; - } - - if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { - $.sync = function(key, cb) { - return $.syncing[key] = GM_addValueChangeListener(g.NAMESPACE + key, function(key2, oldValue, newValue, remote) { - if (remote) { - if (newValue !== void 0) { - newValue = JSON.parse(newValue); + $.get = $.oneItemSugar(function(items, cb) { + var key, keys; + keys = Object.keys(items); + return Promise.all((function() { + var j, len, results; + results = []; + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + results.push(GM.getValue(g.NAMESPACE + key)); + } + return results; + })()).then(function(values) { + var i, j, len, val; + for (i = j = 0, len = values.length; j < len; i = ++j) { + val = values[i]; + if (val) { + items[keys[i]] = $.dict.json(val); } - return cb(newValue, key); } + return cb(items); + }); + }); + $.set = $.oneItemSugar(function(items, cb) { + var key, val; + $.securityCheck(items); + return Promise.all((function() { + var results; + results = []; + for (key in items) { + val = items[key]; + results.push(GM.setValue(g.NAMESPACE + key, JSON.stringify(val))); + } + return results; + })()).then(function() { + $.syncChannel.postMessage(items); + return typeof cb === "function" ? cb() : void 0; + }); + }); + $.clear = function(cb) { + return GM.listValues().then(function(keys) { + return $["delete"](keys.map(function(key) { + return key.replace(g.NAMESPACE, ''); + }), cb); + })["catch"](function() { + return $["delete"](Object.keys(Conf).concat(['previousversion', 'QR Size', 'QR.persona']), cb); }); }; - $.forceSync = function() {}; - } else if ((typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) || $.hasStorage) { - $.sync = function(key, cb) { - key = g.NAMESPACE + key; - $.syncing[key] = cb; - return $.oldValue[key] = $.getValue(key); - }; - (function() { - var onChange; - onChange = function(arg) { - var cb, key, newValue; - key = arg.key, newValue = arg.newValue; - if (!(cb = $.syncing[key])) { - return; + } else { + if (typeof GM_deleteValue === "undefined" || GM_deleteValue === null) { + $.perProtocolSettings = true; + } + if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { + $.getValue = GM_getValue; + $.listValues = function() { + return GM_listValues(); + }; + } else if ($.hasStorage) { + $.getValue = function(key) { + return localStorage.getItem(key); + }; + $.listValues = function() { + var key, results; + results = []; + for (key in localStorage) { + if (key.slice(0, g.NAMESPACE.length) === g.NAMESPACE) { + results.push(key); + } } - if (newValue != null) { - if (newValue === $.oldValue[key]) { - return; + return results; + }; + } else { + $.getValue = function() {}; + $.listValues = function() { + return []; + }; + } + if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { + $.setValue = GM_setValue; + $.deleteValue = GM_deleteValue; + } else if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { + $.oldValue = $.dict(); + $.setValue = function(key, val) { + GM_setValue(key, val); + if (key in $.syncing) { + $.oldValue[key] = val; + if ($.hasStorage) { + return localStorage.setItem(key, val); } - $.oldValue[key] = newValue; - return cb(JSON.parse(newValue), key.slice(g.NAMESPACE.length)); - } else { - if ($.oldValue[key] == null) { - return; + } + }; + $.deleteValue = function(key) { + GM_deleteValue(key); + if (key in $.syncing) { + delete $.oldValue[key]; + if ($.hasStorage) { + return localStorage.removeItem(key); } + } + }; + if (!$.hasStorage) { + $.cantSync = true; + } + } else if ($.hasStorage) { + $.oldValue = $.dict(); + $.setValue = function(key, val) { + if (key in $.syncing) { + $.oldValue[key] = val; + } + return localStorage.setItem(key, val); + }; + $.deleteValue = function(key) { + if (key in $.syncing) { delete $.oldValue[key]; - return cb(void 0, key.slice(g.NAMESPACE.length)); } + return localStorage.removeItem(key); }; - $.on(window, 'storage', onChange); - return $.forceSync = function(key) { - key = g.NAMESPACE + key; - return onChange({ - key: key, - newValue: $.getValue(key) + } else { + $.setValue = function() {}; + $.deleteValue = function() {}; + $.cantSync = $.cantSet = true; + } + if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { + $.sync = function(key, cb) { + return $.syncing[key] = GM_addValueChangeListener(g.NAMESPACE + key, function(key2, oldValue, newValue, remote) { + if (remote) { + if (newValue !== void 0) { + newValue = $.dict.json(newValue); + } + return cb(newValue, key); + } }); }; - })(); - } else { - $.sync = function() {}; - $.forceSync = function() {}; - } - - $["delete"] = function(keys) { - var i, key, len; - if (!(keys instanceof Array)) { - keys = [keys]; - } - for (i = 0, len = keys.length; i < len; i++) { - key = keys[i]; - $.deleteValue(g.NAMESPACE + key); - } - }; - - $.get = function(key, val, cb) { - var items; - if (typeof cb === 'function') { - items = $.item(key, val); + $.forceSync = function() {}; + } else if ((typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) || $.hasStorage) { + $.sync = function(key, cb) { + key = g.NAMESPACE + key; + $.syncing[key] = cb; + return $.oldValue[key] = $.getValue(key); + }; + (function() { + var onChange; + onChange = function(arg) { + var cb, key, newValue; + key = arg.key, newValue = arg.newValue; + if (!(cb = $.syncing[key])) { + return; + } + if (newValue != null) { + if (newValue === $.oldValue[key]) { + return; + } + $.oldValue[key] = newValue; + return cb($.dict.json(newValue), key.slice(g.NAMESPACE.length)); + } else { + if ($.oldValue[key] == null) { + return; + } + delete $.oldValue[key]; + return cb(void 0, key.slice(g.NAMESPACE.length)); + } + }; + $.on(window, 'storage', onChange); + return $.forceSync = function(key) { + key = g.NAMESPACE + key; + return onChange({ + key: key, + newValue: $.getValue(key) + }); + }; + })(); } else { - items = key; - cb = val; + $.sync = function() {}; + $.forceSync = function() {}; } - return $.queueTask($.getSync, items, cb); - }; - - $.getSync = function(items, cb) { - var key, val2; - for (key in items) { - if ((val2 = $.getValue(g.NAMESPACE + key))) { - items[key] = JSON.parse(val2); + $["delete"] = function(keys) { + var j, key, len; + if (!(keys instanceof Array)) { + keys = [keys]; } - } - return cb(items); - }; - - $.set = function(keys, val, cb) { - var key, value; - if (typeof keys === 'string') { - $.setValue(g.NAMESPACE + keys, JSON.stringify(val)); - } else { - for (key in keys) { - value = keys[key]; - $.setValue(g.NAMESPACE + key, JSON.stringify(value)); + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + $.deleteValue(g.NAMESPACE + key); } - cb = val; - } - return typeof cb === "function" ? cb() : void 0; - }; - - $.clear = function(cb) { - var id; - $["delete"](Object.keys(Conf)); - $["delete"](['previousversion', 'AutoWatch', 'QR Size', 'captchas', 'QR.persona', 'hiddenPSA']); - $["delete"]((function() { - var i, len, ref, results; - ref = ['embedding', 'updater', 'thread-stats', 'thread-watcher', 'qr']; - results = []; - for (i = 0, len = ref.length; i < len; i++) { - id = ref[i]; - results.push(id + ".position"); + }; + $.get = $.oneItemSugar(function(items, cb) { + return $.queueTask($.getSync, items, cb); + }); + $.getSync = function(items, cb) { + var err, key, val2; + for (key in items) { + if ((val2 = $.getValue(g.NAMESPACE + key))) { + try { + items[key] = $.dict.json(val2); + } catch (error) { + err = error; + if (!/^(?:undefined)*$/.test(val2)) { + throw err; + } + } + } } - return results; - })()); - try { - $["delete"]($.listValues().map(function(key) { - return key.replace(g.NAMESPACE, ''); - })); - } catch (_error) {} - return typeof cb === "function" ? cb() : void 0; - }; + return cb(items); + }; + $.set = $.oneItemSugar(function(items, cb) { + $.securityCheck(items); + return $.queueTask(function() { + var key, value; + for (key in items) { + value = items[key]; + $.setValue(g.NAMESPACE + key, JSON.stringify(value)); + } + return typeof cb === "function" ? cb() : void 0; + }); + }); + $.clear = function(cb) { + $["delete"](Object.keys(Conf)); + $["delete"](['previousversion', 'QR Size', 'QR.persona']); + try { + $["delete"]($.listValues().map(function(key) { + return key.replace(g.NAMESPACE, ''); + })); + } catch (error) {} + return typeof cb === "function" ? cb() : void 0; + }; + } return $; }).call(this); $$ = (function() { - var slice = [].slice; + var $$, + slice = [].slice; - return function(selector, root) { + $$ = function(selector, root) { if (root == null) { root = d.body; } return slice.call(root.querySelectorAll(selector)); }; + return $$; + }).call(this); CrossOrigin = (function() { - var CrossOrigin; + var CrossOrigin, Request; CrossOrigin = { binary: function(url, cb, headers) { - var options, ref, workaround; + var fallback, gmOptions; if (headers == null) { - headers = {}; + headers = $.dict(); } - url = url.replace(/^((?:https?:)?\/\/(?:\w+\.)?4c(?:ha|d)n\.org)\/adv\//, '$1//adv/'); - workaround = $.engine === 'gecko' && (typeof GM_info !== "undefined" && GM_info !== null) && /^[0-2]\.|^3\.[01](?!\d)/.test(GM_info.version); - workaround || (workaround = /PaleMoon\//.test(navigator.userAgent)); - workaround || (workaround = (typeof GM_info !== "undefined" && GM_info !== null ? (ref = GM_info.script) != null ? ref.includeJSB : void 0 : void 0) != null); - options = { + url = url.replace(/^((?:https?:)?\/\/(?:\w+\.)?(?:4chan|4channel|4cdn)\.org)\/adv\//, '$1//adv/'); + fallback = function() { + return $.ajax(url, { + headers: headers, + responseType: 'arraybuffer', + onloadend: function() { + if (this.status && this.response) { + return cb(new Uint8Array(this.response), this.getAllResponseHeaders()); + } else { + return cb(null); + } + } + }); + }; + if (!(((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) != null) || (typeof GM_xmlhttpRequest !== "undefined" && GM_xmlhttpRequest !== null))) { + fallback(); + return; + } + gmOptions = { method: "GET", url: url, headers: headers, + responseType: 'arraybuffer', + overrideMimeType: 'text/plain; charset=x-user-defined', onload: function(xhr) { - var contentDisposition, contentType, data, i, r, ref1, ref2; - if (workaround) { + var data, i, r; + if (xhr.response instanceof ArrayBuffer) { + data = new Uint8Array(xhr.response); + } else { r = xhr.responseText; data = new Uint8Array(r.length); i = 0; @@ -4633,12 +5768,8 @@ CrossOrigin = (function() { data[i] = r.charCodeAt(i); i++; } - } else { - data = new Uint8Array(xhr.response); } - contentType = (ref1 = xhr.responseHeaders.match(/Content-Type:\s*(.*)/i)) != null ? ref1[1] : void 0; - contentDisposition = (ref2 = xhr.responseHeaders.match(/Content-Disposition:\s*(.*)/i)) != null ? ref2[1] : void 0; - return cb(data, contentType, contentDisposition); + return cb(data, xhr.responseHeaders); }, onerror: function() { return cb(null); @@ -4647,27 +5778,28 @@ CrossOrigin = (function() { return cb(null); } }; - if (workaround) { - options.overrideMimeType = 'text/plain; charset=x-user-defined'; - } else { - options.responseType = 'arraybuffer'; + try { + return ((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) || GM_xmlhttpRequest)(gmOptions); + } catch (error) { + return fallback(); } - return GM_xmlhttpRequest(options); }, file: function(url, cb) { - return CrossOrigin.binary(url, function(data, contentType, contentDisposition) { - var blob, match, mime, name, ref, ref1, ref2, ref3; + return CrossOrigin.binary(url, function(data, headers) { + var blob, contentDisposition, contentType, match, mime, name, ref, ref1, ref2, ref3, ref4; if (data == null) { return cb(null); } - name = (ref = url.match(/([^\/]+)\/*$/)) != null ? ref[1] : void 0; + name = (ref = url.match(/([^\/?#]+)\/*(?:$|[?#])/)) != null ? ref[1] : void 0; + contentType = (ref1 = headers.match(/Content-Type:\s*(.*)/i)) != null ? ref1[1] : void 0; + contentDisposition = (ref2 = headers.match(/Content-Disposition:\s*(.*)/i)) != null ? ref2[1] : void 0; mime = (contentType != null ? contentType.match(/[^;]*/)[0] : void 0) || 'application/octet-stream'; - match = (contentDisposition != null ? (ref1 = contentDisposition.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)) != null ? ref1[1] : void 0 : void 0) || (contentType != null ? (ref2 = contentType.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)) != null ? ref2[1] : void 0 : void 0); + match = (contentDisposition != null ? (ref3 = contentDisposition.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)) != null ? ref3[1] : void 0 : void 0) || (contentType != null ? (ref4 = contentType.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)) != null ? ref4[1] : void 0 : void 0); if (match) { name = match.replace(/\\"/g, '"'); } - if ((typeof GM_info !== "undefined" && GM_info !== null ? (ref3 = GM_info.script) != null ? ref3.includeJSB : void 0 : void 0) != null) { - mime = QR.typeFromExtension[name.match(/[^.]*$/)[0].toLowerCase()] || 'application/octet-stream'; + if (/^text\/plain;\s*charset=x-user-defined$/i.test(mime)) { + mime = $.getOwn(QR.typeFromExtension, name.match(/[^.]*$/)[0].toLowerCase()) || 'application/octet-stream'; } blob = new Blob([data], { type: mime @@ -4676,43 +5808,117 @@ CrossOrigin = (function() { return cb(blob); }); }, - json: (function() { - var callbacks, responses; - callbacks = {}; - responses = {}; - return function(url, cb) { - if (responses[url]) { - cb(responses[url]); - return; - } - if (callbacks[url]) { - callbacks[url].push(cb); - return; - } - callbacks[url] = [cb]; - return GM_xmlhttpRequest({ - method: "GET", - url: url + '', - onload: function(xhr) { - var j, len, ref, response; - response = JSON.parse(xhr.responseText); - ref = callbacks[url]; - for (j = 0, len = ref.length; j < len; j++) { - cb = ref[j]; - cb(response); + Request: Request = (function() { + function Request() {} + + Request.prototype.status = 0; + + Request.prototype.statusText = ''; + + Request.prototype.response = null; + + Request.prototype.responseHeaderString = null; + + Request.prototype.getResponseHeader = function(headerName) { + var header, i, j, key, len, ref, ref1, ref2, val; + if ((this.responseHeaders == null) && (this.responseHeaderString != null)) { + this.responseHeaders = $.dict(); + ref = this.responseHeaderString.split('\r\n'); + for (j = 0, len = ref.length; j < len; j++) { + header = ref[j]; + if ((i = header.indexOf(':')) >= 0) { + key = header.slice(0, i).trim().toLowerCase(); + val = header.slice(i + 1).trim(); + this.responseHeaders[key] = val; } - delete callbacks[url]; - return responses[url] = response; - }, - onerror: function() { - return delete callbacks[url]; - }, - onabort: function() { - return delete callbacks[url]; } - }); + } + return (ref1 = (ref2 = this.responseHeaders) != null ? ref2[headerName.toLowerCase()] : void 0) != null ? ref1 : null; + }; + + Request.prototype.abort = function() {}; + + Request.prototype.onloadend = function() {}; + + return Request; + + })(), + ajax: function(url, options) { + var gmOptions, gmReq, headers, onloadend, req, responseType, timeout; + if (options == null) { + options = {}; + } + onloadend = options.onloadend, timeout = options.timeout, responseType = options.responseType, headers = options.headers; + if (responseType == null) { + responseType = 'json'; + } + if (!(((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) != null) || (typeof GM_xmlhttpRequest !== "undefined" && GM_xmlhttpRequest !== null))) { + return $.ajax(url, options); + } + req = new CrossOrigin.Request(); + req.onloadend = onloadend; + gmOptions = { + method: 'GET', + url: url, + headers: headers, + timeout: timeout, + onload: function(xhr) { + var response; + try { + response = (function() { + switch (responseType) { + case 'json': + if (xhr.responseText) { + return JSON.parse(xhr.responseText); + } else { + return null; + } + break; + default: + return xhr.responseText; + } + })(); + $.extend(req, { + response: response, + status: xhr.status, + statusText: xhr.statusText, + responseHeaderString: xhr.responseHeaders + }); + } catch (error) {} + return req.onloadend(); + }, + onerror: function() { + return req.onloadend(); + }, + onabort: function() { + return req.onloadend(); + }, + ontimeout: function() { + return req.onloadend(); + } }; - })() + try { + gmReq = ((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) || GM_xmlhttpRequest)(gmOptions); + } catch (error) { + return $.ajax(url, options); + } + if (gmReq && typeof gmReq.abort === 'function') { + req.abort = function() { + try { + return gmReq.abort(); + } catch (error) {} + }; + } + return req; + }, + cache: function(url, cb) { + return $.cache(url, cb, { + ajax: CrossOrigin.ajax + }); + }, + permission: function(cb) { + return cb(); + } }; return CrossOrigin; @@ -4728,12 +5934,35 @@ Board = (function() { }; function Board(ID) { + var ref; this.ID = ID; + this.boardID = this.ID; + this.siteID = g.SITE.ID; this.threads = new SimpleDict(); this.posts = new SimpleDict(); + this.config = ((ref = BoardConfig.boards) != null ? ref[this.ID] : void 0) || {}; g.boards[this] = this; } + Board.prototype.cooldowns = function() { + var c, c2, i, key, len, ref; + c2 = (this.config || {}).cooldowns || {}; + c = { + thread: c2.threads || 0, + reply: c2.replies || 0, + image: c2.images || 0, + thread_global: 300 + }; + if (d.cookie.indexOf('pass_enabled=1') >= 0) { + ref = ['reply', 'image']; + for (i = 0, len = ref.length; i < len; i++) { + key = ref[i]; + c[key] = Math.ceil(c[key] / 2); + } + } + return c; + }; + return Board; })(); @@ -4752,6 +5981,8 @@ Callbacks = (function() { Callbacks.CatalogThread = new Callbacks('Catalog Thread'); + Callbacks.CatalogThreadNative = new Callbacks('Catalog Thread'); + function Callbacks(type) { this.type = type; this.keys = []; @@ -4766,19 +5997,26 @@ Callbacks = (function() { return this[name] = cb; }; - Callbacks.prototype.execute = function(node, keys) { + Callbacks.prototype.execute = function(node, keys, force) { var err, errors, i, len, name, ref, ref1, ref2; if (keys == null) { keys = this.keys; } + if (force == null) { + force = false; + } + if (node.callbacksExecuted && !force) { + return; + } + node.callbacksExecuted = true; for (i = 0, len = keys.length; i < len; i++) { name = keys[i]; try { if ((ref = this[name]) != null) { ref.call(node); } - } catch (_error) { - err = _error; + } catch (error) { + err = error; if (!errors) { errors = []; } @@ -4811,17 +6049,19 @@ CatalogThread = (function() { }; function CatalogThread(root, thread) { + var post; this.thread = thread; this.ID = this.thread.ID; this.board = this.thread.board; + post = this.thread.OP.nodes.post; this.nodes = { root: root, - thumb: $('.catalog-thumb', root), - icons: $('.catalog-icons', root), - postCount: $('.post-count', root), - fileCount: $('.file-count', root), - pageCount: $('.page-count', root), - comment: $('.comment', root) + thumb: $('.catalog-thumb', post), + icons: $('.catalog-icons', post), + postCount: $('.post-count', post), + fileCount: $('.file-count', post), + pageCount: $('.page-count', post), + replies: null }; this.thread.catalogView = this; } @@ -4834,6 +6074,34 @@ CatalogThread = (function() { }).call(this); +CatalogThreadNative = (function() { + var CatalogThreadNative; + + CatalogThreadNative = (function() { + CatalogThreadNative.prototype.toString = function() { + return this.ID; + }; + + function CatalogThreadNative(root) { + this.nodes = { + root: root, + thumb: $(g.SITE.selectors.catalog.thumb, root) + }; + this.siteID = g.SITE.ID; + this.boardID = this.nodes.thumb.parentNode.pathname.split(/\/+/)[1]; + this.board = g.boards[this.boardID] || new Board(this.boardID); + this.ID = this.threadID = +(root.dataset.id || root.id).match(/\d*$/)[0]; + this.thread = this.board.threads.get(this.ID) || new Thread(this.ID, this.board); + } + + return CatalogThreadNative; + + })(); + + return CatalogThreadNative; + +}).call(this); + Connection = (function() { var Connection, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; @@ -4861,15 +6129,15 @@ Connection = (function() { }; Connection.prototype.onMessage = function(e) { - var base, data, type, value; + var data, type, value; if (!(e.source === this.targetWindow() && e.origin === this.origin && typeof e.data === 'string' && e.data.slice(0, g.NAMESPACE.length) === g.NAMESPACE)) { return; } data = JSON.parse(e.data.slice(g.NAMESPACE.length)); for (type in data) { value = data[type]; - if (typeof (base = this.cb)[type] === "function") { - base[type](value); + if ($.hasOwn(this.cb, type)) { + this.cb[type](value); } } }; @@ -4887,13 +6155,13 @@ DataBoard = (function() { bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; DataBoard = (function() { - DataBoard.keys = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads', 'customTitles']; + DataBoard.keys = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads', 'watcherLastModified', 'customTitles']; - function DataBoard(key, sync, dontClean) { + function DataBoard(key1, sync, dontClean) { var init; - this.key = key; + this.key = key1; this.onSync = bind(this.onSync, this); - this.data = Conf[this.key]; + this.initData(Conf[this.key]); $.sync(this.key, this.onSync); if (!dontClean) { this.clean(); @@ -4910,71 +6178,208 @@ DataBoard = (function() { $.on(d, '4chanXInitFinished', init); } - DataBoard.prototype.save = function(cb) { - return $.set(this.key, this.data, cb); + DataBoard.prototype.initData = function(data1) { + var base, boards, lastChecked, name, ref; + this.data = data1; + if (this.data.boards) { + ref = this.data, boards = ref.boards, lastChecked = ref.lastChecked; + this.data['4chan.org'] = { + boards: boards, + lastChecked: lastChecked + }; + delete this.data.boards; + delete this.data.lastChecked; + } + return (base = this.data)[name = g.SITE.ID] || (base[name] = { + boards: $.dict() + }); }; - DataBoard.prototype["delete"] = function(arg) { - var boardID, postID, ref, threadID; - boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID; - $.forceSync(this.key); - if (postID) { - if (!((ref = this.data.boards[boardID]) != null ? ref[threadID] : void 0)) { - return; - } - delete this.data.boards[boardID][threadID][postID]; - this.deleteIfEmpty({ - boardID: boardID, - threadID: threadID - }); - } else if (threadID) { - if (!this.data.boards[boardID]) { - return; - } - delete this.data.boards[boardID][threadID]; - this.deleteIfEmpty({ - boardID: boardID - }); - } else { - delete this.data.boards[boardID]; + DataBoard.prototype.changes = []; + + DataBoard.prototype.save = function(change, cb) { + change(); + this.changes.push(change); + return $.get(this.key, { + boards: $.dict() + }, (function(_this) { + return function(items) { + var i, len, needSync, ref; + if (!_this.changes.length) { + return; + } + needSync = (items[_this.key].version || 0) > (_this.data.version || 0); + if (needSync) { + _this.initData(items[_this.key]); + ref = _this.changes; + for (i = 0, len = ref.length; i < len; i++) { + change = ref[i]; + change(); + } + } + _this.changes = []; + _this.data.version = (_this.data.version || 0) + 1; + return $.set(_this.key, _this.data, function() { + if (needSync) { + if (typeof _this.sync === "function") { + _this.sync(); + } + } + return typeof cb === "function" ? cb() : void 0; + }); + }; + })(this)); + }; + + DataBoard.prototype.forceSync = function(cb) { + return $.get(this.key, { + boards: $.dict() + }, (function(_this) { + return function(items) { + var change, i, len, ref; + if ((items[_this.key].version || 0) > (_this.data.version || 0)) { + _this.initData(items[_this.key]); + ref = _this.changes; + for (i = 0, len = ref.length; i < len; i++) { + change = ref[i]; + change(); + } + if (typeof _this.sync === "function") { + _this.sync(); + } + } + return typeof cb === "function" ? cb() : void 0; + }; + })(this)); + }; + + DataBoard.prototype["delete"] = function(arg, cb) { + var boardID, postID, siteID, threadID; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID; + siteID || (siteID = g.SITE.ID); + if (!this.data[siteID]) { + return; } - return this.save(); + return this.save((function(_this) { + return function() { + var ref; + if (postID) { + if (!((ref = _this.data[siteID].boards[boardID]) != null ? ref[threadID] : void 0)) { + return; + } + delete _this.data[siteID].boards[boardID][threadID][postID]; + return _this.deleteIfEmpty({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }); + } else if (threadID) { + if (!_this.data[siteID].boards[boardID]) { + return; + } + delete _this.data[siteID].boards[boardID][threadID]; + return _this.deleteIfEmpty({ + siteID: siteID, + boardID: boardID + }); + } else { + return delete _this.data[siteID].boards[boardID]; + } + }; + })(this), cb); }; DataBoard.prototype.deleteIfEmpty = function(arg) { - var boardID, threadID; - boardID = arg.boardID, threadID = arg.threadID; - $.forceSync(this.key); + var boardID, siteID, threadID; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID; + if (!this.data[siteID]) { + return; + } if (threadID) { - if (!Object.keys(this.data.boards[boardID][threadID]).length) { - delete this.data.boards[boardID][threadID]; + if (!Object.keys(this.data[siteID].boards[boardID][threadID]).length) { + delete this.data[siteID].boards[boardID][threadID]; return this.deleteIfEmpty({ + siteID: siteID, boardID: boardID }); } - } else if (!Object.keys(this.data.boards[boardID]).length) { - return delete this.data.boards[boardID]; + } else if (!Object.keys(this.data[siteID].boards[boardID]).length) { + return delete this.data[siteID].boards[boardID]; } }; - DataBoard.prototype.set = function(arg, cb) { - var base, base1, base2, boardID, postID, threadID, val; - boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID, val = arg.val; - $.forceSync(this.key); + DataBoard.prototype.set = function(data, cb) { + return this.save((function(_this) { + return function() { + return _this.setUnsafe(data); + }; + })(this), cb); + }; + + DataBoard.prototype.setUnsafe = function(arg) { + var base, base1, base2, base3, boardID, postID, siteID, threadID, val; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID, val = arg.val; + siteID || (siteID = g.SITE.ID); + (base = this.data)[siteID] || (base[siteID] = { + boards: $.dict() + }); if (postID !== void 0) { - ((base = ((base1 = this.data.boards)[boardID] || (base1[boardID] = {})))[threadID] || (base[threadID] = {}))[postID] = val; + return ((base1 = ((base2 = this.data[siteID].boards)[boardID] || (base2[boardID] = $.dict())))[threadID] || (base1[threadID] = $.dict()))[postID] = val; } else if (threadID !== void 0) { - ((base2 = this.data.boards)[boardID] || (base2[boardID] = {}))[threadID] = val; + return ((base3 = this.data[siteID].boards)[boardID] || (base3[boardID] = $.dict()))[threadID] = val; } else { - this.data.boards[boardID] = val; + return this.data[siteID].boards[boardID] = val; + } + }; + + DataBoard.prototype.extend = function(arg, cb) { + var boardID, postID, siteID, threadID, val; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID, val = arg.val; + return this.save((function(_this) { + return function() { + var key, oldVal, subVal; + oldVal = _this.get({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + postID: postID, + defaultValue: $.dict() + }); + for (key in val) { + subVal = val[key]; + if (typeof subVal === 'undefined') { + delete oldVal[key]; + } else { + oldVal[key] = subVal; + } + } + return _this.setUnsafe({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + postID: postID, + val: oldVal + }); + }; + })(this), cb); + }; + + DataBoard.prototype.setLastChecked = function(key) { + if (key == null) { + key = 'lastChecked'; } - return this.save(cb); + return this.save((function(_this) { + return function() { + return _this.data[key] = Date.now(); + }; + })(this)); }; DataBoard.prototype.get = function(arg) { - var ID, board, boardID, defaultValue, i, len, postID, thread, threadID, val; - boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID, defaultValue = arg.defaultValue; - if (board = this.data.boards[boardID]) { + var ID, board, boardID, defaultValue, i, len, postID, ref, siteID, thread, threadID, val; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID, postID = arg.postID, defaultValue = arg.defaultValue; + siteID || (siteID = g.SITE.ID); + if (board = (ref = this.data[siteID]) != null ? ref.boards[boardID] : void 0) { if (threadID == null) { if (postID != null) { for (thread = i = 0, len = board.length; i < len; thread = ++i) { @@ -4994,53 +6399,66 @@ DataBoard = (function() { return val || defaultValue; }; - DataBoard.prototype.forceSync = function() { - return $.forceSync(this.key); - }; - DataBoard.prototype.clean = function() { - var boardID, now, ref, val; - $.forceSync(this.key); - ref = this.data.boards; + var boardID, now, ref, ref1, siteID, val; + siteID = g.SITE.ID; + ref = this.data[siteID].boards; for (boardID in ref) { val = ref[boardID]; this.deleteIfEmpty({ + siteID: siteID, boardID: boardID }); } now = Date.now(); - if ((this.data.lastChecked || 0) < now - 2 * $.HOUR) { - this.data.lastChecked = now; - for (boardID in this.data.boards) { + if (!((now - 2 * $.HOUR < (ref1 = this.data[siteID].lastChecked || 0) && ref1 <= now))) { + this.data[siteID].lastChecked = now; + for (boardID in this.data[siteID].boards) { this.ajaxClean(boardID); } } }; DataBoard.prototype.ajaxClean = function(boardID) { - return $.cache("//a.4cdn.org/" + boardID + "/threads.json", (function(_this) { - return function(e1) { - var ref; - if ((ref = e1.target.status) !== 200 && ref !== 404) { + var base, siteID, that, threadsList; + that = this; + siteID = g.SITE.ID; + threadsList = typeof (base = g.SITE.urls).threadsListJSON === "function" ? base.threadsListJSON({ + siteID: siteID, + boardID: boardID + }) : void 0; + if (!threadsList) { + return; + } + return $.cache(threadsList, function() { + var archiveList, base1, response1; + if (this.status !== 200) { + return; + } + archiveList = typeof (base1 = g.SITE.urls).archiveListJSON === "function" ? base1.archiveListJSON({ + siteID: siteID, + boardID: boardID + }) : void 0; + if (!archiveList) { + return that.ajaxCleanParse(boardID, this.response); + } + response1 = this.response; + return $.cache(archiveList, function() { + if (!(this.status === 200 || (!g.SITE.archivedBoardsKnown && this.status === 404))) { return; } - return $.cache("//a.4cdn.org/" + boardID + "/archive.json", function(e2) { - var ref1; - if ((ref1 = e2.target.status) !== 200 && ref1 !== 404) { - return; - } - return _this.ajaxCleanParse(boardID, e1.target.response, e2.target.response); - }); - }; - })(this)); + return that.ajaxCleanParse(boardID, response1, this.response); + }); + }); }; DataBoard.prototype.ajaxCleanParse = function(boardID, response1, response2) { - var ID, board, i, j, k, len, len1, len2, page, ref, thread, threads; - if (!(board = this.data.boards[boardID])) { + var ID, board, i, j, k, len, len1, len2, page, ref, siteID, thread, threads; + siteID = g.SITE.ID; + if (!(board = this.data[siteID].boards[boardID])) { return; } - threads = {}; + threads = $.dict(); if (response1) { for (i = 0, len = response1.length; i < len; i++) { page = response1[i]; @@ -5062,17 +6480,19 @@ DataBoard = (function() { } } } - this.data.boards[boardID] = threads; + this.data[siteID].boards[boardID] = threads; this.deleteIfEmpty({ + siteID: siteID, boardID: boardID }); - return this.save(); + return $.set(this.key, this.data); }; DataBoard.prototype.onSync = function(data) { - this.data = data || { - boards: {} - }; + if (!((data.version || 0) > (this.data.version || 0))) { + return; + } + this.initData(data); return typeof this.sync === "function" ? this.sync() : void 0; }; @@ -5090,23 +6510,36 @@ Fetcher = (function() { Fetcher = (function() { function Fetcher(boardID1, threadID, postID1, root, quoter) { - var post; + var board, post, ref, that, thread; this.boardID = boardID1; this.threadID = threadID; this.postID = postID1; this.root = root; this.quoter = quoter; - if (post = g.posts[this.boardID + "." + this.postID]) { + if (post = g.posts.get(this.boardID + "." + this.postID)) { + this.insert(post); + return; + } + if ((post = (ref = Index.replyData) != null ? ref[this.boardID + "." + this.postID] : void 0) && (thread = g.threads.get(this.boardID + "." + this.threadID))) { + board = g.boards[this.boardID]; + post = new Post(g.SITE.Build.postFromObject(post, this.boardID), thread, board, { + isFetchedQuote: true + }); + Main.callbackNodes('Post', [post]); this.insert(post); return; } this.root.textContent = "Loading post No." + this.postID + "..."; if (this.threadID) { - $.cache("//a.4cdn.org/" + this.boardID + "/thread/" + this.threadID + ".json", (function(_this) { - return function(e, isCached) { - return _this.fetchedPost(e.target, isCached); - }; - })(this)); + that = this; + $.cache(g.SITE.urls.threadJSON({ + boardID: this.boardID, + threadID: this.threadID + }), function(arg) { + var isCached; + isCached = arg.isCached; + return that.fetchedPost(this, isCached); + }); } else { this.archivedPost(); } @@ -5117,6 +6550,7 @@ Fetcher = (function() { if (!this.root.parentNode) { return; } + this.quoter || (this.quoter = post); clone = post.addClone(this.quoter.context, $.hasClass(this.root, 'dialog')); Main.callbackNodes('Post', [clone]); nodes = clone.nodes; @@ -5140,26 +6574,26 @@ Fetcher = (function() { } $.rmAll(this.root); $.add(this.root, nodes.root); - return $.event('PostsInserted'); + return $.event('PostsInserted', null, this.root); }; Fetcher.prototype.fetchedPost = function(req, isCached) { - var api, board, k, len, post, posts, status, thread; - if (post = g.posts[this.boardID + "." + this.postID]) { + var api, board, k, len, post, posts, status, that, thread; + if (post = g.posts.get(this.boardID + "." + this.postID)) { this.insert(post); return; } status = req.status; if (status !== 200) { - if (this.archivedPost()) { + if (status && this.archivedPost()) { return; } $.addClass(this.root, 'warning'); - this.root.textContent = status === 404 ? "Thread No." + this.threadID + " 404'd." : "Error " + req.statusText + " (" + req.status + ")."; + this.root.textContent = status === 404 ? "Thread No." + this.threadID + " 404'd." : !status ? 'Connection Error' : "Error " + req.statusText + " (" + req.status + ")."; return; } posts = req.response.posts; - Build.spoilerRange[this.boardID] = posts[0].custom_spoiler; + g.SITE.Build.spoilerRange[this.boardID] = posts[0].custom_spoiler; for (k = 0, len = posts.length; k < len; k++) { post = posts[k]; if (post.no === this.postID) { @@ -5168,15 +6602,17 @@ Fetcher = (function() { } if (post.no !== this.postID) { if (isCached) { - api = "//a.4cdn.org/" + this.boardID + "/thread/" + this.threadID + ".json"; + api = g.SITE.urls.threadJSON({ + boardID: this.boardID, + threadID: this.threadID + }); $.cleanCache(function(url) { return url === api; }); - $.cache(api, (function(_this) { - return function(e) { - return _this.fetchedPost(e.target, false); - }; - })(this)); + that = this; + $.cache(api, function() { + return that.fetchedPost(this, false); + }); return; } if (this.archivedPost()) { @@ -5187,15 +6623,16 @@ Fetcher = (function() { return; } board = g.boards[this.boardID] || new Board(this.boardID); - thread = g.threads[this.boardID + "." + this.threadID] || new Thread(this.threadID, board); - post = new Post(Build.postFromObject(post, this.boardID), thread, board); - post.isFetchedQuote = true; + thread = g.threads.get(this.boardID + "." + this.threadID) || new Thread(this.threadID, board); + post = new Post(g.SITE.Build.postFromObject(post, this.boardID), thread, board, { + isFetchedQuote: true + }); Main.callbackNodes('Post', [post]); return this.insert(post); }; Fetcher.prototype.archivedPost = function() { - var archive, url; + var archive, encryptionOK, that, url; if (!Conf['Resurrect Quotes']) { return false; } @@ -5206,41 +6643,31 @@ Fetcher = (function() { return false; } archive = Redirect.data.post[this.boardID]; - if (/^https:\/\//.test(url) || location.protocol === 'http:') { - $.cache(url, (function(_this) { - return function(e) { - return _this.parseArchivedPost(e.target.response, url, archive); - }; - })(this), { - responseType: 'json', - withCredentials: archive.withCredentials - }); - return true; - } else if (Conf['Exempt Archives from Encryption']) { - CrossOrigin.json(url, (function(_this) { - return function(response) { - var key, media, ref; - media = response.media; - if (media) { - for (key in media) { - if (/_link$/.test(key)) { - if (!((ref = media[key]) != null ? ref.match(/^http:\/\//) : void 0)) { - delete media[key]; - } + encryptionOK = /^https:\/\//.test(url) || location.protocol === 'http:'; + if (encryptionOK || Conf['Exempt Archives from Encryption']) { + that = this; + CrossOrigin.cache(url, function() { + var key, media, ref, ref1; + if (!encryptionOK && ((ref = this.response) != null ? ref.media : void 0)) { + media = this.response.media; + for (key in media) { + if (/_link$/.test(key)) { + if (!((ref1 = $.getOwn(media, key)) != null ? ref1.match(/^http:\/\//) : void 0)) { + delete media[key]; } } } - return _this.parseArchivedPost(response, url, archive); - }; - })(this)); + } + return that.parseArchivedPost(this.response, url, archive); + }); return true; } return false; }; Fetcher.prototype.parseArchivedPost = function(data, url, archive) { - var board, comment, greentext, i, j, key, o, post, ref, ref1, tag, text, text2, thread, val; - if (post = g.posts[this.boardID + "." + this.postID]) { + var board, comment, greentext, i, j, media_link, o, post, ref, tag, text, text2, thread, thumb_link; + if (post = g.posts.get(this.boardID + "." + this.postID)) { this.insert(post); return; } @@ -5276,26 +6703,20 @@ Fetcher = (function() { results1 = []; for (j = l = 0, len1 = ref.length; l < len1; j = ++l) { text2 = ref[j]; - results1.push({ - innerHTML: ((j % 2) ? "" + E(text2) + "" : E(text2)) - }); + results1.push({innerHTML: ((j % 2) ? "" + E(text2) + "" : E(text2))}); } return results1; })(); - text = { - innerHTML: ((greentext) ? "" + E.cat(text) + "" : E.cat(text)) - }; + text = {innerHTML: ((greentext) ? "" + E.cat(text) + "" : E.cat(text))}; results.push(text); } } return results; }).call(this); - comment = { - innerHTML: E.cat(comment) - }; + comment = {innerHTML: E.cat(comment)}; this.threadID = +data.thread_num; o = { - postID: this.postID, + ID: this.postID, threadID: this.threadID, boardID: this.boardID, isReply: this.postID !== this.threadID @@ -5313,11 +6734,18 @@ Fetcher = (function() { return 'Admin'; case 'D': return 'Developer'; + case 'V': + return 'Verified'; + case 'F': + return 'Founder'; + case 'G': + return 'Manager'; } })(), uniqueID: data.poster_hash, flagCode: data.poster_country, - flag: data.poster_country_name, + flagCodeTroll: data.troll_country_code, + flag: data.poster_country_name || data.troll_country_name, dateUTC: data.timestamp, dateText: data.fourchan_date, commentHTML: comment @@ -5325,22 +6753,31 @@ Fetcher = (function() { if (o.info.capcode) { delete o.info.uniqueID; } - if ((ref = data.media) != null ? ref.media_filename : void 0) { - ref1 = data.media; - for (key in ref1) { - val = ref1[key]; - if (/_link$/.test(key) && (val != null ? val[0] : void 0) === '/') { - data.media[key] = url.split('/', 3).join('/') + val; - } + if (data.media && !!+data.media.banned) { + o.fileDeleted = true; + } else if ((ref = data.media) != null ? ref.media_filename : void 0) { + thumb_link = data.media.thumb_link; + if ((thumb_link != null ? thumb_link[0] : void 0) === '/') { + thumb_link = url.split('/', 3).join('/') + thumb_link; + } + if (!Redirect.securityCheck(thumb_link)) { + thumb_link = ''; + } + media_link = Redirect.to('file', { + boardID: this.boardID, + filename: data.media.media_orig + }); + if (!Redirect.securityCheck(media_link)) { + media_link = ''; } o.file = { name: data.media.media_filename, - url: data.media.media_link || data.media.remote_media_link || (location.protocol + "//i.4cdn.org/" + this.boardID + "/" + (encodeURIComponent(data.media[this.boardID === 'f' ? 'media_filename' : 'media_orig']))), + url: media_link || (this.boardID === 'f' ? location.protocol + "//" + (ImageHost.flashHost()) + "/" + this.boardID + "/" + (encodeURIComponent(E(data.media.media_filename))) : location.protocol + "//" + (ImageHost.host()) + "/" + this.boardID + "/" + data.media.media_orig), height: data.media.media_h, width: data.media.media_w, MD5: data.media.media_hash, size: $.bytesToString(data.media.media_size), - thumbURL: data.media.thumb_link || (location.protocol + "//i.4cdn.org/" + this.boardID + "/" + data.media.preview_orig), + thumbURL: thumb_link || (location.protocol + "//" + (ImageHost.thumbHost()) + "/" + this.boardID + "/" + data.media.preview_orig), theight: data.media.preview_h, twidth: data.media.preview_w, isSpoiler: data.media.spoiler === '1' @@ -5352,84 +6789,44 @@ Fetcher = (function() { o.file.tag = JSON.parse(data.media.exif).Tag; } } + o.extra = $.dict(); board = g.boards[this.boardID] || new Board(this.boardID); - thread = g.threads[this.boardID + "." + this.threadID] || new Thread(this.threadID, board); - post = new Post(Build.post(o), thread, board); + thread = g.threads.get(this.boardID + "." + this.threadID) || new Thread(this.threadID, board); + post = new Post(g.SITE.Build.post(o), thread, board, { + isFetchedQuote: true + }); post.kill(); if (post.file) { post.file.thumbURL = o.file.thumbURL; } - post.isFetchedQuote = true; Main.callbackNodes('Post', [post]); return this.insert(post); }; Fetcher.prototype.archiveTags = { - '\n': { - innerHTML: "
          " - }, - '[b]': { - innerHTML: "" - }, - '[/b]': { - innerHTML: "" - }, - '[spoiler]': { - innerHTML: "" - }, - '[/spoiler]': { - innerHTML: "" - }, - '[code]': { - innerHTML: "
          "
          -      },
          -      '[/code]': {
          -        innerHTML: "
          " - }, - '[moot]': { - innerHTML: "
          " - }, - '[/moot]': { - innerHTML: "
          " - }, - '[banned]': { - innerHTML: "" - }, - '[/banned]': { - innerHTML: "" - }, + '\n': {innerHTML: "
          "}, + '[b]': {innerHTML: ""}, + '[/b]': {innerHTML: ""}, + '[spoiler]': {innerHTML: ""}, + '[/spoiler]': {innerHTML: ""}, + '[code]': {innerHTML: "
          "},
          +      '[/code]': {innerHTML: "
          "}, + '[moot]': {innerHTML: "
          "}, + '[/moot]': {innerHTML: "
          "}, + '[banned]': {innerHTML: ""}, + '[/banned]': {innerHTML: ""}, '[fortune]': function(text) { - return { - innerHTML: "" - }; - }, - '[/fortune]': { - innerHTML: "" - }, - '[i]': { - innerHTML: "" - }, - '[/i]': { - innerHTML: "" + return {innerHTML: ""}; }, - '[red]': { - innerHTML: "" - }, - '[/red]': { - innerHTML: "" - }, - '[green]': { - innerHTML: "" - }, - '[/green]': { - innerHTML: "" - }, - '[blue]': { - innerHTML: "" - }, - '[/blue]': { - innerHTML: "" - } + '[/fortune]': {innerHTML: ""}, + '[i]': {innerHTML: ""}, + '[/i]': {innerHTML: ""}, + '[red]': {innerHTML: ""}, + '[/red]': {innerHTML: ""}, + '[green]': {innerHTML: ""}, + '[/green]': {innerHTML: ""}, + '[blue]': {innerHTML: ""}, + '[/blue]': {innerHTML: ""} }; return Fetcher; @@ -5450,9 +6847,7 @@ Notice = (function() { this.onclose = onclose; this.close = bind(this.close, this); this.add = bind(this.add, this); - this.el = $.el('div', { - innerHTML: "
          " - }); + this.el = $.el('div', {innerHTML: "
          "}); this.el.style.opacity = 0; this.setType(type); $.on(this.el.firstElementChild, 'click', this.close); @@ -5508,121 +6903,152 @@ Post = (function() { return this.ID; }; - function Post(root, thread, board) { - var clone, j, len, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, ref8; + function Post(root, thread, board, flags) { + var clone, j, k, key, len, len1, ref, ref1, ref10, ref11, ref12, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, selector; this.thread = thread; this.board = board; - this.ID = +root.id.slice(2); + if (flags == null) { + flags = {}; + } + $.extend(this, flags); + this.ID = +root.id.match(/\d*$/)[0]; + this.postID = this.ID; + this.threadID = this.thread.ID; + this.boardID = this.board.ID; + this.siteID = g.SITE.ID; this.fullID = this.board + "." + this.ID; this.context = this; + this.isReply = this.ID !== this.threadID; root.dataset.fullID = this.fullID; this.nodes = this.parseNodes(root); - if (!(this.isReply = $.hasClass(this.nodes.post, 'reply'))) { + if (!this.isReply) { this.thread.OP = this; - this.thread.isArchived = !!$('.archivedIcon', this.nodes.info); - this.thread.isSticky = !!$('.stickyIcon', this.nodes.info); - this.thread.isClosed = this.thread.isArchived || !!$('.closedIcon', this.nodes.info); + ref = ['isSticky', 'isClosed', 'isArchived']; + for (j = 0, len = ref.length; j < len; j++) { + key = ref[j]; + if ((selector = g.SITE.selectors.icons[key])) { + this.thread[key] = !!$(selector, this.nodes.info); + } + } if (this.thread.isArchived) { + this.thread.isClosed = true; this.thread.kill(); } } this.info = { - nameBlock: Conf['Anonymize'] ? 'Anonymous' : this.nodes.nameBlock.textContent.trim(), - subject: ((ref = this.nodes.subject) != null ? ref.textContent : void 0) || void 0, - name: (ref1 = this.nodes.name) != null ? ref1.textContent : void 0, - tripcode: (ref2 = this.nodes.tripcode) != null ? ref2.textContent : void 0, - uniqueID: (ref3 = this.nodes.uniqueID) != null ? ref3.firstElementChild.textContent : void 0, - capcode: (ref4 = this.nodes.capcode) != null ? ref4.textContent.replace('## ', '') : void 0, - flagCode: (ref5 = this.nodes.flag) != null ? (ref6 = ref5.className.match(/flag-(\w+)/)) != null ? ref6[1].toUpperCase() : void 0 : void 0, - flag: (ref7 = this.nodes.flag) != null ? ref7.title : void 0, - date: this.nodes.date ? new Date(this.nodes.date.dataset.utc * 1000) : void 0 + subject: ((ref1 = this.nodes.subject) != null ? ref1.textContent : void 0) || void 0, + name: (ref2 = this.nodes.name) != null ? ref2.textContent : void 0, + email: this.nodes.email ? decodeURIComponent(this.nodes.email.href.replace(/^mailto:/, '')) : void 0, + tripcode: (ref3 = this.nodes.tripcode) != null ? ref3.textContent : void 0, + uniqueID: (ref4 = this.nodes.uniqueID) != null ? ref4.textContent : void 0, + capcode: (ref5 = this.nodes.capcode) != null ? ref5.textContent.replace('## ', '') : void 0, + pass: (ref6 = this.nodes.pass) != null ? ref6.title.match(/\d*$/)[0] : void 0, + flagCode: (ref7 = this.nodes.flag) != null ? (ref8 = ref7.className.match(/flag-(\w+)/)) != null ? ref8[1].toUpperCase() : void 0 : void 0, + flagCodeTroll: (ref9 = this.nodes.flag) != null ? (ref10 = ref9.className.match(/bfl-(\w+)/)) != null ? ref10[1].toUpperCase() : void 0 : void 0, + flag: (ref11 = this.nodes.flag) != null ? ref11.title : void 0, + date: this.nodes.date ? g.SITE.parseDate(this.nodes.date) : void 0 }; + if (Conf['Anonymize']) { + this.info.nameBlock = 'Anonymous'; + } else { + this.info.nameBlock = ((this.info.name || '') + " " + (this.info.tripcode || '')).trim(); + } + if (this.info.capcode) { + this.info.nameBlock += " ## " + this.info.capcode; + } + if (this.info.uniqueID) { + this.info.nameBlock += " (ID: " + this.info.uniqueID + ")"; + } this.parseComment(); this.parseQuotes(); - this.parseFile(); + this.parseFiles(); this.isDead = false; this.isHidden = false; this.clones = []; - if (g.posts[this.fullID]) { + if (g.posts.get(this.fullID)) { this.isRebuilt = true; - this.clones = g.posts[this.fullID].clones; - ref8 = this.clones; - for (j = 0, len = ref8.length; j < len; j++) { - clone = ref8[j]; + this.clones = g.posts.get(this.fullID).clones; + ref12 = this.clones; + for (k = 0, len1 = ref12.length; k < len1; k++) { + clone = ref12[k]; clone.origin = this; } } + if (!this.isFetchedQuote && this.ID > this.thread.lastPost) { + this.thread.lastPost = this.ID; + } this.board.posts.push(this.ID, this); this.thread.posts.push(this.ID, this); g.posts.push(this.fullID, this); } Post.prototype.parseNodes = function(root) { - var info, nodes, post; - post = $('.post', root); - info = $('.postInfo', post); + var base, info, key, nodes, post, ref, s, selector; + s = g.SITE.selectors; + post = $(s.post, root) || root; + info = $(s.infoRoot, post); nodes = { root: root, + bottom: this.isReply || !g.SITE.isOPContainerThread ? root : $(s.opBottom, root), post: post, info: info, - subject: $('.subject', info), - name: $('.name', info), - email: $('.useremail', info), - tripcode: $('.postertrip', info), - uniqueID: $('.posteruid', info), - capcode: $('.capcode.hand', info), - flag: $('.flag, .countryFlag', info), - date: $('.dateTime', info), - nameBlock: $('.nameBlock', info), - quote: $('.postNum > a:nth-of-type(2)', info), - reply: $('.replylink', info), - comment: $('.postMessage', post), - links: [], + comment: $(s.comment, post), quotelinks: [], - archivelinks: [] + archivelinks: [], + embedlinks: [] }; + ref = s.info; + for (key in ref) { + selector = ref[key]; + nodes[key] = $(selector, info); + } + if (typeof (base = g.SITE).parseNodes === "function") { + base.parseNodes(this, nodes); + } + nodes.uniqueIDRoot || (nodes.uniqueIDRoot = nodes.uniqueID); if ($.engine === 'edge') { Object.defineProperty(nodes, 'backlinks', { configurable: true, enumerable: true, get: function() { - return info.getElementsByClassName('backlink'); + return post.getElementsByClassName('backlink'); } }); } else { - nodes.backlinks = info.getElementsByClassName('backlink'); + nodes.backlinks = post.getElementsByClassName('backlink'); } return nodes; }; Post.prototype.parseComment = function() { - var abbr, bq, commentDisplay, j, k, len, len1, node, ref, spoilers; + var base, bq; this.nodes.comment.normalize(); - bq = this.nodes.comment.cloneNode(true); - ref = $$('.abbr + br, .exif, b, .fortune', bq); - for (j = 0, len = ref.length; j < len; j++) { - node = ref[j]; - $.rm(node); + this.nodes.commentClean = bq = this.nodes.comment.cloneNode(true); + if (typeof (base = g.SITE).cleanComment === "function") { + base.cleanComment(bq); } - if (abbr = $('.abbr', bq)) { - $.rm(abbr); + return this.info.comment = this.nodesToText(bq); + }; + + Post.prototype.commentDisplay = function() { + var base, bq; + bq = this.nodes.commentClean.cloneNode(true); + if (!(Conf['Remove Spoilers'] || Conf['Reveal Spoilers'])) { + this.cleanSpoilers(bq); } - this.info.comment = this.nodesToText(bq); - if (abbr) { - this.info.comment = this.info.comment.replace(/\n\n$/, ''); + if (typeof (base = g.SITE).cleanCommentDisplay === "function") { + base.cleanCommentDisplay(bq); } - commentDisplay = this.info.comment; - if (!(Conf['Remove Spoilers'] || Conf['Reveal Spoilers'])) { - spoilers = $$('s', bq); - if (spoilers.length) { - for (k = 0, len1 = spoilers.length; k < len1; k++) { - node = spoilers[k]; - $.replace(node, $.tn('[spoiler]')); - } - commentDisplay = this.nodesToText(bq); - } + return this.nodesToText(bq).trim().replace(/\s+$/gm, ''); + }; + + Post.prototype.commentOrig = function() { + var base, bq; + bq = this.nodes.commentClean.cloneNode(true); + if (typeof (base = g.SITE).insertTags === "function") { + base.insertTags(bq); } - return this.info.commentDisplay = commentDisplay.trim().replace(/\s+$/gm, ''); + return this.nodesToText(bq); }; Post.prototype.nodesToText = function(bq) { @@ -5636,10 +7062,19 @@ Post = (function() { return text; }; + Post.prototype.cleanSpoilers = function(bq) { + var j, len, node, spoilers; + spoilers = $$(g.SITE.selectors.spoiler, bq); + for (j = 0, len = spoilers.length; j < len; j++) { + node = spoilers[j]; + $.replace(node, $.tn('[spoiler]')); + } + }; + Post.prototype.parseQuotes = function() { var j, len, quotelink, ref; this.quotes = []; - ref = $$(':not(pre) > .quotelink', this.nodes.comment); + ref = $$(g.SITE.selectors.quotelink, this.nodes.comment); for (j = 0, len = ref.length; j < len; j++) { quotelink = ref[j]; this.parseQuote(quotelink); @@ -5648,7 +7083,7 @@ Post = (function() { Post.prototype.parseQuote = function(quotelink) { var fullID, match; - match = quotelink.href.match(/^https?:\/\/boards\.4chan\.org\/+([^\/]+)\/+(?:res|thread)\/+\d+(?:[\/?][^#]*)?#p(\d+)$/); + match = quotelink.href.match(g.SITE.regexp.quotelink); if (!(match || (this.isClone && quotelink.dataset.postID))) { return; } @@ -5656,58 +7091,85 @@ Post = (function() { if (this.isClone) { return; } - fullID = match[1] + "." + match[2]; + fullID = match[1] + "." + match[3]; if (indexOf.call(this.quotes, fullID) < 0) { return this.quotes.push(fullID); } }; - Post.prototype.parseFile = function() { - var fileEl, fileText, info, link, m, ref, ref1, ref2, size, thumb, unit; - if (!(fileEl = $('.file', this.nodes.post))) { - return; + Post.prototype.parseFiles = function() { + var docIndex, file, fileRoot, fileRoots, index, j, len; + this.files = []; + fileRoots = this.fileRoots(); + index = 0; + for (docIndex = j = 0, len = fileRoots.length; j < len; docIndex = ++j) { + fileRoot = fileRoots[docIndex]; + if ((file = this.parseFile(fileRoot))) { + file.index = index++; + file.docIndex = docIndex; + this.files.push(file); + } + } + if (this.files.length) { + return this.file = this.files[0]; + } + }; + + Post.prototype.fileRoots = function() { + var roots; + if (g.SITE.selectors.multifile) { + roots = $$(g.SITE.selectors.multifile, this.nodes.root); + if (roots.length) { + return roots; + } + } + return [this.nodes.root]; + }; + + Post.prototype.parseFile = function(fileRoot) { + var file, key, ref, ref1, selector, size, unit; + file = {}; + ref = g.SITE.selectors.file; + for (key in ref) { + selector = ref[key]; + file[key] = $(selector, fileRoot); } - if (!(link = $('.fileText > a, .fileText-original > a', fileEl))) { + file.thumbLink = (ref1 = file.thumb) != null ? ref1.parentNode : void 0; + if (!(file.text && file.link)) { return; } - if (!(info = (ref = link.nextSibling) != null ? ref.textContent.match(/\(([\d.]+ [KMG]?B).*\)/) : void 0)) { + if (!g.SITE.parseFile(this, file)) { return; } - fileText = fileEl.firstElementChild; - this.file = { - text: fileText, - link: link, - url: link.href, - name: fileText.title || link.title || link.textContent, - size: info[1], - isImage: /(jpg|png|gif)$/i.test(link.href), - isVideo: /webm$/i.test(link.href), - dimensions: (ref1 = info[0].match(/\d+x\d+/)) != null ? ref1[0] : void 0, - tag: (ref2 = info[0].match(/,[^,]*, ([a-z]+)\)/i)) != null ? ref2[1] : void 0 - }; - size = +this.file.size.match(/[\d.]+/)[0]; - unit = ['B', 'KB', 'MB', 'GB'].indexOf(this.file.size.match(/\w+$/)[0]); + $.extend(file, { + url: file.link.href, + isImage: $.isImage(file.link.href), + isVideo: $.isVideo(file.link.href) + }); + size = +file.size.match(/[\d.]+/)[0]; + unit = ['B', 'KB', 'MB', 'GB'].indexOf(file.size.match(/\w+$/)[0]); while (unit-- > 0) { size *= 1024; } - this.file.sizeInBytes = size; - if ((thumb = $('.fileThumb > [data-md5]', fileEl))) { - return $.extend(this.file, { - thumb: thumb, - thumbURL: (m = link.href.match(/\d+(?=\.\w+$)/)) ? location.protocol + "//i.4cdn.org/" + this.board + "/" + m[0] + "s.jpg" : void 0, - MD5: thumb.dataset.md5, - isSpoiler: $.hasClass(thumb.parentNode, 'imgspoiler') - }); - } + file.sizeInBytes = size; + return file; }; - Post.prototype.kill = function(file) { + Post.deadMark = $.el('span', { + textContent: '\u00A0(Dead)', + className: 'qmark-dead' + }); + + Post.prototype.kill = function(file, index) { var clone, j, k, len, len1, quotelink, ref, ref1, strong; + if (index == null) { + index = 0; + } if (file) { - if (this.isDead || this.file.isDead) { + if (this.isDead || this.files[index].isDead) { return; } - this.file.isDead = true; + this.files[index].isDead = true; $.addClass(this.nodes.root, 'deleted-file'); } else { if (this.isDead) { @@ -5730,7 +7192,7 @@ Post = (function() { ref = this.clones; for (j = 0, len = ref.length; j < len; j++) { clone = ref[j]; - clone.kill(file); + clone.kill(file, index); } if (file) { return; @@ -5741,7 +7203,7 @@ Post = (function() { if (!(!$.hasClass(quotelink, 'deadlink'))) { continue; } - quotelink.textContent = quotelink.textContent + '\u00A0(Dead)'; + $.add(quotelink, Post.deadMark.cloneNode(true)); $.addClass(quotelink, 'deadlink'); } }; @@ -5751,7 +7213,10 @@ Post = (function() { this.isDead = false; $.rmClass(this.nodes.root, 'deleted-post'); strong = $('strong.warning', this.nodes.info); - if (this.file && this.file.isDead) { + if (this.files.some(function(file) { + return file.isDead; + })) { + $.addClass(this.nodes.root, 'deleted-file'); strong.textContent = '[File deleted]'; } else { $.rm(strong); @@ -5770,7 +7235,7 @@ Post = (function() { if (!($.hasClass(quotelink, 'deadlink'))) { continue; } - quotelink.textContent = quotelink.textContent.replace('\u00A0(Dead)', ''); + $.rm($('.qmark-dead', quotelink)); $.rmClass(quotelink, 'deadlink'); } }; @@ -5782,6 +7247,7 @@ Post = (function() { }; Post.prototype.addClone = function(context, contractThumb) { + Callbacks.Post.execute(this); return new Post.Clone(this, context, contractThumb); }; @@ -5795,6 +7261,14 @@ Post = (function() { } }; + Post.prototype.setCatalogOP = function(isCatalogOP) { + this.nodes.root.classList.toggle('catalog-container', isCatalogOP); + this.nodes.root.classList.toggle('opContainer', !isCatalogOP); + this.nodes.post.classList.toggle('catalog-post', isCatalogOP); + this.nodes.post.classList.toggle('op', !isCatalogOP); + return this.nodes.post.style.left = this.nodes.post.style.right = null; + }; + return Post; })(); @@ -5813,60 +7287,83 @@ Post = (function() { _Class.prototype.isClone = true; - function _Class(origin, context, contractThumb) { - var base, file, i, inline, inlined, j, k, key, l, len, len1, len2, len3, node, nodes, ref, ref1, ref2, ref3, ref4, ref5, root, val; + function _Class() { + var that; + that = Object.create(Post.Clone.prototype); + that.construct.apply(that, arguments); + return that; + } + + _Class.prototype.construct = function(origin, context, contractThumb) { + var base, file, fileRoot, fileRoots, i, inline, inlined, j, k, key, l, len, len1, len2, len3, len4, m, node, nodes, originFile, ref, ref1, ref2, ref3, ref4, ref5, ref6, root, selector, val; this.origin = origin; this.context = context; - ref = ['ID', 'fullID', 'board', 'thread', 'info', 'quotes', 'isReply']; + ref = ['ID', 'postID', 'threadID', 'boardID', 'siteID', 'fullID', 'board', 'thread', 'info', 'quotes', 'isReply']; for (i = 0, len = ref.length; i < len; i++) { key = ref[i]; this[key] = this.origin[key]; } nodes = this.origin.nodes; root = contractThumb ? this.cloneWithoutVideo(nodes.root) : nodes.root.cloneNode(true); - (base = Post.Clone).prefix || (base.prefix = 0); + (base = Post.Clone).suffix || (base.suffix = 0); ref1 = [root].concat(slice.call($$('[id]', root))); for (j = 0, len1 = ref1.length; j < len1; j++) { node = ref1[j]; - node.id = Post.Clone.prefix + node.id; + node.id += "_" + Post.Clone.suffix; } - Post.Clone.prefix++; - this.nodes = this.parseNodes(root); - ref2 = $$('.inline', this.nodes.post); + Post.Clone.suffix++; + ref2 = $$('.inline', root); for (k = 0, len2 = ref2.length; k < len2; k++) { inline = ref2[k]; $.rm(inline); } - ref3 = $$('.inlined', this.nodes.post); + ref3 = $$('.inlined', root); for (l = 0, len3 = ref3.length; l < len3; l++) { inlined = ref3[l]; $.rmClass(inlined, 'inlined'); } + this.nodes = this.parseNodes(root); root.hidden = false; $.rmClass(root, 'forwarded'); $.rmClass(this.nodes.post, 'highlight'); + if (!this.isReply) { + this.setCatalogOP(false); + $.rm($('.catalog-link', this.nodes.post)); + $.rm($('.catalog-stats', this.nodes.post)); + $.rm($('.catalog-replies', this.nodes.post)); + } this.parseQuotes(); this.quotes = slice.call(this.origin.quotes); - if (this.origin.file) { - this.file = {}; - ref4 = this.origin.file; - for (key in ref4) { - val = ref4[key]; - this.file[key] = val; - } - file = $('.file', this.nodes.post); - this.file.text = file.firstElementChild; - this.file.link = $('.fileText > a, .fileText-original', file); - this.file.thumb = $('.fileThumb > [data-md5]', file); - this.file.fullImage = $('.full-image', file); - this.file.videoControls = $('.video-controls', this.file.text); - if (this.file.videoThumb) { - this.file.thumb.muted = true; - } - if ((ref5 = this.file.thumb) != null ? ref5.dataset.src : void 0) { - this.file.thumb.src = this.file.thumb.dataset.src; - this.file.thumb.removeAttribute('data-src'); - } + this.files = []; + if (this.origin.files.length) { + fileRoots = this.fileRoots(); + } + ref4 = this.origin.files; + for (m = 0, len4 = ref4.length; m < len4; m++) { + originFile = ref4[m]; + file = {}; + for (key in originFile) { + val = originFile[key]; + file[key] = val; + } + fileRoot = fileRoots[file.docIndex]; + ref5 = g.SITE.selectors.file; + for (key in ref5) { + selector = ref5[key]; + file[key] = $(selector, fileRoot); + } + file.thumbLink = (ref6 = file.thumb) != null ? ref6.parentNode : void 0; + if (file.thumbLink) { + file.fullImage = $('.full-image', file.thumbLink); + } + file.videoControls = $('.video-controls', file.text); + if (file.videoThumb) { + file.thumb.muted = true; + } + this.files.push(file); + } + if (this.files.length) { + this.file = this.files[0]; if (this.file.thumb && contractThumb) { ImageExpand.contract(this); } @@ -5874,8 +7371,8 @@ Post = (function() { if (this.origin.isDead) { this.isDead = true; } - root.dataset.clone = this.origin.clones.push(this) - 1; - } + return root.dataset.clone = this.origin.clones.push(this) - 1; + }; _Class.prototype.cloneWithoutVideo = function(node) { var child, clone, i, len, ref; @@ -6034,6 +7531,47 @@ RandomAccessList = (function() { }).call(this); +ShimSet = (function() { + var ShimSet; + + ShimSet = (function() { + function ShimSet() { + this.elements = $.dict(); + this.size = 0; + } + + ShimSet.prototype.has = function(value) { + return value in this.elements; + }; + + ShimSet.prototype.add = function(value) { + if (this.elements[value]) { + return; + } + this.elements[value] = true; + return this.size++; + }; + + ShimSet.prototype["delete"] = function(value) { + if (!this.elements[value]) { + return; + } + delete this.elements[value]; + return this.size--; + }; + + return ShimSet; + + })(); + + if (!('Set' in window)) { + window.Set = ShimSet; + } + + return ShimSet; + +}).call(this); + SimpleDict = (function() { var SimpleDict, slice = [].slice; @@ -6069,6 +7607,14 @@ SimpleDict = (function() { } }; + SimpleDict.prototype.get = function(key) { + if (key === 'keys') { + return void 0; + } else { + return $.getOwn(this, key); + } + }; + return SimpleDict; })(); @@ -6086,21 +7632,28 @@ Thread = (function() { }; function Thread(ID, board) { - this.ID = ID; this.board = board; + this.ID = +ID; + this.threadID = this.ID; + this.boardID = this.board.ID; + this.siteID = g.SITE.ID; this.fullID = this.board + "." + this.ID; this.posts = new SimpleDict(); this.isDead = false; this.isHidden = false; - this.isOnTop = false; this.isSticky = false; this.isClosed = false; this.isArchived = false; this.postLimit = false; this.fileLimit = false; + this.lastPost = 0; this.ipCount = void 0; + this.json = null; this.OP = null; this.catalogView = null; + this.nodes = { + root: null + }; this.board.threads.push(this.ID, this); g.threads.push(this.fullID, this); } @@ -6162,11 +7715,14 @@ Thread = (function() { return; } icon = $.el('img', { - src: "" + Build.staticPath + typeLC + Build.gifIcon, + src: "" + g.SITE.Build.staticPath + typeLC + g.SITE.Build.gifIcon, alt: type, title: type, className: typeLC + "Icon retina" }); + if (g.BOARD.ID === 'f') { + icon.style.cssText = 'height: 18px; width: 18px;'; + } root = type !== 'Sticky' && this.isSticky ? $('.stickyIcon', this.OP.nodes.info) : $('.page-num', this.OP.nodes.info) || this.OP.nodes.quote; $.after(root, [$.tn(' '), icon]); if (!this.catalogView) { @@ -6180,11 +7736,19 @@ Thread = (function() { }; Thread.prototype.collect = function() { + var n; + n = 0; this.posts.forEach(function(post) { - return post.collect(); + if (post.clones.length) { + return n++; + } else { + return post.collect(); + } }); - g.threads.rm(this.fullID); - return this.board.threads.rm(this); + if (!n) { + g.threads.rm(this.fullID); + return this.board.threads.rm(this); + } }; return Thread; @@ -6195,158 +7759,1317 @@ Thread = (function() { }).call(this); -Redirect = (function() { - var Redirect, - indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; +SW = {}; - Redirect = { - archives: [ - { "uid": 3, "name": "4plebs", "domain": "archive.4plebs.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "adv", "f", "hr", "o", "pol", "s4s", "sp", "tg", "trv", "tv", "x" ], "files": [ "adv", "f", "hr", "o", "pol", "s4s", "sp", "tg", "trv", "tv", "x" ] }, - { "uid": 4, "name": "Nyafuu Archive", "domain": "archive.nyafuu.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "c", "e", "news", "w", "wg", "wsr" ], "files": [ "c", "e", "news", "w", "wg", "wsr" ] }, - { "uid": 8, "name": "Rebecca Black Tech", "domain": "archive.rebeccablacktech.com", "http": false, "https": true, "software": "fuuka", "boards": [ "cgl", "g", "mu" ], "files": [ "cgl", "g", "mu" ] }, - { "uid": 10, "name": "warosu", "domain": "warosu.org", "http": false, "https": true, "software": "fuuka", "boards": [ "3", "biz", "cgl", "ck", "diy", "fa", "g", "ic", "jp", "lit", "sci", "tg", "vr" ], "files": [ "3", "biz", "cgl", "ck", "diy", "fa", "g", "ic", "jp", "lit", "sci", "tg", "vr" ] }, - { "uid": 23, "name": "Desustorage", "domain": "desustorage.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "a", "aco", "an", "c", "co", "d", "fit", "gif", "his", "int", "k", "m", "mlp", "qa", "r9k", "tg", "trash", "vr", "wsg" ], "files": [ "a", "aco", "an", "c", "co", "d", "fit", "gif", "his", "int", "k", "m", "mlp", "qa", "r9k", "tg", "trash", "vr", "wsg" ] }, - { "uid": 24, "name": "fireden.net", "domain": "boards.fireden.net", "http": false, "https": true, "software": "foolfuuka", "boards": [ "a", "cm", "ic", "sci", "tg", "v", "vg", "y" ], "files": [ "a", "cm", "ic", "sci", "tg", "v", "vg", "y" ] }, - { "uid": 25, "name": "arch.b4k.co", "domain": "arch.b4k.co", "http": true, "https": true, "software": "foolfuuka", "boards": [ "g", "jp", "mlp", "v" ], "files": [] }, - { "uid": 5, "name": "Love is Over", "domain": "archive.loveisover.me", "http": true, "https": false, "software": "foolfuuka", "boards": [ "c", "d", "e", "i", "lgbt", "t", "u" ], "files": [ "c", "d", "e", "i", "lgbt", "t", "u" ] }, - { "uid": 28, "name": "bstats", "domain": "archive.b-stats.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "f", "cm", "hm", "lgbt", "news", "qst", "trash", "y" ], "files": [] }, - { "uid": 29, "name": "Archived.Moe", "domain": "archived.moe", "http": true, "https": false, "software": "foolfuuka", "boards": [ "3", "a", "aco", "adv", "an", "asp", "b", "biz", "c", "cgl", "ck", "cm", "co", "d", "diy", "e", "f", "fa", "fit", "g", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "int", "jp", "k", "lgbt", "lit", "m", "mlp", "mu", "n", "news", "o", "out", "p", "po", "pol", "qa", "qst", "r", "r9k", "s", "s4s", "sci", "soc", "sp", "t", "tg", "toy", "trash", "trv", "tv", "u", "v", "vg", "vp", "vr", "w", "wg", "wsg", "wsr", "x", "y" ], "files": [ "gd", "po", "qst" ] }, - { "uid": 30, "name": "TheBArchive.com", "domain": "thebarchive.com", "http": true, "https": false, "software": "foolfuuka", "boards": [ "b" ], "files": [ "b" ] } - ], - init: function() { - this.selectArchives(); - if (Conf['archiveAutoUpdate'] && Conf['lastarchivecheck'] < Date.now() - 2 * $.DAY) { - return this.update(); +(function() { + var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + SW.tinyboard = { + isOPContainerThread: true, + mayLackJSON: true, + threadModTimeIgnoresSage: true, + disabledFeatures: ['Resurrect Quotes', 'Quick Reply Personas', 'Quick Reply', 'Cooldown', 'Report Link', 'Delete Link', 'Edit Link', 'Quote Inlining', 'Quote Previewing', 'Quote Backlinks', 'File Info Formatting', 'Image Expansion', 'Image Expansion (Menu)', 'Comment Expansion', 'Thread Expansion', 'Favicon', 'Quote Threading', 'Thread Updater', 'Banner', 'Flash Features', 'Reply Pruning'], + detect: function() { + var j, len, m, properties, ref, root, script; + ref = $$('script:not([src])', d.head); + for (j = 0, len = ref.length; j < len; j++) { + script = ref[j]; + if ((m = script.textContent.match(/\bvar configRoot=(".*?")/))) { + properties = $.dict(); + try { + root = JSON.parse(m[1]); + if (root[0] === '/') { + properties.root = location.origin + root; + } else if (/^https?:/.test(root)) { + properties.root = root; + } + } catch (error) {} + return properties; + } } + return false; }, - selectArchives: function() { - var archive, archives, boardID, boards, data, files, id, j, k, key, l, len, len1, len2, name, o, record, ref, ref1, ref2, software, type, uid; - o = { - thread: {}, - post: {}, - file: {} - }; - archives = {}; - ref = Conf['archives']; - for (j = 0, len = ref.length; j < len; j++) { - data = ref[j]; - ref1 = ['boards', 'files']; - for (k = 0, len1 = ref1.length; k < len1; k++) { - key = ref1[k]; - if (!(data[key] instanceof Array)) { - data[key] = []; - } + awaitBoard: function(cb) { + var reactUI, s; + if ((reactUI = $.id('react-ui'))) { + s = this.selectors = Object.create(this.selectors); + s.boardFor = { + index: '.page-container' + }; + s.thread = 'div[id^="thread_"]'; + return Main.mounted(cb); + } else { + return cb(); + } + }, + urls: { + thread: function(arg, isArchived) { + var boardID, ref, siteID, threadID; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID; + return "" + (((ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0) || ("http://" + siteID + "/")) + boardID + "/" + (isArchived ? 'archive/' : '') + "res/" + threadID + ".html"; + }, + post: function(arg) { + var postID; + postID = arg.postID; + return "#" + postID; + }, + index: function(arg) { + var boardID, ref, siteID; + siteID = arg.siteID, boardID = arg.boardID; + return "" + (((ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0) || ("http://" + siteID + "/")) + boardID + "/"; + }, + catalog: function(arg) { + var boardID, ref, siteID; + siteID = arg.siteID, boardID = arg.boardID; + return "" + (((ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0) || ("http://" + siteID + "/")) + boardID + "/catalog.html"; + }, + threadJSON: function(arg, isArchived) { + var boardID, ref, root, siteID, threadID; + siteID = arg.siteID, boardID = arg.boardID, threadID = arg.threadID; + root = (ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0; + if (root) { + return "" + root + boardID + "/" + (isArchived ? 'archive/' : '') + "res/" + threadID + ".json"; + } else { + return ''; } - uid = data.uid, name = data.name, boards = data.boards, files = data.files, software = data.software; - if (software !== 'fuuka' && software !== 'foolfuuka') { - continue; + }, + archivedThreadJSON: function(thread) { + return SW.tinyboard.urls.threadJSON(thread, true); + }, + threadsListJSON: function(arg) { + var boardID, ref, root, siteID; + siteID = arg.siteID, boardID = arg.boardID; + root = (ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0; + if (root) { + return "" + root + boardID + "/threads.json"; + } else { + return ''; } - archives[JSON.stringify(uid != null ? uid : name)] = data; - for (l = 0, len2 = boards.length; l < len2; l++) { - boardID = boards[l]; - if (!(boardID in o.thread)) { - o.thread[boardID] = data; - } - if (!(boardID in o.post || software !== 'foolfuuka')) { - o.post[boardID] = data; - } - if (!(boardID in o.file || indexOf.call(files, boardID) < 0)) { - o.file[boardID] = data; - } + }, + archiveListJSON: function(arg) { + var boardID, ref, root, siteID; + siteID = arg.siteID, boardID = arg.boardID; + root = (ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0; + if (root) { + return "" + root + boardID + "/archive/archive.json"; + } else { + return ''; } - } - ref2 = Conf['selectedArchives']; - for (boardID in ref2) { - record = ref2[boardID]; - for (type in record) { - id = record[type]; - if (!((archive = archives[JSON.stringify(id)]))) { - continue; + }, + catalogJSON: function(arg) { + var boardID, ref, root, siteID; + siteID = arg.siteID, boardID = arg.boardID; + root = (ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0; + if (root) { + return "" + root + boardID + "/catalog.json"; + } else { + return ''; + } + }, + file: function(arg, filename) { + var boardID, ref, siteID; + siteID = arg.siteID, boardID = arg.boardID; + return "" + (((ref = Conf['siteProperties'][siteID]) != null ? ref.root : void 0) || ("http://" + siteID + "/")) + boardID + "/" + filename; + }, + thumb: function(board, filename) { + return SW.tinyboard.urls.file(board, filename); + } + }, + selectors: { + board: 'form[name="postcontrols"]', + thread: 'input[name="board"] ~ div[id^="thread_"]', + threadDivider: 'div[id^="thread_"] > hr:last-child', + summary: '.omitted', + postContainer: 'div[id^="reply_"]:not(.hidden)', + opBottom: '.op', + replyOriginal: 'div[id^="reply_"]:not(.hidden)', + infoRoot: '.intro', + info: { + subject: '.subject', + name: '.name', + email: '.email', + tripcode: '.trip', + uniqueID: '.poster_id', + capcode: '.capcode', + flag: '.flag', + date: 'time', + nameBlock: 'label', + quote: 'a[href*="#q"]', + reply: 'a[href*="/res/"]:not([href*="#"])' + }, + icons: { + isSticky: '.fa-thumb-tack', + isClosed: '.fa-lock' + }, + file: { + text: '.fileinfo', + link: '.fileinfo > a', + thumb: 'a > .post-image' + }, + thumbLink: '.file > a', + multifile: '.files > .file', + highlightable: { + op: ' > .op', + reply: '.reply', + catalog: ' > .thread' + }, + comment: '.body', + spoiler: '.spoiler', + quotelink: 'a[onclick*="highlightReply("]', + catalog: { + board: '#Grid', + thread: '.mix', + thumb: '.thread-image' + }, + boardList: '.boardlist', + boardListBottom: '.boardlist.bottom', + styleSheet: '#stylesheet', + psa: '.blotter', + nav: { + prev: '.pages > form > [value=Previous]', + next: '.pages > form > [value=Next]' + } + }, + classes: { + highlight: 'highlighted' + }, + xpath: { + thread: 'div[starts-with(@id,"thread_")]', + postContainer: 'div[starts-with(@id,"reply_") or starts-with(@id,"thread_")]', + replyContainer: 'div[starts-with(@id,"reply_")]' + }, + regexp: { + quotelink: /\/([^\/]+)\/res\/(\d+)(?:\.\w+)?#(\d+)$/, + quotelinkHTML: /]*\bhref="[^"]*\/([^\/]+)\/res\/(\d+)(?:\.\w+)?#(\d+)"/g + }, + Build: { + parseJSON: function(data, board) { + var extra_file, file, i, j, len, o, ref; + o = SW.yotsuba.Build.parseJSON(data, board); + if (data.ext === 'deleted') { + delete o.file; + $.extend(o, { + files: [], + fileDeleted: true, + filesDeleted: [0] + }); + } + if (data.extra_files) { + ref = data.extra_files; + for (i = j = 0, len = ref.length; j < len; i = ++j) { + extra_file = ref[i]; + if (extra_file.ext === 'deleted') { + o.filesDeleted.push(i); + } else { + file = SW.yotsuba.Build.parseJSONFile(data, board); + o.files.push(file); + } } - boards = type === 'file' ? archive.files : archive.boards; - if (indexOf.call(boards, boardID) >= 0) { - o[type][boardID] = archive; + if (o.files.length) { + o.file = o.files[0]; } } + return o; + }, + parseComment: function(html) { + html = html.replace(//gi, '\n').replace(/<[^>]*>/g, ''); + return $.unescape(html); } - return Redirect.data = o; }, - update: function(cb) { - var i, j, k, len, len1, load, nloaded, ref, ref1, responses, url, urls; - urls = []; - responses = []; - nloaded = 0; - ref = Conf['archiveLists'].split('\n'); - for (j = 0, len = ref.length; j < len; j++) { - url = ref[j]; - if (!(url[0] !== '#')) { - continue; - } - url = url.trim(); - if (url) { - urls.push(url); - } + bgColoredEl: function() { + return $.el('div', { + className: 'post reply' + }); + }, + isFileURL: function(url) { + return /\/src\/[^\/]+/.test(url.pathname); + }, + preParsingFixes: function(board) { + var broken; + if ((broken = $('a > input[name="board"]', board))) { + return $.before(broken.parentNode, broken); } - load = function(i) { - return function() { - var err, fail, response; - fail = function(action, msg) { - return new Notice('warning', "Error " + action + " archive data from\n" + urls[i] + "\n" + msg, 20); - }; - if (this.status !== 200) { - return fail('fetching', (this.status ? "Error " + this.statusText + " (" + this.status + ")" : 'Connection Error')); - } - try { - response = JSON.parse(this.response); - } catch (_error) { - err = _error; - return fail('parsing', err.message); - } - if (!(response instanceof Array)) { - response = [response]; - } - responses[i] = response; - nloaded++; - if (nloaded === urls.length) { - return Redirect.parse(responses, cb); - } - }; - }; - if (urls.length) { - for (i = k = 0, len1 = urls.length; k < len1; i = ++k) { - url = urls[i]; - if ((ref1 = url[0]) === '[' || ref1 === '{') { - load(i).call({ - status: 200, - response: url - }); - } else { - $.ajax(url, { - responseType: 'text', - onloadend: load(i) - }); - } - } - } else { - Redirect.parse([], cb); + }, + parseNodes: function(post, nodes) { + var m, nextSibling, node, text, uniqueID; + if (nodes.uniqueID) { + return; + } + text = ''; + node = nodes.nameBlock.nextSibling; + while (node && node.nodeType === 3) { + text += node.textContent; + node = node.nextSibling; + } + if ((m = text.match(/(\s*ID:\s*)(\S+)/))) { + nodes.info.normalize(); + nextSibling = nodes.nameBlock.nextSibling; + nextSibling = nextSibling.splitText(m[1].length); + nextSibling.splitText(m[2].length); + nodes.uniqueID = uniqueID = $.el('span', { + className: 'poster_id' + }); + $.replace(nextSibling, uniqueID); + return $.add(uniqueID, nextSibling); } }, - parse: function(responses, cb) { - var archiveUIDs, archives, data, items, j, k, len, len1, ref, response, uid; - archives = []; - archiveUIDs = {}; - for (j = 0, len = responses.length; j < len; j++) { - response = responses[j]; - for (k = 0, len1 = response.length; k < len1; k++) { - data = response[k]; - uid = JSON.stringify((ref = data.uid) != null ? ref : data.name); - if (uid in archiveUIDs) { - $.extend(archiveUIDs[uid], data); - } else { - archiveUIDs[uid] = data; - archives.push(data); - } - } + parseDate: function(node) { + var date, ref; + date = Date.parse((ref = node.getAttribute('datetime')) != null ? ref.trim() : void 0); + if (!isNaN(date)) { + return new Date(date); + } + date = Date.parse(node.textContent.trim() + ' UTC'); + if (!isNaN(date)) { + return new Date(date); + } + return void 0; + }, + parseFile: function(post, file) { + var info, infoNode, link, nameNode, ref, ref1, text, thumb; + text = file.text, link = file.link, thumb = file.thumb; + if ($.x("ancestor::" + this.xpath.postContainer + "[1]", text) !== post.nodes.root) { + return false; + } + if (!(infoNode = indexOf.call((ref = link.nextSibling) != null ? ref.textContent : void 0, '(') >= 0 ? link.nextSibling : link.nextElementSibling)) { + return false; + } + if (!(info = infoNode.textContent.match(/\((.*,\s*)?([\d.]+ ?[KMG]?B).*\)/))) { + return false; + } + nameNode = $('.postfilename', text); + $.extend(file, { + name: nameNode ? nameNode.title || nameNode.textContent : link.pathname.match(/[^\/]*$/)[0], + size: info[2], + dimensions: (ref1 = info[0].match(/\d+x\d+/)) != null ? ref1[0] : void 0 + }); + if (thumb) { + $.extend(file, { + thumbURL: /\/static\//.test(thumb.src) && $.isImage(link.href) ? link.href : thumb.src, + isSpoiler: /^Spoiler/i.test(info[1] || '') || link.textContent === 'Spoiler Image' + }); + } + return true; + }, + isThumbExpanded: function(file) { + return $.hasClass(file.thumb.parentNode, 'expanded') || file.thumb.parentNode.dataset.expanded === 'true'; + }, + isLinkified: function(link) { + return /\bnofollow\b/.test(link.rel); + }, + catalogPin: function(threadRoot) { + return threadRoot.dataset.sticky = 'true'; + } + }; + +}).call(this); + +(function() { + var slice = [].slice; + + SW.yotsuba = { + isOPContainerThread: false, + hasIPCount: true, + archivedBoardsKnown: true, + urls: { + thread: function(arg) { + var boardID, threadID; + boardID = arg.boardID, threadID = arg.threadID; + return location.protocol + "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/thread/" + threadID; + }, + post: function(arg) { + var postID; + postID = arg.postID; + return "#p" + postID; + }, + index: function(arg) { + var boardID; + boardID = arg.boardID; + return location.protocol + "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/"; + }, + catalog: function(arg) { + var boardID; + boardID = arg.boardID; + if (boardID === 'f') { + return void 0; + } else { + return location.protocol + "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/catalog"; + } + }, + archive: function(arg) { + var boardID; + boardID = arg.boardID; + if (BoardConfig.isArchived(boardID)) { + return location.protocol + "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/archive"; + } else { + return void 0; + } + }, + threadJSON: function(arg) { + var boardID, threadID; + boardID = arg.boardID, threadID = arg.threadID; + return location.protocol + "//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json"; + }, + threadsListJSON: function(arg) { + var boardID; + boardID = arg.boardID; + return location.protocol + "//a.4cdn.org/" + boardID + "/threads.json"; + }, + archiveListJSON: function(arg) { + var boardID; + boardID = arg.boardID; + if (BoardConfig.isArchived(boardID)) { + return location.protocol + "//a.4cdn.org/" + boardID + "/archive.json"; + } else { + return ''; + } + }, + catalogJSON: function(arg) { + var boardID; + boardID = arg.boardID; + return location.protocol + "//a.4cdn.org/" + boardID + "/catalog.json"; + }, + file: function(arg, filename) { + var boardID, hostname; + boardID = arg.boardID; + hostname = boardID === 'f' ? ImageHost.flashHost() : ImageHost.host(); + return location.protocol + "//" + hostname + "/" + boardID + "/" + filename; + }, + thumb: function(arg, filename) { + var boardID; + boardID = arg.boardID; + return location.protocol + "//" + (ImageHost.thumbHost()) + "/" + boardID + "/" + filename; + } + }, + isPrunedByAge: function(arg) { + var boardID; + boardID = arg.boardID; + return boardID === 'f'; + }, + areMD5sDeferred: function(arg) { + var boardID; + boardID = arg.boardID; + return boardID === 'f'; + }, + isOnePage: function(arg) { + var boardID; + boardID = arg.boardID; + return boardID === 'f'; + }, + noAudio: function(arg) { + var boardID; + boardID = arg.boardID; + return BoardConfig.noAudio(boardID); + }, + selectors: { + board: '.board', + thread: '.thread', + threadDivider: '.board > hr', + summary: '.summary', + postContainer: '.postContainer', + replyOriginal: '.replyContainer:not([data-clone])', + sideArrows: 'div.sideArrows', + post: '.post', + infoRoot: '.postInfo', + info: { + subject: '.subject', + name: '.name', + email: '.useremail', + tripcode: '.postertrip', + uniqueIDRoot: '.posteruid', + uniqueID: '.posteruid > .hand', + capcode: '.capcode.hand', + pass: '.n-pu', + flag: '.flag, .bfl', + date: '.dateTime', + nameBlock: '.nameBlock', + quote: '.postNum > a:nth-of-type(2)', + reply: '.replylink' + }, + icons: { + isSticky: '.stickyIcon', + isClosed: '.closedIcon', + isArchived: '.archivedIcon' + }, + file: { + text: '.file > :first-child', + link: '.fileText > a', + thumb: 'a.fileThumb > [data-md5]' + }, + thumbLink: 'a.fileThumb', + highlightable: { + op: '.opContainer', + reply: ' > .reply', + catalog: '' + }, + comment: '.postMessage', + spoiler: 's', + quotelink: ':not(pre) > .quotelink', + catalog: { + board: '#threads', + thread: '.thread', + thumb: '.thumb' + }, + boardList: '#boardNavDesktop > .boardList', + boardListBottom: '#boardNavDesktopFoot > .boardList', + styleSheet: 'link[title=switch]', + psa: '#globalMessage', + psaTop: '#globalToggle', + searchBox: '#search-box', + nav: { + prev: '.prev > form > [type=submit]', + next: '.next > form > [type=submit]' + } + }, + classes: { + highlight: 'highlight' + }, + xpath: { + thread: 'div[contains(concat(" ",@class," ")," thread ")]', + postContainer: 'div[contains(@class,"postContainer")]', + replyContainer: 'div[contains(@class,"replyContainer")]' + }, + regexp: { + quotelink: /^https?:\/\/boards\.4chan(?:nel)?\.org\/+([^\/]+)\/+thread\/+(\d+)(?:[\/?][^#]*)?(?:#p(\d+))?$/, + quotelinkHTML: /]*\bhref="(?:(?:\/\/boards\.4chan(?:nel)?\.org)?\/([^\/]+)\/thread\/)?(\d+)?(?:#p(\d+))?"/g, + pass: /^https?:\/\/www\.4chan(?:nel)?\.org\/+pass(?:$|[?#])/, + captcha: /^https?:\/\/sys\.4chan(?:nel)?\.org\/+captcha(?:$|[?#])/ + }, + bgColoredEl: function() { + return $.el('div', { + className: 'reply' + }); + }, + isThisPageLegit: function() { + var ref, ref1; + return ((ref = location.hostname) === 'boards.4chan.org' || ref === 'boards.4channel.org') && d.doctype && !$('link[href*="favicon-status.ico"]', d.head) && ((ref1 = d.title) !== '4chan - Temporarily Offline' && ref1 !== '4chan - Error' && ref1 !== '504 Gateway Time-out' && ref1 !== 'MathJax Equation Source'); + }, + is404: function() { + var ref; + return ((ref = d.title) === '4chan - Temporarily Offline' || ref === '4chan - 404 Not Found') || (g.VIEW === 'thread' && $('.board') && !$('.opContainer')); + }, + isIncomplete: function() { + var ref; + return ((ref = g.VIEW) === 'index' || ref === 'thread') && !$('.board + *'); + }, + isBoardlessPage: function(url) { + var ref; + return (ref = url.hostname) === 'www.4chan.org' || ref === 'www.4channel.org'; + }, + isAuxiliaryPage: function(url) { + var ref; + return (ref = url.hostname) !== 'boards.4chan.org' && ref !== 'boards.4channel.org'; + }, + isFileURL: function(url) { + return ImageHost.test(url.hostname); + }, + initAuxiliary: function() { + var match, pathname; + switch (location.hostname) { + case 'www.4chan.org': + case 'www.4channel.org': + if (SW.yotsuba.regexp.pass.test(location.href)) { + PassMessage.init(); + } else { + $.onExists(doc, 'body', function() { + return $.addStyle(CSS.www); + }); + Captcha.replace.init(); + } + break; + case 'sys.4chan.org': + case 'sys.4channel.org': + pathname = location.pathname.split(/\/+/); + if (pathname[2] === 'imgboard.php') { + if (/\bmode=report\b/.test(location.search)) { + Report.init(); + } else if ((match = location.search.match(/\bres=(\d+)/))) { + $.ready(function() { + var ref; + if (Conf['404 Redirect'] && ((ref = $.id('errmsg')) != null ? ref.textContent : void 0) === 'Error: Specified thread does not exist.') { + return Redirect.navigate('thread', { + boardID: g.BOARD.ID, + postID: +match[1] + }); + } + }); + } + } else if (pathname[2] === 'post') { + PostSuccessful.init(); + } + } + }, + scriptData: function() { + var j, len, ref, script; + ref = $$('script:not([src])', d.head); + for (j = 0, len = ref.length; j < len; j++) { + script = ref[j]; + if (/\bcooldowns *=/.test(script.textContent)) { + return script.textContent; + } + } + return ''; + }, + parseThreadMetadata: function(thread) { + var file, m, scriptData; + scriptData = this.scriptData(); + thread.postLimit = /\bbumplimit *= *1\b/.test(scriptData); + thread.fileLimit = /\bimagelimit *= *1\b/.test(scriptData); + thread.ipCount = (m = scriptData.match(/\bunique_ips *= *(\d+)\b/)) ? +m[1] : void 0; + if (g.BOARD.ID === 'f' && thread.OP.file) { + file = thread.OP.file; + return $.ajax(this.urls.threadJSON({ + boardID: 'f', + threadID: thread.ID + }), { + timeout: $.MINUTE, + onloadend: function() { + if (this.response) { + return file.text.dataset.md5 = file.MD5 = this.response.posts[0].md5; + } + } + }); + } + }, + parseNodes: function(post, nodes) { + var icon, j, len, ref, results, type; + if (post.boardID === 'f') { + ref = ['Sticky', 'Closed']; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + type = ref[j]; + if ((icon = $("img[alt=" + type + "]", nodes.info))) { + results.push($.addClass(icon, (type.toLowerCase()) + "Icon", 'retina')); + } + } + return results; + } + }, + parseDate: function(node) { + return new Date(node.dataset.utc * 1000); + }, + parseFile: function(post, file) { + var info, link, m, ref, ref1, ref2, text, thumb; + text = file.text, link = file.link, thumb = file.thumb; + if (!(info = (ref = link.nextSibling) != null ? ref.textContent.match(/\(([\d.]+ [KMG]?B).*\)/) : void 0)) { + return false; + } + $.extend(file, { + name: text.title || link.title || link.textContent, + size: info[1], + dimensions: (ref1 = info[0].match(/\d+x\d+/)) != null ? ref1[0] : void 0, + tag: (ref2 = info[0].match(/,[^,]*, ([a-z]+)\)/i)) != null ? ref2[1] : void 0, + MD5: text.dataset.md5 + }); + if (thumb) { + $.extend(file, { + thumbURL: thumb.src, + MD5: thumb.dataset.md5, + isSpoiler: $.hasClass(thumb.parentNode, 'imgspoiler') + }); + if (file.isSpoiler) { + file.thumbURL = (m = link.href.match(/\d+(?=\.\w+$)/)) ? location.protocol + "//" + (ImageHost.thumbHost()) + "/" + post.board + "/" + m[0] + "s.jpg" : void 0; + } + } + return true; + }, + cleanComment: function(bq) { + var abbr, br, i, j, k, len, node, ref; + if ((abbr = $('.abbr', bq))) { + ref = $$('.abbr + br, .exif', bq); + for (j = 0, len = ref.length; j < len; j++) { + node = ref[j]; + $.rm(node); + } + for (i = k = 0; k < 2; i = ++k) { + if ((br = abbr.previousSibling) && br.nodeName === 'BR') { + $.rm(br); + } + } + return $.rm(abbr); + } + }, + cleanCommentDisplay: function(bq) { + var b; + if ((b = $('b', bq)) && /^Rolled /.test(b.textContent)) { + $.rm(b); + } + return $.rm($('.fortune', bq)); + }, + insertTags: function(bq) { + var j, k, len, len1, node, ref, ref1; + ref = $$('s, .removed-spoiler', bq); + for (j = 0, len = ref.length; j < len; j++) { + node = ref[j]; + $.replace(node, [$.tn('[spoiler]')].concat(slice.call(node.childNodes), [$.tn('[/spoiler]')])); + } + ref1 = $$('.prettyprint', bq); + for (k = 0, len1 = ref1.length; k < len1; k++) { + node = ref1[k]; + $.replace(node, [$.tn('[code]')].concat(slice.call(node.childNodes), [$.tn('[/code]')])); + } + }, + hasCORS: function(url) { + return url.split('/').slice(0, 3).join('/') === location.protocol + '//a.4cdn.org'; + }, + sfwBoards: function(sfw) { + return BoardConfig.sfwBoards(sfw); + }, + uidColor: function(uid) { + var i, msg; + msg = 0; + i = 0; + while (i < 8) { + msg = (msg << 5) - msg + uid.charCodeAt(i++); + } + return (msg >> 8) & 0xFFFFFF; + }, + isLinkified: function(link) { + return ImageHost.test(link.hostname); + }, + testNativeExtension: function() { + return $.global(function() { + if (window.Parser.postMenuIcon) { + return this.enabled = 'true'; + } + }); + }, + transformBoardList: function() { + var a, chr, i, items, j, len, node, nodes, ref, spacer, span; + nodes = []; + spacer = function() { + return $.el('span', { + className: 'spacer' + }); + }; + items = $.X('.//a|.//text()[not(ancestor::a)]', $(SW.yotsuba.selectors.boardList)); + i = 0; + while (node = items.snapshotItem(i++)) { + switch (node.nodeName) { + case '#text': + ref = node.nodeValue; + for (j = 0, len = ref.length; j < len; j++) { + chr = ref[j]; + span = $.el('span', { + textContent: chr + }); + if (chr === ' ') { + span.className = 'space'; + } + if (chr === ']') { + nodes.push(spacer()); + } + nodes.push(span); + if (chr === '[') { + nodes.push(spacer()); + } + } + break; + case 'A': + a = node.cloneNode(true); + nodes.push(a); + } + } + return nodes; + } + }; + +}).call(this); + +(function() { + var Build, + slice = [].slice; + + Build = { + staticPath: '//s.4cdn.org/image/', + gifIcon: window.devicePixelRatio >= 2 ? '@2x.gif' : '.gif', + spoilerRange: $.dict(), + shortFilename: function(filename) { + var ext; + ext = filename.match(/\.?[^\.]*$/)[0]; + if (filename.length - ext.length > 30) { + return (filename.match(/(?:[\uD800-\uDBFF][\uDC00-\uDFFF]|[^]){0,25}/)[0]) + "(...)" + ext; + } else { + return filename; + } + }, + spoilerThumb: function(boardID) { + var spoilerRange; + if (spoilerRange = Build.spoilerRange[boardID]) { + return Build.staticPath + "spoiler-" + boardID + (Math.floor(1 + spoilerRange * Math.random())) + ".png"; + } else { + return Build.staticPath + "spoiler.png"; + } + }, + sameThread: function(boardID, threadID) { + return g.VIEW === 'thread' && g.BOARD.ID === boardID && g.THREADID === +threadID; + }, + threadURL: function(boardID, threadID) { + if (boardID !== g.BOARD.ID) { + return "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/thread/" + threadID; + } else if (g.VIEW !== 'thread' || +threadID !== g.THREADID) { + return "/" + boardID + "/thread/" + threadID; + } else { + return ''; + } + }, + postURL: function(boardID, threadID, postID) { + return (Build.threadURL(boardID, threadID)) + "#p" + postID; + }, + parseJSON: function(data, arg) { + var boardID, key, o, siteID; + siteID = arg.siteID, boardID = arg.boardID; + o = { + ID: data.no, + postID: data.no, + threadID: data.resto || data.no, + boardID: boardID, + siteID: siteID, + isReply: !!data.resto, + isSticky: !!data.sticky, + isClosed: !!data.closed, + isArchived: !!data.archived, + fileDeleted: !!data.filedeleted, + filesDeleted: data.filedeleted ? [0] : [] + }; + o.info = { + subject: $.unescape(data.sub), + email: $.unescape(data.email), + name: $.unescape(data.name) || '', + tripcode: data.trip, + pass: data.since4pass != null ? "" + data.since4pass : void 0, + uniqueID: data.id, + flagCode: data.country, + flagCodeTroll: data.board_flag, + flag: $.unescape(data.country_name || data.flag_name), + dateUTC: data.time, + dateText: data.now, + commentHTML: { + innerHTML: data.com || '' + } + }; + if (data.capcode) { + o.info.capcode = data.capcode.replace(/_highlight$/, '').replace(/_/g, ' ').replace(/\b\w/g, function(c) { + return c.toUpperCase(); + }); + o.capcodeHighlight = /_highlight$/.test(data.capcode); + delete o.info.uniqueID; + } + o.files = []; + if (data.ext) { + o.file = SW.yotsuba.Build.parseJSONFile(data, { + siteID: siteID, + boardID: boardID + }); + o.files.push(o.file); + } + o.extra = $.dict(); + for (key in data) { + if (key[0] === 'x') { + o.extra[key] = data[key]; + } + } + return o; + }, + parseJSONFile: function(data, arg) { + var boardID, filename, o, site, siteID; + siteID = arg.siteID, boardID = arg.boardID; + site = g.sites[siteID]; + filename = site.software === 'yotsuba' && boardID === 'f' ? "" + (encodeURIComponent(data.filename)) + data.ext : "" + data.tim + data.ext; + o = { + name: ($.unescape(data.filename)) + data.ext, + url: site.urls.file({ + siteID: siteID, + boardID: boardID + }, filename), + height: data.h, + width: data.w, + MD5: data.md5, + size: $.bytesToString(data.fsize), + thumbURL: site.urls.thumb({ + siteID: siteID, + boardID: boardID + }, data.tim + "s.jpg"), + theight: data.tn_h, + twidth: data.tn_w, + isSpoiler: !!data.spoiler, + tag: data.tag, + hasDownscale: !!data.m_img + }; + if ((data.h != null) && !/\.pdf$/.test(o.url)) { + o.dimensions = o.width + "x" + o.height; + } + return o; + }, + parseComment: function(html) { + html = html.replace(//gi, '\n').replace(/\n\n]*>/g, ''); + return $.unescape(html); + }, + parseCommentDisplay: function(html) { + var html2; + if (!(Conf['Remove Spoilers'] || Conf['Reveal Spoilers'])) { + while ((html2 = html.replace(/(?:(?!<\/?s>).)*<\/s>/g, '[spoiler]')) !== html) { + html = html2; + } + } + html = html.replace(/^Rolled [^<]*<\/b>/i, '').replace(/ " + ((!o.isReply || boardID === "f" || subject) ? "" + E(subject || "") + " " : "") + "" + ((email) ? "" : "") + "" + E(name) + "" + ((tripcode) ? " " + E(tripcode) + "" : "") + ((pass) ? " " : "") + ((capcode) ? " ## " + E(capcode) + "" : "") + ((email) ? "" : "") + ((boardID === "f" && !o.isReply || capcodeDescription) ? "" : " ") + ((capcodeDescription) ? " \""" : "") + ((uniqueID && !capcode) ? " (ID: " + E(uniqueID) + ")" : "") + ((flagCode) ? " " : "") + ((flagCodeTroll) ? " " : "") + "
          " + E(dateText) + " No." + E(ID) + "" + ((o.isSticky) ? " \"Sticky\"" : "") + ((o.isClosed && !o.isArchived) ? " \"Closed\"" : "") + ((o.isArchived) ? " \"Archived\"" : "") + ((!o.isReply && g.VIEW === "index") ? "   [Reply]" : "") + ""}; + + /* File Info */ + if (file) { + protocol = /^https?:(?=\/\/i\.4cdn\.org\/)/; + fileURL = file.url.replace(protocol, ''); + shortFilename = Build.shortFilename(file.name); + fileThumb = file.isSpoiler ? Build.spoilerThumb(boardID) : file.thumbURL.replace(protocol, ''); + } + fileBlock = {innerHTML: ((file) ? "
          " + ((boardID === "f") ? "
          File: " + E(file.name) + "-(" + E(file.size) + ", " + E(file.dimensions) + ((file.tag) ? ", " + E(file.tag) : "") + ")
          " : "
          File: " + ((file.isSpoiler) ? "Spoiler Image" : E(shortFilename)) + " (" + E(file.size) + ", " + E(file.dimensions || "PDF") + ")
          \""") + "
          " : ((o.fileDeleted) ? "
          \"File
          " : ""))}; + + /* Whole Post */ + postClass = o.isReply ? 'reply' : 'op'; + wholePost = {innerHTML: ((o.isReply) ? "
          >>
          " : "") + "
          " + ((o.isReply) ? (postInfo).innerHTML + (fileBlock).innerHTML : (fileBlock).innerHTML + (postInfo).innerHTML) + "
          " + (commentHTML).innerHTML + "
          "}; + container = $.el('div', { + className: "postContainer " + postClass + "Container", + id: "pc" + ID + }); + $.extend(container, wholePost); + ref1 = $$('.quotelink', container); + for (i = 0, len = ref1.length; i < len; i++) { + quote = ref1[i]; + href = quote.getAttribute('href'); + if (href[0] === '#') { + if (!Build.sameThread(boardID, threadID)) { + quote.href = Build.threadURL(boardID, threadID) + href; + } + } else { + if ((match = quote.href.match(SW.yotsuba.regexp.quotelink)) && (Build.sameThread(match[1], match[2]))) { + quote.href = href.match(/(#[^#]*)?$/)[0] || '#'; + } + } + } + return container; + }, + summaryText: function(status, posts, files) { + var text; + text = ''; + if (status) { + text += status + " "; + } + text += posts + " post" + (posts > 1 ? 's' : ''); + if (+files) { + text += " and " + files + " image repl" + (files > 1 ? 'ies' : 'y'); + } + return text += " " + (status === '-' ? 'shown' : 'omitted') + "."; + }, + summary: function(boardID, threadID, posts, files) { + return $.el('a', { + className: 'summary', + textContent: Build.summaryText('', posts, files), + href: "/" + boardID + "/thread/" + threadID + }); + }, + thread: function(thread, data, withReplies) { + var files, posts, ref, root, summary; + if ((root = thread.nodes.root)) { + $.rmAll(root); + } else { + thread.nodes.root = root = $.el('div', { + className: 'thread', + id: "t" + data.no + }); + } + if (Build.hat) { + $.add(root, Build.hat.cloneNode(false)); + } + $.add(root, thread.OP.nodes.root); + if (data.omitted_posts || !withReplies && data.replies) { + ref = withReplies ? [ + data.omitted_posts, data.images - data.last_replies.filter(function(data) { + return !!data.ext; + }).length + ] : [data.replies, data.images], posts = ref[0], files = ref[1]; + summary = Build.summary(thread.board.ID, data.no, posts, files); + $.add(root, summary); + } + return root; + }, + catalogThread: function(thread, data, pageCount) { + var br, container, cssText, fileCount, gifIcon, i, imgClass, len, postCount, ratio, ref, root, spoilerRange, src, staticPath, tn_h, tn_w; + staticPath = Build.staticPath, gifIcon = Build.gifIcon; + tn_w = data.tn_w, tn_h = data.tn_h; + if (data.spoiler && !Conf['Reveal Spoiler Thumbnails']) { + src = staticPath + "spoiler"; + if (spoilerRange = Build.spoilerRange[thread.board]) { + src += ("-" + thread.board) + Math.floor(1 + spoilerRange * Math.random()); + } + src += '.png'; + imgClass = 'spoiler-file'; + cssText = "--tn-w: 100; --tn-h: 100;"; + } else if (data.filedeleted) { + src = staticPath + "filedeleted-res" + gifIcon; + imgClass = 'deleted-file'; + } else if (thread.OP.file) { + src = thread.OP.file.thumbURL; + ratio = 250 / Math.max(tn_w, tn_h); + cssText = "--tn-w: " + (tn_w * ratio) + "; --tn-h: " + (tn_h * ratio) + ";"; + } else { + src = staticPath + "nofile.png"; + imgClass = 'no-file'; + } + postCount = data.replies + 1; + fileCount = data.images + !!data.ext; + container = $.el('div', {innerHTML: "
          " + E(postCount) + " / " + E(fileCount) + " / " + E(pageCount) + "" + ((thread.isSticky) ? "" : "") + ((thread.isClosed) ? "" : "") + "
          "}); + $.before(thread.OP.nodes.info, slice.call(container.childNodes)); + ref = $$('br', thread.OP.nodes.comment); + for (i = 0, len = ref.length; i < len; i++) { + br = ref[i]; + if (br.previousSibling && br.previousSibling.nodeName === 'BR') { + $.addClass(br, 'extra-linebreak'); + } + } + root = $.el('div', { + className: 'thread catalog-thread', + id: "t" + thread + }); + if (thread.OP.highlights) { + $.addClass.apply($, [root].concat(slice.call(thread.OP.highlights))); + } + if (!thread.OP.file) { + $.addClass(root, 'noFile'); + } + root.style.cssText = cssText || ''; + return root; + }, + catalogReply: function(thread, data) { + var excerpt, link; + excerpt = ''; + if (data.com) { + excerpt = Build.parseCommentDisplay(data.com).replace(/>>\d+/g, '').trim().replace(/\n+/g, ' // '); + } + if (data.ext) { + excerpt || (excerpt = "" + ($.unescape(data.filename)) + data.ext); + } + if (data.com) { + excerpt || (excerpt = $.unescape(data.com.replace(//gi, ' // '))); + } + excerpt || (excerpt = '\xA0'); + if (excerpt.length > 73) { + excerpt = excerpt.slice(0, 70) + "..."; + } + link = Build.postURL(thread.board.ID, thread.ID, data.no); + return $.el('div', { + className: 'catalog-reply' + }, {innerHTML: ": " + E(excerpt) + "..."}); + } + }; + + SW.yotsuba.Build = Build; + +}).call(this); + +Site = (function() { + var Site; + + Site = { + defaultProperties: { + '4chan.org': { + software: 'yotsuba' + }, + '4channel.org': { + canonical: '4chan.org' + }, + '4cdn.org': { + canonical: '4chan.org' + }, + 'notso.smuglo.li': { + canonical: 'smuglo.li' + }, + 'smugloli.net': { + canonical: 'smuglo.li' + }, + 'smug.nepu.moe': { + canonical: 'smuglo.li' + } + }, + init: function(cb) { + var hostname; + $.extend(Conf['siteProperties'], Site.defaultProperties); + hostname = Site.resolve(); + if (hostname && $.hasOwn(SW, Conf['siteProperties'][hostname].software)) { + this.set(hostname); + cb(); + } + return $.onExists(doc, 'body', (function(_this) { + return function() { + var base, base1, changed, changes, key, properties, software; + for (software in SW) { + if (!((changes = typeof (base = SW[software]).detect === "function" ? base.detect() : void 0))) { + continue; + } + changes.software = software; + hostname = location.hostname.replace(/^www\./, ''); + properties = ((base1 = Conf['siteProperties'])[hostname] || (base1[hostname] = $.dict())); + changed = 0; + for (key in changes) { + if (!(properties[key] !== changes[key])) { + continue; + } + properties[key] = changes[key]; + changed++; + } + if (changed) { + $.set('siteProperties', Conf['siteProperties']); + } + if (!g.SITE) { + _this.set(hostname); + cb(); + } + return; + } + }; + })(this)); + }, + resolve: function(url) { + var canonical, hostname; + if (url == null) { + url = location; + } + hostname = url.hostname; + while (hostname && !$.hasOwn(Conf['siteProperties'], hostname)) { + hostname = hostname.replace(/^[^.]*\.?/, ''); + } + if (hostname) { + if ((canonical = Conf['siteProperties'][hostname].canonical)) { + hostname = canonical; + } + } + return hostname; + }, + parseURL: function(url) { + var siteID; + siteID = Site.resolve(url); + return Main.parseURL(g.sites[siteID], url); + }, + set: function(hostname) { + var ID, properties, ref, site, software; + ref = Conf['siteProperties']; + for (ID in ref) { + properties = ref[ID]; + if (properties.canonical) { + continue; + } + software = properties.software; + if (!(software && $.hasOwn(SW, software))) { + continue; + } + g.sites[ID] = site = Object.create(SW[software]); + $.extend(site, { + ID: ID, + siteID: ID, + properties: properties, + software: software + }); + } + return g.SITE = g.sites[hostname]; + } + }; + + return Site; + +}).call(this); + +Redirect = (function() { + var Redirect, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + Redirect = { + archives: [ + { "uid": 3, "name": "4plebs", "domain": "archive.4plebs.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "adv", "f", "hr", "mlpol", "mo", "o", "pol", "s4s", "sp", "tg", "trv", "tv", "x" ], "files": [ "adv", "f", "hr", "mlpol", "mo", "o", "pol", "s4s", "sp", "tg", "trv", "tv", "x" ], "reports": true }, + { "uid": 10, "name": "warosu", "domain": "warosu.org", "http": false, "https": true, "software": "fuuka", "boards": [ "3", "biz", "cgl", "ck", "diy", "fa", "ic", "jp", "lit", "sci", "vr", "vt" ], "files": [ "3", "biz", "cgl", "ck", "diy", "fa", "ic", "jp", "lit", "sci", "vr", "vt" ], "search": [ "biz", "cgl", "ck", "diy", "fa", "ic", "jp", "lit", "sci", "vr", "vt" ] }, + { "uid": 23, "name": "Desuarchive", "domain": "desuarchive.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "a", "aco", "an", "c", "cgl", "co", "d", "fit", "g", "his", "int", "k", "m", "mlp", "mu", "q", "qa", "r9k", "tg", "trash", "vr", "wsg" ], "files": [ "a", "aco", "an", "c", "cgl", "co", "d", "fit", "g", "his", "int", "k", "m", "mlp", "mu", "q", "qa", "r9k", "tg", "trash", "vr" ], "reports": true }, + { "uid": 24, "name": "fireden.net", "domain": "boards.fireden.net", "http": false, "https": true, "software": "foolfuuka", "boards": [ "cm", "co", "ic", "sci", "vip", "y" ], "files": [ "cm", "co", "ic", "sci", "vip", "y" ], "search": [ "cm", "co", "ic", "sci", "y" ] }, + { "uid": 25, "name": "arch.b4k.co", "domain": "arch.b4k.co", "http": true, "https": true, "software": "foolfuuka", "boards": [ "g", "mlp", "qb", "v", "vg", "vm", "vmg", "vp", "vrpg", "vst" ], "files": [ "qb", "v", "vg", "vm", "vmg", "vp", "vrpg", "vst" ], "search": [ "qb", "v", "vg", "vm", "vmg", "vp", "vrpg", "vst" ] }, + { "uid": 29, "name": "Archived.Moe", "domain": "archived.moe", "http": true, "https": true, "software": "foolfuuka", "boards": [ "3", "a", "aco", "adv", "an", "asp", "b", "bant", "biz", "c", "can", "cgl", "ck", "cm", "co", "cock", "con", "d", "diy", "e", "f", "fa", "fap", "fit", "fitlit", "g", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "int", "jp", "k", "lgbt", "lit", "m", "mlp", "mlpol", "mo", "mtv", "mu", "n", "news", "o", "out", "outsoc", "p", "po", "pol", "pw", "q", "qa", "qb", "qst", "r", "r9k", "s", "s4s", "sci", "soc", "sp", "spa", "t", "tg", "toy", "trash", "trv", "tv", "u", "v", "vg", "vint", "vip", "vm", "vmg", "vp", "vr", "vrpg", "vst", "vt", "w", "wg", "wsg", "wsr", "x", "xs", "y" ], "files": [ "can", "cock", "con", "fap", "fitlit", "gd", "mlpol", "mo", "mtv", "outsoc", "po", "q", "qb", "qst", "spa", "vint", "vip" ], "search": [ "aco", "adv", "an", "asp", "b", "bant", "biz", "c", "can", "cgl", "ck", "cm", "cock", "con", "d", "diy", "e", "f", "fap", "fitlit", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "lgbt", "lit", "mlpol", "mo", "mtv", "n", "news", "o", "out", "outsoc", "p", "po", "pw", "q", "qa", "qst", "r", "s", "soc", "spa", "trv", "u", "vint", "vip", "vrpg", "w", "wg", "wsg", "wsr", "x", "y" ], "reports": true }, + { "uid": 30, "name": "TheBArchive.com", "domain": "thebarchive.com", "http": true, "https": true, "software": "foolfuuka", "boards": [ "b", "bant" ], "files": [ "b", "bant" ], "reports": true }, + { "uid": 31, "name": "Archive Of Sins", "domain": "archiveofsins.com", "http": true, "https": true, "software": "foolfuuka", "boards": [ "h", "hc", "hm", "i", "lgbt", "r", "s", "soc", "t", "u" ], "files": [ "h", "hc", "hm", "i", "lgbt", "r", "s", "soc", "t", "u" ], "reports": true }, + { "uid": 36, "name": "palanq.win", "domain": "archive.palanq.win", "http": false, "https": true, "software": "foolfuuka", "boards": [ "bant", "c", "con", "e", "i", "n", "news", "out", "p", "pw", "qst", "toy", "vip", "vp", "vt", "w", "wg", "wsr" ], "files": [ "bant", "c", "e", "i", "n", "news", "out", "p", "pw", "qst", "toy", "vip", "vp", "vt", "w", "wg", "wsr" ], "reports": true }, + { "uid": 37, "name": "Eientei", "domain": "eientei.xyz", "http": false, "https": true, "software": "Eientei", "boards": [ "3", "i", "sci", "xs" ], "files": [ "3", "i", "sci", "xs" ], "reports": true } + ], + init: function() { + var now, ref; + this.selectArchives(); + if (Conf['archiveAutoUpdate']) { + now = Date.now(); + if (!((now - 2 * $.DAY < (ref = Conf['lastarchivecheck']) && ref <= now))) { + return this.update(); + } + } + }, + selectArchives: function() { + var archive, archives, boardID, boards, data, files, id, j, k, key, l, len, len1, len2, name, o, record, ref, ref1, ref2, software, type, uid; + o = { + thread: $.dict(), + post: $.dict(), + file: $.dict() + }; + archives = $.dict(); + ref = Conf['archives']; + for (j = 0, len = ref.length; j < len; j++) { + data = ref[j]; + ref1 = ['boards', 'files']; + for (k = 0, len1 = ref1.length; k < len1; k++) { + key = ref1[k]; + if (!(data[key] instanceof Array)) { + data[key] = []; + } + } + uid = data.uid, name = data.name, boards = data.boards, files = data.files, software = data.software; + if (software !== 'fuuka' && software !== 'foolfuuka') { + continue; + } + archives[JSON.stringify(uid != null ? uid : name)] = data; + for (l = 0, len2 = boards.length; l < len2; l++) { + boardID = boards[l]; + if (!(boardID in o.thread)) { + o.thread[boardID] = data; + } + if (!(boardID in o.post || software !== 'foolfuuka')) { + o.post[boardID] = data; + } + if (!(boardID in o.file || indexOf.call(files, boardID) < 0)) { + o.file[boardID] = data; + } + } + } + ref2 = Conf['selectedArchives']; + for (boardID in ref2) { + record = ref2[boardID]; + for (type in record) { + id = record[type]; + if (!((archive = archives[JSON.stringify(id)]) && $.hasOwn(o, type))) { + continue; + } + boards = type === 'file' ? archive.files : archive.boards; + if (indexOf.call(boards, boardID) >= 0) { + o[type][boardID] = archive; + } + } + } + return Redirect.data = o; + }, + update: function(cb) { + var err, fail, i, j, k, len, len1, load, nloaded, ref, ref1, response, responses, url, urls; + urls = []; + responses = []; + nloaded = 0; + ref = Conf['archiveLists'].split('\n'); + for (j = 0, len = ref.length; j < len; j++) { + url = ref[j]; + if (!(url[0] !== '#')) { + continue; + } + url = url.trim(); + if (url) { + urls.push(url); + } + } + fail = function(url, action, msg) { + return new Notice('warning', "Error " + action + " archive data from\n" + url + "\n" + msg, 20); + }; + load = function(i) { + return function() { + var response; + if (this.status !== 200) { + return fail(urls[i], 'fetching', (this.status ? "Error " + this.statusText + " (" + this.status + ")" : 'Connection Error')); + } + response = this.response; + if (!(response instanceof Array)) { + response = [response]; + } + responses[i] = response; + nloaded++; + if (nloaded === urls.length) { + return Redirect.parse(responses, cb); + } + }; + }; + if (urls.length) { + for (i = k = 0, len1 = urls.length; k < len1; i = ++k) { + url = urls[i]; + if ((ref1 = url[0]) === '[' || ref1 === '{') { + try { + response = JSON.parse(url); + } catch (error) { + err = error; + fail(url, 'parsing', err.message); + continue; + } + load(i).call({ + status: 200, + response: response + }); + } else { + CrossOrigin.ajax(url, { + onloadend: load(i) + }); + } + } + } else { + Redirect.parse([], cb); + } + }, + parse: function(responses, cb) { + var archiveUIDs, archives, data, items, j, k, len, len1, ref, response, uid; + archives = []; + archiveUIDs = $.dict(); + for (j = 0, len = responses.length; j < len; j++) { + response = responses[j]; + for (k = 0, len1 = response.length; k < len1; k++) { + data = response[k]; + uid = JSON.stringify((ref = data.uid) != null ? ref : data.name); + if (uid in archiveUIDs) { + $.extend(archiveUIDs[uid], data); + } else { + archiveUIDs[uid] = $.dict.clone(data); + archives.push(data); + } + } } items = { archives: archives, @@ -6368,7 +9091,7 @@ Redirect = (function() { protocol: function(archive) { var protocol; protocol = location.protocol; - if (!archive[protocol.slice(0, -1)]) { + if (!$.getOwn(archive, protocol.slice(0, -1))) { protocol = protocol === 'https:' ? 'http:' : 'https:'; } return protocol + "//"; @@ -6398,6 +9121,16 @@ Redirect = (function() { file: function(archive, arg) { var boardID, filename; boardID = arg.boardID, filename = arg.filename; + if (!filename) { + return ''; + } + if (boardID === 'f') { + filename = encodeURIComponent($.unescape(decodeURIComponent(filename))); + } else { + if (/[sm]\.jpg$/.test(filename)) { + return ''; + } + } return "" + (Redirect.protocol(archive)) + archive.domain + "/" + boardID + "/full_image/" + filename; }, board: function(archive, arg) { @@ -6410,9 +9143,10 @@ Redirect = (function() { boardID = arg.boardID, type = arg.type, value = arg.value; type = type === 'name' ? 'username' : type === 'MD5' ? 'image' : type; if (type === 'capcode') { - value = { - 'Developer': 'dev' - }[value] || value.toLowerCase(); + value = $.getOwn({ + 'Developer': 'dev', + 'Verified': 'ver' + }, value) || value.toLowerCase(); } else if (type === 'image') { value = value.replace(/[+\/=]/g, function(c) { return { @@ -6426,6 +9160,19 @@ Redirect = (function() { path = archive.software === 'foolfuuka' ? boardID + "/search/" + type + "/" + value + "/" : type === 'image' ? boardID + "/image/" + value : boardID + "/?task=search2&search_" + type + "=" + value; return "" + (Redirect.protocol(archive)) + archive.domain + "/" + path; }, + report: function(boardID) { + var archive, boards, domain, https, j, len, name, ref, reports, software, urls; + urls = []; + ref = Conf['archives']; + for (j = 0, len = ref.length; j < len; j++) { + archive = ref[j]; + software = archive.software, https = archive.https, reports = archive.reports, boards = archive.boards, name = archive.name, domain = archive.domain; + if (software === 'foolfuuka' && https && reports && boards instanceof Array && indexOf.call(boards, boardID) >= 0) { + urls.push([name, "https://" + domain + "/_/api/chan/offsite_report/"]); + } + } + return urls; + }, securityCheck: function(url) { return /^https:\/\//.test(url) || location.protocol === 'http:' || Conf['Exempt Archives from Encryption']; }, @@ -6452,50 +9199,10 @@ Anonymize = (function() { Anonymize = { init: function() { - var ref; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Anonymize'])) { - return; - } - if (g.VIEW === 'archive') { - return this.archive(); - } - return Callbacks.Post.push({ - name: 'Anonymize', - cb: this.node - }); - }, - node: function() { - var email, name, ref, tripcode; - if (this.info.capcode || this.isClone) { + if (!Conf['Anonymize']) { return; } - ref = this.nodes, name = ref.name, tripcode = ref.tripcode, email = ref.email; - if (this.info.name !== 'Anonymous') { - name.textContent = 'Anonymous'; - } - if (tripcode) { - $.rm(tripcode); - delete this.nodes.tripcode; - } - if (email) { - $.replace(email, name); - return delete this.nodes.email; - } - }, - archive: function() { - return $.ready(function() { - var i, j, len, len1, name, ref, ref1, trip; - ref = $$('.name'); - for (i = 0, len = ref.length; i < len; i++) { - name = ref[i]; - name.textContent = 'Anonymous'; - } - ref1 = $$('.postertrip'); - for (j = 0, len1 = ref1.length; j < len1; j++) { - trip = ref1[j]; - $.rm(trip); - } - }); + return $.addClass(doc, 'anonymize'); } }; @@ -6505,48 +9212,59 @@ Anonymize = (function() { Filter = (function() { var Filter, - indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, + slice = [].slice; Filter = { - filters: {}, + filters: $.dict(), init: function() { - var boards, err, excludes, filter, hl, i, key, len, line, op, ref, ref1, ref2, ref3, ref4, ref5, ref6, regexp, stub, top; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Filter'])) { + var base, base1, boards, err, excludes, file, filter, hide, hl, i, isstring, j, key, len, len1, line, mask, noti, op, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, regexp, stub, top, type, types; + if (!(((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'catalog') && Conf['Filter'])) { + return; + } + if (g.VIEW === 'catalog' && !Conf['Filter in Native Catalog']) { return; } if (!Conf['Filtered Backlinks']) { $.addClass(doc, 'hide-backlinks'); } for (key in Config.filter) { - this.filters[key] = []; ref1 = Conf[key].split('\n'); for (i = 0, len = ref1.length; i < len; i++) { line = ref1[i]; if (line[0] === '#') { continue; } - if (!(regexp = line.match(/\/(.+)\/(\w*)/))) { + if (!(regexp = line.match(/\/(.*)\/(\w*)/))) { continue; } filter = line.replace(regexp[0], ''); - boards = ((ref2 = filter.match(/boards:([^;]+)/)) != null ? ref2[1].toLowerCase() : void 0) || 'global'; - boards = boards === 'global' ? null : boards.split(','); - excludes = boards === null ? ((ref3 = filter.match(/exclude:([^;]+)/)) != null ? ref3[1].toLowerCase().split(',') : void 0) || null : null; - if (key === 'uniqueID' || key === 'MD5') { + boards = this.parseBoards((ref2 = filter.match(/(?:^|;)\s*boards:([^;]+)/)) != null ? ref2[1] : void 0); + excludes = this.parseBoards((ref3 = filter.match(/(?:^|;)\s*exclude:([^;]+)/)) != null ? ref3[1] : void 0); + if ((isstring = (key === 'uniqueID' || key === 'MD5'))) { regexp = regexp[1]; } else { try { regexp = RegExp(regexp[1], regexp[2]); - } catch (_error) { - err = _error; + } catch (error) { + err = error; new Notice('warning', [$.tn("Invalid " + key + " filter:"), $.el('br'), $.tn(line), $.el('br'), $.tn(err.message)], 60); continue; } } - op = ((ref4 = filter.match(/[^t]op:(yes|no|only)/)) != null ? ref4[1] : void 0) || 'yes'; + op = ((ref4 = filter.match(/(?:^|;)\s*op:(no|only)/)) != null ? ref4[1] : void 0) || ''; + mask = $.getOwn({ + 'no': 1, + 'only': 2 + }, op) || 0; + file = ((ref5 = filter.match(/(?:^|;)\s*file:(no|only)/)) != null ? ref5[1] : void 0) || ''; + mask = mask | ($.getOwn({ + 'no': 4, + 'only': 8 + }, file) || 0); stub = (function() { - var ref5; - switch ((ref5 = filter.match(/stub:(yes|no)/)) != null ? ref5[1] : void 0) { + var ref6; + switch ((ref6 = filter.match(/(?:^|;)\s*stub:(yes|no)/)) != null ? ref6[1] : void 0) { case 'yes': return true; case 'no': @@ -6555,146 +9273,416 @@ Filter = (function() { return Conf['Stubs']; } })(); - if (hl = /highlight/.test(filter)) { - hl = ((ref5 = filter.match(/highlight:([\w-]+)/)) != null ? ref5[1] : void 0) || 'filter-highlight'; - top = ((ref6 = filter.match(/top:(yes|no)/)) != null ? ref6[1] : void 0) || 'yes'; + noti = /(?:^|;)\s*notify/.test(filter); + if ((hl = /(?:^|;)\s*highlight/.test(filter))) { + hl = ((ref6 = filter.match(/(?:^|;)\s*highlight:([\w-]+)/)) != null ? ref6[1] : void 0) || 'filter-highlight'; + top = ((ref7 = filter.match(/(?:^|;)\s*top:(yes|no)/)) != null ? ref7[1] : void 0) || 'yes'; top = top === 'yes'; } - this.filters[key].push(this.createFilter(regexp, boards, excludes, op, stub, hl, top)); - } - if (!this.filters[key].length) { - delete this.filters[key]; + if (key === 'general') { + if ((types = filter.match(/(?:^|;)\s*type:([^;]*)/))) { + types = types[1].split(','); + } else { + types = ['subject', 'name', 'filename', 'comment']; + } + } + hide = !(hl || noti); + filter = { + isstring: isstring, + regexp: regexp, + boards: boards, + excludes: excludes, + mask: mask, + hide: hide, + stub: stub, + hl: hl, + top: top, + noti: noti + }; + if (key === 'general') { + for (j = 0, len1 = types.length; j < len1; j++) { + type = types[j]; + ((base = this.filters)[type] || (base[type] = [])).push(filter); + } + } else { + ((base1 = this.filters)[key] || (base1[key] = [])).push(filter); + } } } if (!Object.keys(this.filters).length) { return; } - return Callbacks.Post.push({ - name: 'Filter', - cb: this.node - }); + if (g.VIEW === 'catalog') { + return Filter.catalog(); + } else { + return Callbacks.Post.push({ + name: 'Filter', + cb: this.node + }); + } }, - createFilter: function(regexp, boards, excludes, op, stub, hl, top) { - var settings, test; - test = typeof regexp === 'string' ? function(value) { - return regexp === value; - } : function(value) { - return regexp.test(value); - }; - settings = { - hide: !hl, - stub: stub, - "class": hl, - top: top - }; - return function(value, boardID, isReply) { - if (boards && indexOf.call(boards, boardID) < 0) { - return false; - } - if (excludes && indexOf.call(excludes, boardID) >= 0) { - return false; - } - if (isReply && op === 'only' || !isReply && op === 'no') { - return false; - } - if (!test(value)) { - return false; + parseBoards: function(boardsRaw) { + var boardID, boardID2, boards, i, j, len, len1, ref, ref1, ref2, ref3, site, siteFilter, siteID; + if (!boardsRaw) { + return false; + } + if ((boards = Filter.parseBoardsMemo[boardsRaw])) { + return boards; + } + boards = $.dict(); + siteFilter = ''; + ref = boardsRaw.split(','); + for (i = 0, len = ref.length; i < len; i++) { + boardID = ref[i]; + if (indexOf.call(boardID, ':') >= 0) { + ref1 = boardID.split(':').slice(-2), siteFilter = ref1[0], boardID = ref1[1]; + } + ref2 = g.sites; + for (siteID in ref2) { + site = ref2[siteID]; + if (siteID.slice(0, siteFilter.length) === siteFilter) { + if (boardID === 'nsfw' || boardID === 'sfw') { + ref3 = (typeof site.sfwBoards === "function" ? site.sfwBoards(boardID === 'sfw') : void 0) || []; + for (j = 0, len1 = ref3.length; j < len1; j++) { + boardID2 = ref3[j]; + boards[siteID + "/" + boardID2] = true; + } + } else { + boards[siteID + "/" + (encodeURIComponent(boardID))] = true; + } + } } - return settings; - }; + } + Filter.parseBoardsMemo[boardsRaw] = boards; + return boards; }, - node: function() { - var filter, i, key, len, ref, ref1, result, value; - if (this.isClone) { - return; + parseBoardsMemo: $.dict(), + test: function(post, hideable) { + var board, filter, hide, hl, i, j, key, len, len1, mask, noti, ref, ref1, ref2, site, stub, top, value; + if (hideable == null) { + hideable = true; } + if (post.filterResults) { + return post.filterResults; + } + hide = false; + stub = true; + hl = void 0; + top = false; + noti = false; + if (QuoteYou.isYou(post)) { + hideable = false; + } + mask = (post.isReply ? 2 : 1); + mask = mask | (post.file ? 4 : 8); + board = post.siteID + "/" + post.boardID; + site = post.siteID + "/*"; for (key in Filter.filters) { - if ((value = Filter[key](this)) != null) { - ref = Filter.filters[key]; - for (i = 0, len = ref.length; i < len; i++) { - filter = ref[i]; - if (!(result = filter(value, this.board.ID, this.isReply))) { + ref = Filter.values(key, post); + for (i = 0, len = ref.length; i < len; i++) { + value = ref[i]; + ref1 = Filter.filters[key]; + for (j = 0, len1 = ref1.length; j < len1; j++) { + filter = ref1[j]; + if ((filter.boards && !(filter.boards[board] || filter.boards[site])) || (filter.excludes && (filter.excludes[board] || filter.excludes[site])) || (filter.mask & mask) || (filter.isstring ? filter.regexp !== value : !filter.regexp.test(value))) { continue; } - if (result.hide && !this.isFetchedQuote) { - if (this.isReply) { - PostHiding.hide(this, result.stub); - } else if (g.VIEW === 'index') { - ThreadHiding.hide(this.thread, result.stub); - } else { - continue; + if (filter.hide) { + if (hideable) { + hide = true; + stub && (stub = filter.stub); + } + } else { + if (!(hl && (ref2 = filter.hl, indexOf.call(hl, ref2) >= 0))) { + (hl || (hl = [])).push(filter.hl); + } + top || (top = filter.top); + if (filter.noti) { + noti = true; } - return; - } - $.addClass(this.nodes.root, result["class"]); - if (!(this.highlights && (ref1 = result["class"], indexOf.call(this.highlights, ref1) >= 0))) { - (this.highlights || (this.highlights = [])).push(result["class"]); - } - if (!this.isReply && result.top) { - this.thread.isOnTop = true; } } } } + if (hide) { + return { + hide: hide, + stub: stub + }; + } else { + return { + hl: hl, + top: top, + noti: noti + }; + } }, - isHidden: function(post) { - var filter, i, key, len, ref, result, value; - for (key in Filter.filters) { - if ((value = Filter[key](post)) != null) { - ref = Filter.filters[key]; - for (i = 0, len = ref.length; i < len; i++) { - filter = ref[i]; - if (result = filter(value, post.boardID, post.isReply)) { - if (result.hide) { - return true; - } - } - } + node: function() { + var hide, hl, noti, ref, stub, top; + if (this.isClone) { + return; + } + ref = Filter.test(this, !this.isFetchedQuote && (this.isReply || g.VIEW === 'index')), hide = ref.hide, stub = ref.stub, hl = ref.hl, top = ref.top, noti = ref.noti; + if (hide) { + if (this.isReply) { + PostHiding.hide(this, stub); + } else { + ThreadHiding.hide(this.thread, stub); + } + } else { + if (hl) { + this.highlights = hl; + $.addClass.apply($, [this.nodes.root].concat(slice.call(hl))); } } - return false; + if (noti && Unread.posts && (this.ID > Unread.lastReadPost) && !QuoteYou.isYou(this)) { + return Unread.openNotification(this, ' triggered a notification filter'); + } }, - postID: function(post) { - var ref; - return "" + ((ref = post.ID) != null ? ref : post.postID); + catalog: function() { + var base, url; + if (!(url = typeof (base = g.SITE.urls).catalogJSON === "function" ? base.catalogJSON(g.BOARD) : void 0)) { + return; + } + Filter.catalogData = $.dict(); + $.ajax(url, { + onloadend: Filter.catalogParse + }); + return Callbacks.CatalogThreadNative.push({ + name: 'Filter', + cb: this.catalogNode + }); }, - name: function(post) { - return post.info.name; + catalogParse: function() { + var i, item, j, len, len1, page, ref, ref1, ref2; + if ((ref = this.status) !== 200 && ref !== 404) { + new Notice('warning', "Failed to fetch catalog JSON data. " + (this.status ? "Error " + this.statusText + " (" + this.status + ")" : 'Connection Error'), 1); + return; + } + ref1 = this.response; + for (i = 0, len = ref1.length; i < len; i++) { + page = ref1[i]; + ref2 = page.threads; + for (j = 0, len1 = ref2.length; j < len1; j++) { + item = ref2[j]; + Filter.catalogData[item.no] = item; + } + } + g.BOARD.threads.forEach(function(thread) { + if (thread.catalogViewNative) { + return Filter.catalogNode.call(thread.catalogViewNative); + } + }); }, - uniqueID: function(post) { - return post.info.uniqueID; + catalogNode: function() { + var base, hide, hl, ref, ref1, top; + if (!(this.boardID === g.BOARD.ID && Filter.catalogData[this.ID])) { + return; + } + if ((ref = QuoteYou.db) != null ? ref.get({ + siteID: g.SITE.ID, + boardID: this.boardID, + threadID: this.ID, + postID: this.ID + }) : void 0) { + return; + } + ref1 = Filter.test(g.SITE.Build.parseJSON(Filter.catalogData[this.ID], this)), hide = ref1.hide, hl = ref1.hl, top = ref1.top; + if (hide) { + return this.nodes.root.hidden = true; + } else { + if (hl) { + this.highlights = hl; + $.addClass.apply($, [this.nodes.root].concat(slice.call(hl))); + } + if (top) { + $.prepend(this.nodes.root.parentNode, this.nodes.root); + return typeof (base = g.SITE).catalogPin === "function" ? base.catalogPin(this.nodes.root) : void 0; + } + } }, - tripcode: function(post) { - return post.info.tripcode; + isHidden: function(post) { + return !!Filter.test(post).hide; }, - capcode: function(post) { - return post.info.capcode; + valueF: { + postID: function(post) { + return ["" + post.ID]; + }, + name: function(post) { + return [post.info.name]; + }, + uniqueID: function(post) { + return [post.info.uniqueID || '']; + }, + tripcode: function(post) { + return [post.info.tripcode]; + }, + capcode: function(post) { + return [post.info.capcode]; + }, + pass: function(post) { + return [post.info.pass]; + }, + email: function(post) { + return [post.info.email]; + }, + subject: function(post) { + return [post.info.subject || (post.isReply ? void 0 : '')]; + }, + comment: function(post) { + var base, ref, ref1; + return [((base = post.info).comment != null ? base.comment : base.comment = (ref = g.sites[post.siteID]) != null ? (ref1 = ref.Build) != null ? typeof ref1.parseComment === "function" ? ref1.parseComment(post.info.commentHTML.innerHTML) : void 0 : void 0 : void 0)]; + }, + flag: function(post) { + return [post.info.flag]; + }, + filename: function(post) { + return post.files.map(function(f) { + return f.name; + }); + }, + dimensions: function(post) { + return post.files.map(function(f) { + return f.dimensions; + }); + }, + filesize: function(post) { + return post.files.map(function(f) { + return f.size; + }); + }, + MD5: function(post) { + return post.files.map(function(f) { + return f.MD5; + }); + } }, - subject: function(post) { - return post.info.subject; + values: function(key, post) { + if ($.hasOwn(Filter.valueF, key)) { + return Filter.valueF[key](post).filter(function(v) { + return v != null; + }); + } else { + return [ + key.split('+').map(function(k) { + var f; + if ((f = $.getOwn(Filter.valueF, k))) { + return f(post).map(function(v) { + return v || ''; + }).join('\n'); + } else { + return ''; + } + }).join('\n') + ]; + } }, - comment: function(post) { - var base; - return (base = post.info).comment != null ? base.comment : base.comment = Build.parseComment(post.info.commentHTML.innerHTML); + addFilter: function(type, re, cb) { + if (!$.hasOwn(Config.filter, type)) { + return; + } + return $.get(type, Conf[type], function(item) { + var save; + save = item[type]; + save = save ? save + "\n" + re : re; + return $.set(type, save, cb); + }); }, - flag: function(post) { - return post.info.flag; + removeFilters: function(type, res, cb) { + return $.get(type, Conf[type], function(item) { + var save; + save = item[type]; + res = res.map(Filter.escape).join('|'); + save = save.replace(RegExp("(?:$\n|^)(?:" + res + ")$", 'mg'), ''); + return $.set(type, save, cb); + }); }, - filename: function(post) { - var ref; - return (ref = post.file) != null ? ref.name : void 0; + showFilters: function(type) { + var section, select; + Settings.open('Filter'); + section = $('.section-container'); + select = $('select[name=filter]', section); + select.value = type; + Settings.selectFilter.call(select); + return $.onExists(section, 'textarea', function(ta) { + var tl; + tl = ta.textLength; + ta.setSelectionRange(tl, tl); + return ta.focus(); + }); }, - dimensions: function(post) { - var ref; - return (ref = post.file) != null ? ref.dimensions : void 0; + quickFilterMD5: function() { + var files, filter, links, msg, notice, origin, post; + post = Get.postFromNode(this); + files = post.files.filter(function(f) { + return f.MD5; + }); + if (!files.length) { + return; + } + filter = files.map(function(f) { + return "/" + f.MD5 + "/"; + }).join('\n'); + Filter.addFilter('MD5', filter); + origin = post.origin || post; + if (origin.isReply) { + PostHiding.hide(origin); + } else if (g.VIEW === 'index') { + ThreadHiding.hide(origin.thread); + } + if (!Conf['MD5 Quick Filter Notifications']) { + if (post.nodes.post.getBoundingClientRect().height) { + new Notice('info', 'MD5 filtered.', 2); + } + return; + } + notice = Filter.quickFilterMD5.notice; + if (notice) { + notice.filters.push(filter); + notice.posts.push(origin); + return $('span', notice.el).textContent = notice.filters.length + " MD5s filtered."; + } else { + msg = $.el('div', {innerHTML: "MD5 filtered. [show] [undo]"}); + notice = Filter.quickFilterMD5.notice = new Notice('info', msg, void 0, function() { + return delete Filter.quickFilterMD5.notice; + }); + notice.filters = [filter]; + notice.posts = [origin]; + links = $$('a', msg); + $.on(links[0], 'click', Filter.quickFilterCB.show.bind(notice)); + return $.on(links[1], 'click', Filter.quickFilterCB.undo.bind(notice)); + } }, - filesize: function(post) { - var ref; - return (ref = post.file) != null ? ref.size : void 0; + quickFilterCB: { + show: function() { + Filter.showFilters('MD5'); + return this.close(); + }, + undo: function() { + var i, len, post, ref; + Filter.removeFilters('MD5', this.filters); + ref = this.posts; + for (i = 0, len = ref.length; i < len; i++) { + post = ref[i]; + if (post.isReply) { + PostHiding.show(post); + } else if (g.VIEW === 'index') { + ThreadHiding.show(post.thread); + } + } + return this.close(); + } }, - MD5: function(post) { - var ref; - return (ref = post.file) != null ? ref.MD5 : void 0; + escape: function(value) { + return value.replace(/\/|\\|\^|\$|\n|\.|\(|\)|\{|\}|\[|\]|\?|\*|\+|\|/g, function(c) { + if (c === '\n') { + return '\\n'; + } else if (c === '\\') { + return '\\\\'; + } else { + return "\\" + c; + } + }); }, menu: { init: function() { @@ -6714,7 +9702,7 @@ Filter = (function() { }, subEntries: [] }; - ref1 = [['Name', 'name'], ['Unique ID', 'uniqueID'], ['Tripcode', 'tripcode'], ['Capcode', 'capcode'], ['Subject', 'subject'], ['Comment', 'comment'], ['Flag', 'flag'], ['Filename', 'filename'], ['Image dimensions', 'dimensions'], ['Filesize', 'filesize'], ['Image MD5', 'MD5']]; + ref1 = [['Name', 'name'], ['Unique ID', 'uniqueID'], ['Tripcode', 'tripcode'], ['Capcode', 'capcode'], ['Pass Date', 'pass'], ['Email', 'email'], ['Subject', 'subject'], ['Comment', 'comment'], ['Flag', 'flag'], ['Filename', 'filename'], ['Image dimensions', 'dimensions'], ['Filesize', 'filesize'], ['Image MD5', 'MD5']]; for (i = 0, len = ref1.length; i < len; i++) { type = ref1[i]; entry.subEntries.push(Filter.menu.createSubEntry(type[0], type[1])); @@ -6732,40 +9720,25 @@ Filter = (function() { return { el: el, open: function(post) { - var value; - value = Filter[type](post); - return (value != null) && !(g.BOARD.ID === 'f' && type === 'MD5'); + return Filter.values(type, post).length; } }; }, makeFilter: function() { - var re, type, value; + var res, type, values; type = this.dataset.type; - value = Filter[type](Filter.menu.post); - re = type === 'uniqueID' || type === 'MD5' ? value : value.replace(/\/|\\|\^|\$|\n|\.|\(|\)|\{|\}|\[|\]|\?|\*|\+|\|/g, function(c) { - if (c === '\n') { - return '\\n'; - } else if (c === '\\') { - return '\\\\'; + values = Filter.values(type, Filter.menu.post); + res = values.map(function(value) { + var re; + re = type === 'uniqueID' || type === 'MD5' ? value : Filter.escape(value); + if (type === 'uniqueID' || type === 'MD5') { + return "/" + re + "/"; } else { - return "\\" + c; + return "/^" + re + "$/"; } - }); - re = type === 'uniqueID' || type === 'MD5' ? "/" + re + "/" : "/^" + re + "$/"; - return $.get(type, Conf[type], function(item) { - var save, section, select, ta, tl; - save = item[type]; - save = save ? save + "\n" + re : re; - $.set(type, save); - Settings.open('Filter'); - section = $('.section-container'); - select = $('select[name=filter]', section); - select.value = type; - Settings.selectFilter.call(select); - ta = $('textarea', section); - tl = ta.textLength; - ta.setSelectionRange(tl, tl); - return ta.focus(); + }).join('\n'); + return Filter.addFilter(type, res, function() { + return Filter.showFilters(type); }); } } @@ -6793,8 +9766,15 @@ PostHiding = (function() { cb: this.node }); }, + isHidden: function(boardID, threadID, postID) { + return !!(PostHiding.db && PostHiding.db.get({ + boardID: boardID, + threadID: threadID, + postID: postID + })); + }, node: function() { - var data, sideArrows; + var button, data, sa, sideArrows; if (!this.isReply || this.isClone || this.isFetchedQuote) { return; } @@ -6813,9 +9793,14 @@ PostHiding = (function() { if (!Conf['Reply Hiding Buttons']) { return; } - sideArrows = $('.sideArrows', this.nodes.root); - $.replace(sideArrows.firstChild, PostHiding.makeButton(this, 'hide')); - return sideArrows.removeAttribute('class'); + button = PostHiding.makeButton(this, 'hide'); + if ((sa = g.SITE.selectors.sideArrows)) { + sideArrows = $(sa, this.nodes.root); + $.replace(sideArrows.firstChild, button); + return sideArrows.className = 'replacedSideArrows'; + } else { + return $.prepend(this.nodes.info, button); + } }, menu: { init: function() { @@ -7086,7 +10071,7 @@ Recursive = (function() { indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; Recursive = { - recursives: {}, + recursives: $.dict(), init: function() { var ref; if ((ref = g.VIEW) !== 'index' && ref !== 'thread') { @@ -7169,6 +10154,10 @@ ThreadHiding = (function() { return this.catalogWatch(); } this.catalogSet(g.BOARD); + $.on(d, 'IndexRefreshInternal', this.onIndexRefresh); + if (Conf['Thread Hiding Buttons']) { + $.addClass(doc, 'thread-hide'); + } return Callbacks.Post.push({ name: 'Thread Hiding', cb: this.node @@ -7176,12 +10165,12 @@ ThreadHiding = (function() { }, catalogSet: function(board) { var hiddenThreads, threadID; - if (!$.hasStorage) { + if (!($.hasStorage && g.SITE.software === 'yotsuba')) { return; } hiddenThreads = ThreadHiding.db.get({ boardID: board.ID, - defaultValue: {} + defaultValue: $.dict() }); for (threadID in hiddenThreads) { hiddenThreads[threadID] = true; @@ -7189,7 +10178,7 @@ ThreadHiding = (function() { return localStorage.setItem("4chan-hide-t-" + board, JSON.stringify(hiddenThreads)); }, catalogWatch: function() { - if (!$.hasStorage) { + if (!($.hasStorage && g.SITE.software === 'yotsuba')) { return; } this.hiddenThreads = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; @@ -7205,7 +10194,7 @@ ThreadHiding = (function() { var hiddenThreads2, threadID; hiddenThreads2 = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; for (threadID in hiddenThreads2) { - if (!(threadID in ThreadHiding.hiddenThreads)) { + if (!$.hasOwn(ThreadHiding.hiddenThreads, threadID)) { ThreadHiding.db.set({ boardID: g.BOARD.ID, threadID: threadID, @@ -7216,7 +10205,7 @@ ThreadHiding = (function() { } } for (threadID in ThreadHiding.hiddenThreads) { - if (!(threadID in hiddenThreads2)) { + if (!$.hasOwn(hiddenThreads2, threadID)) { ThreadHiding.db["delete"]({ boardID: g.BOARD.ID, threadID: threadID @@ -7225,31 +10214,35 @@ ThreadHiding = (function() { } return ThreadHiding.hiddenThreads = hiddenThreads2; }, + isHidden: function(boardID, threadID) { + return !!(ThreadHiding.db && ThreadHiding.db.get({ + boardID: boardID, + threadID: threadID + })); + }, node: function() { var data; if (this.isReply || this.isClone || this.isFetchedQuote) { return; } + if (Conf['Thread Hiding Buttons']) { + $.prepend(this.nodes.root, ThreadHiding.makeButton(this.thread, 'hide')); + } if (data = ThreadHiding.db.get({ boardID: this.board.ID, threadID: this.ID })) { - ThreadHiding.hide(this.thread, data.makeStub); + return ThreadHiding.hide(this.thread, data.makeStub); } - if (!Conf['Thread Hiding Buttons']) { - return; - } - return $.prepend(this.nodes.root, ThreadHiding.makeButton(this.thread, 'hide')); }, - onIndexBuild: function(nodes) { - var i, len, root, thread; - for (i = 0, len = nodes.length; i < len; i++) { - root = nodes[i]; - thread = Get.threadFromRoot(root); + onIndexRefresh: function() { + return g.BOARD.threads.forEach(function(thread) { + var root; + root = thread.nodes.root; if (thread.isHidden && thread.stub && !root.contains(thread.stub)) { - ThreadHiding.makeStub(thread, root); + return ThreadHiding.makeStub(thread, root); } - } + }); }, menu: { init: function() { @@ -7354,17 +10347,15 @@ ThreadHiding = (function() { className: type + "-thread-button", href: 'javascript:;' }); - $.extend(a, { - innerHTML: "" - }); + $.extend(a, {innerHTML: ""}); a.dataset.fullID = thread.fullID; $.on(a, 'click', ThreadHiding.toggle); return a; }, makeStub: function(thread, root) { - var a, numReplies, summary; - numReplies = $$('.thread > .replyContainer', root).length; - if (summary = $('.summary', root)) { + var a, numReplies, summary, threadDivider; + numReplies = $$(g.SITE.selectors.replyOriginal, root).length; + if (summary = $(g.SITE.selectors.summary, root)) { numReplies += +summary.textContent.match(/\d+/); } a = ThreadHiding.makeButton(thread, 'show'); @@ -7377,7 +10368,10 @@ ThreadHiding = (function() { } else { $.add(thread.stub, a); } - return $.prepend(root, thread.stub); + $.prepend(root, thread.stub); + if ((threadDivider = $(g.SITE.selectors.threadDivider, root))) { + return $.addClass(threadDivider, 'threadDivider'); + } }, saveHiddenState: function(thread, makeStub) { if (thread.isHidden) { @@ -7398,7 +10392,7 @@ ThreadHiding = (function() { }, toggle: function(thread) { if (!(thread instanceof Thread)) { - thread = g.threads[this.dataset.fullID]; + thread = g.threads.get(this.dataset.fullID); } if (thread.isHidden) { ThreadHiding.show(thread); @@ -7415,10 +10409,12 @@ ThreadHiding = (function() { if (thread.isHidden) { return; } - threadRoot = thread.OP.nodes.root.parentNode; + threadRoot = thread.nodes.root; thread.isHidden = true; - if (Conf['JSON Index']) { - Index.updateHideLabel(); + Index.updateHideLabel(); + if (thread.catalogView && !Index.showHiddenThreads) { + $.rm(thread.catalogView.nodes.root); + $.event('PostsRemoved', null, Index.root); } if (!makeStub) { return threadRoot.hidden = true; @@ -7431,10 +10427,12 @@ ThreadHiding = (function() { $.rm(thread.stub); delete thread.stub; } - threadRoot = thread.OP.nodes.root.parentNode; + threadRoot = thread.nodes.root; threadRoot.hidden = thread.isHidden = false; - if (Conf['JSON Index']) { - return Index.updateHideLabel(); + Index.updateHideLabel(); + if (thread.catalogView && Index.showHiddenThreads) { + $.rm(thread.catalogView.nodes.root); + return $.event('PostsRemoved', null, Index.root); } } }; @@ -7443,375 +10441,178 @@ ThreadHiding = (function() { }).call(this); -Build = (function() { - var Build, - slice = [].slice; +BoardConfig = (function() { + var BoardConfig; - Build = { - staticPath: '//s.4cdn.org/image/', - gifIcon: window.devicePixelRatio >= 2 ? '@2x.gif' : '.gif', - spoilerRange: {}, - unescape: function(text) { - if (text == null) { - return text; - } - return text.replace(/<[^>]*>/g, '').replace(/&(amp|#039|quot|lt|gt|#44);/g, function(c) { - return { - '&': '&', - ''': "'", - '"': '"', - '<': '<', - '>': '>', - ',': ',' - }[c]; - }); - }, - shortFilename: function(filename) { - var ext; - ext = filename.match(/\.?[^\.]*$/)[0]; - if (filename.length - ext.length > 30) { - return (filename.match(/(?:[\uD800-\uDBFF][\uDC00-\uDFFF]|[^]){0,25}/)[0]) + "(...)" + ext; - } else { - return filename; - } - }, - spoilerThumb: function(boardID) { - var spoilerRange; - if (spoilerRange = Build.spoilerRange[boardID]) { - return Build.staticPath + "spoiler-" + boardID + (Math.floor(1 + spoilerRange * Math.random())) + ".png"; - } else { - return Build.staticPath + "spoiler.png"; + BoardConfig = { + cbs: [], + init: function() { + var boards, now, ref; + if (g.SITE.software !== 'yotsuba') { + return; } - }, - sameThread: function(boardID, threadID) { - return g.VIEW === 'thread' && g.BOARD.ID === boardID && g.THREADID === +threadID; - }, - postURL: function(boardID, threadID, postID) { - if (Build.sameThread(boardID, threadID)) { - return "#p" + postID; + now = Date.now(); + if (!((now - 2 * $.HOUR < (ref = Conf['boardConfig'].lastChecked || 0) && ref <= now))) { + return $.ajax(location.protocol + "//a.4cdn.org/boards.json", { + onloadend: this.load + }); } else { - return "/" + boardID + "/thread/" + threadID + "#p" + postID; + boards = Conf['boardConfig'].boards; + return this.set(boards); } }, - parseJSON: function(data, boardID) { - var o; - o = { - postID: data.no, - threadID: data.resto || data.no, - boardID: boardID, - isReply: !!data.resto, - isSticky: !!data.sticky, - isClosed: !!data.closed, - isArchived: !!data.archived, - fileDeleted: !!data.filedeleted - }; - o.info = { - subject: Build.unescape(data.sub), - email: Build.unescape(data.email), - name: Build.unescape(data.name) || '', - tripcode: data.trip, - uniqueID: data.id, - flagCode: data.country, - flag: Build.unescape(data.country_name), - dateUTC: data.time, - dateText: data.now, - commentHTML: { - innerHTML: data.com || '' + load: function() { + var board, boards, err, i, len, ref; + if (this.status === 200 && this.response && this.response.boards) { + boards = $.dict(); + ref = this.response.boards; + for (i = 0, len = ref.length; i < len; i++) { + board = ref[i]; + boards[board.board] = board; } - }; - if (data.capcode) { - o.info.capcode = data.capcode.replace(/_highlight$/, '').replace(/_/g, ' ').replace(/\b\w/g, function(c) { - return c.toUpperCase(); + $.set('boardConfig', { + boards: boards, + lastChecked: Date.now() }); - o.capcodeHighlight = /_highlight$/.test(data.capcode); - delete o.info.uniqueID; - } - if (data.ext) { - o.file = { - name: (Build.unescape(data.filename)) + data.ext, - url: boardID === 'f' ? location.protocol + "//i.4cdn.org/" + boardID + "/" + (encodeURIComponent(data.filename)) + data.ext : location.protocol + "//i.4cdn.org/" + boardID + "/" + data.tim + data.ext, - height: data.h, - width: data.w, - MD5: data.md5, - size: $.bytesToString(data.fsize), - thumbURL: location.protocol + "//i.4cdn.org/" + boardID + "/" + data.tim + "s.jpg", - theight: data.tn_h, - twidth: data.tn_w, - isSpoiler: !!data.spoiler, - tag: data.tag - }; - if (!/\.pdf$/.test(o.file.url)) { - o.file.dimensions = o.file.width + "x" + o.file.height; - } + } else { + boards = Conf['boardConfig'].boards; + err = (function() { + switch (this.status) { + case 0: + return 'Connection Error'; + case 200: + return 'Invalid Data'; + default: + return "Error " + this.statusText + " (" + this.status + ")"; + } + }).call(this); + new Notice('warning', "Failed to load board configuration. " + err, 20); } - return o; + return BoardConfig.set(boards); }, - parseComment: function(html) { - html = html.replace(//gi, '\n').replace(/\n\nRolled [^<]*<\/b>/i, '').replace(/]*>/g, ''); - return Build.unescape(html); - }, - postFromObject: function(data, boardID, suppressThumb) { - var o; - o = Build.parseJSON(data, boardID); - return Build.post(o, suppressThumb); - }, - post: function(o, suppressThumb) { - var boardID, capcode, capcodeDescription, capcodeLC, capcodeLong, capcodePlural, commentHTML, container, dateText, dateUTC, email, file, fileBlock, fileThumb, fileURL, flag, flagCode, gifIcon, href, i, len, match, name, postClass, postID, postInfo, postLink, protocol, quote, quoteLink, ref, ref1, shortFilename, staticPath, subject, threadID, tripcode, uniqueID, wholePost; - postID = o.postID, threadID = o.threadID, boardID = o.boardID, file = o.file; - ref = o.info, subject = ref.subject, email = ref.email, name = ref.name, tripcode = ref.tripcode, capcode = ref.capcode, uniqueID = ref.uniqueID, flagCode = ref.flagCode, flag = ref.flag, dateUTC = ref.dateUTC, dateText = ref.dateText, commentHTML = ref.commentHTML; - staticPath = Build.staticPath, gifIcon = Build.gifIcon; - - /* Post Info */ - if (capcode) { - capcodeLC = capcode.toLowerCase(); - if (capcode === 'Founder') { - capcodePlural = 'the Founder'; - capcodeDescription = "4chan's Founder"; - } else { - capcodeLong = { - 'Admin': 'Administrator', - 'Mod': 'Moderator' - }[capcode] || capcode; - capcodePlural = capcodeLong + "s"; - capcodeDescription = "a 4chan " + capcodeLong; - } - } - postLink = Build.postURL(boardID, threadID, postID); - quoteLink = Build.sameThread(boardID, threadID) ? "javascript:quote('" + (+postID) + "');" : "/" + boardID + "/thread/" + threadID + "#q" + postID; - postInfo = { - innerHTML: "
          " + ((!o.isReply || boardID === "f" || subject) ? "" + E(subject || "") + " " : "") + "" + ((email) ? "" : "") + "" + E(name) + "" + ((tripcode) ? " " + E(tripcode) + "" : "") + ((capcode) ? " ## " + E(capcode) + "" : "") + ((email) ? "" : "") + ((boardID === "f" && !o.isReply || capcode) ? "" : " ") + ((capcode) ? " \""" : "") + ((uniqueID && !capcode) ? " (ID: " + E(uniqueID) + ")" : "") + ((flagCode) ? " " : "") + " " + E(dateText) + " No." + E(postID) + "" + ((o.isSticky) ? " \"Sticky\"" : "") + ((o.isClosed && !o.isArchived) ? " \"Closed\"" : "") + ((o.isArchived) ? " \"Archived\"" : "") + ((!o.isReply && g.VIEW === "index") ? "   [Reply]" : "") + "
          " - }; - - /* File Info */ - if (file) { - protocol = /^https?:(?=\/\/i\.4cdn\.org\/)/; - fileURL = file.url.replace(protocol, ''); - shortFilename = Build.shortFilename(file.name); - fileThumb = file.isSpoiler ? Build.spoilerThumb(boardID) : file.thumbURL.replace(protocol, ''); + set: function(boards1) { + var ID, board, cb, i, len, ref, ref1; + this.boards = boards1; + ref = g.boards; + for (ID in ref) { + board = ref[ID]; + board.config = this.boards[ID] || {}; } - fileBlock = { - innerHTML: ((file) ? "
          " + ((boardID === "f") ? "
          File: " + E(file.name) + "-(" + E(file.size) + ", " + E(file.dimensions) + ((file.tag) ? ", " + E(file.tag) : "") + ")
          " : "
          File: " + ((file.isSpoiler) ? "Spoiler Image" : E(shortFilename)) + " (" + E(file.size) + ", " + E(file.dimensions || "PDF") + ")
          ") + "
          " : ((o.fileDeleted) ? "
          \"File
          " : "")) - }; - - /* Whole Post */ - postClass = o.isReply ? 'reply' : 'op'; - wholePost = { - innerHTML: ((o.isReply) ? "
          >>
          " : "") + "
          " + ((o.isReply) ? (postInfo).innerHTML + (fileBlock).innerHTML : (fileBlock).innerHTML + (postInfo).innerHTML) + "
          " + (commentHTML).innerHTML + "
          " - }; - container = $.el('div', { - className: "postContainer " + postClass + "Container", - id: "pc" + postID - }); - $.extend(container, wholePost); - ref1 = $$('.quotelink', container); + ref1 = this.cbs; for (i = 0, len = ref1.length; i < len; i++) { - quote = ref1[i]; - href = quote.getAttribute('href'); - if ((href[0] === '#') && !(Build.sameThread(boardID, threadID))) { - quote.href = ("/" + boardID + "/thread/" + threadID) + href; - } else if ((match = href.match(/^\/([^\/]+)\/thread\/(\d+)/)) && (Build.sameThread(match[1], match[2]))) { - quote.href = href.match(/(#[^#]*)?$/)[0] || '#'; - } else if (/^\d+(#|$)/.test(href) && !(g.VIEW === 'thread' && g.BOARD.ID === boardID)) { - quote.href = "/" + boardID + "/thread/" + href; - } + cb = ref1[i]; + $.queueTask(cb); } - return container; }, - summaryText: function(status, posts, files) { - var text; - text = ''; - if (status) { - text += status + " "; - } - text += posts + " post" + (posts > 1 ? 's' : ''); - if (+files) { - text += " and " + files + " image repl" + (files > 1 ? 'ies' : 'y'); + ready: function(cb) { + if (this.boards) { + return cb(); + } else { + return this.cbs.push(cb); } - return text += " " + (status === '-' ? 'shown' : 'omitted') + "."; - }, - summary: function(boardID, threadID, posts, files) { - return $.el('a', { - className: 'summary', - textContent: Build.summaryText('', posts, files), - href: "/" + boardID + "/thread/" + threadID - }); }, - thread: function(board, data) { - var OP, root; - Build.spoilerRange[board] = data.custom_spoiler; - if (OP = board.posts[data.no]) { - if (OP.isFetchedQuote) { - OP = null; + sfwBoards: function(sfw) { + var board, data, ref, results; + ref = this.boards || Conf['boardConfig'].boards; + results = []; + for (board in ref) { + data = ref[board]; + if (!!data.ws_board === sfw) { + results.push(board); } } - if (OP && (root = OP.nodes.root.parentNode)) { - $.rmAll(root); - } else { - root = $.el('div', { - className: 'thread', - id: "t" + data.no - }); - } - $.add(root, Build.excerptThread(board, data, OP)); - return root; + return results; }, - excerptThread: function(board, data, OP) { - var files, nodes, posts, ref; - nodes = [OP ? OP.nodes.root : Build.postFromObject(data, board.ID, true)]; - if (data.omitted_posts || !Conf['Show Replies'] && data.replies) { - ref = Conf['Show Replies'] ? [ - data.omitted_posts, data.images - data.last_replies.filter(function(data) { - return !!data.ext; - }).length - ] : [data.replies, data.images], posts = ref[0], files = ref[1]; - nodes.push(Build.summary(board.ID, data.no, posts, files)); - } - return nodes; + isSFW: function(board) { + var ref; + return !!((ref = (this.boards || Conf['boardConfig'].boards)[board]) != null ? ref.ws_board : void 0); }, - catalogThread: function(thread) { - var br, cc, comment, data, exif, fileCount, gifIcon, href, i, imgClass, j, k, l, len, len1, len2, len3, pageCount, postCount, pp, quote, ref, ref1, ref2, ref3, ref4, root, spoilerRange, src, staticPath; - staticPath = Build.staticPath, gifIcon = Build.gifIcon; - data = Index.liveThreadData[Index.liveThreadIDs.indexOf(thread.ID)]; - if (data.spoiler && !Conf['Reveal Spoiler Thumbnails']) { - src = staticPath + "spoiler"; - if (spoilerRange = Build.spoilerRange[thread.board]) { - src += ("-" + thread.board) + Math.floor(1 + spoilerRange * Math.random()); - } - src += '.png'; - imgClass = 'spoiler-file'; - } else if (data.filedeleted) { - src = staticPath + "filedeleted-res" + gifIcon; - imgClass = 'deleted-file'; - } else if (thread.OP.file) { - src = thread.OP.file.thumbURL; - } else { - src = staticPath + "nofile.png"; - imgClass = 'no-file'; - } - postCount = data.replies + 1; - fileCount = data.images + !!data.ext; - pageCount = Math.floor(Index.liveThreadIDs.indexOf(thread.ID) / Index.threadsNumPerPage) + 1; - comment = { - innerHTML: data.com || '' - }; - root = $.el('div', { - className: 'catalog-thread' - }); - $.extend(root, { - innerHTML: "
          " + E(postCount) + " / " + E(fileCount) + " / " + E(pageCount) + "
          " + ((thread.OP.info.subject) ? "
          " + E(thread.OP.info.subject) + "
          " : "") + "
          " + (comment).innerHTML + "
          " - }); - root.dataset.fullID = thread.fullID; - if (thread.OP.highlights) { - $.addClass.apply($, [root].concat(slice.call(thread.OP.highlights))); - } - ref = $$('.quotelink', root.lastElementChild); - for (i = 0, len = ref.length; i < len; i++) { - quote = ref[i]; - href = quote.getAttribute('href'); - if (href[0] === '#') { - quote.href = ("/" + thread.board + "/thread/" + thread.ID) + href; - } - } - ref1 = $$('.abbr, .exif', root.lastElementChild); - for (j = 0, len1 = ref1.length; j < len1; j++) { - exif = ref1[j]; - $.rm(exif); - } - ref2 = $$('.prettyprint', root.lastElementChild); - for (k = 0, len2 = ref2.length; k < len2; k++) { - pp = ref2[k]; - cc = $.el('span', { - className: 'catalog-code' - }); - $.add(cc, slice.call(pp.childNodes)); - $.replace(pp, cc); - } - ref3 = $$('br', root.lastElementChild); - for (l = 0, len3 = ref3.length; l < len3; l++) { - br = ref3[l]; - if (((ref4 = br.previousSibling) != null ? ref4.nodeName : void 0) === 'BR') { - $.rm(br); - } - } - if (thread.isSticky) { - $.add($('.catalog-icons', root), $.el('img', { - src: staticPath + "sticky" + gifIcon, - className: 'stickyIcon', - title: 'Sticky' - })); - } - if (thread.isClosed) { - $.add($('.catalog-icons', root), $.el('img', { - src: staticPath + "closed" + gifIcon, - className: 'closedIcon', - title: 'Closed' - })); - } - if (data.bumplimit) { - $.addClass($('.post-count', root), 'warning'); - } - if (data.imagelimit) { - $.addClass($('.file-count', root), 'warning'); + domain: function() { + return 'boards.4chan.org'; + }, + isArchived: function(board) { + var data; + data = (this.boards || Conf['boardConfig'].boards)[board]; + return !data || data.is_archived; + }, + noAudio: function(boardID) { + var boards; + if (g.SITE.software !== 'yotsuba') { + return false; } - return root; + boards = this.boards || Conf['boardConfig'].boards; + return boards && boards[boardID] && !boards[boardID].webm_audio; + }, + title: function(boardID) { + var ref, ref1; + return ((ref = this.boards || Conf['boardConfig'].boards) != null ? (ref1 = ref[boardID]) != null ? ref1.title : void 0 : void 0) || ''; } }; - return Build; - -}).call(this); - -(function() { - + return BoardConfig; }).call(this); Get = (function() { var Get, + slice = [].slice, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; Get = { + url: function() { + var IDs, args, f, site, type; + type = arguments[0], IDs = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; + if ((site = g.sites[IDs.siteID]) && (f = $.getOwn(site.urls, type))) { + return f.apply(null, [IDs].concat(slice.call(args))); + } else { + return void 0; + } + }, threadExcerpt: function(thread) { var OP, excerpt, ref, ref1; OP = thread.OP; - excerpt = ("/" + thread.board + "/ - ") + (((ref = OP.info.subject) != null ? ref.trim() : void 0) || OP.info.commentDisplay.replace(/\n+/g, ' // ') || ((ref1 = OP.file) != null ? ref1.name : void 0) || OP.info.nameBlock); + excerpt = ("/" + (decodeURIComponent(thread.board.ID)) + "/ - ") + (((ref = OP.info.subject) != null ? ref.trim() : void 0) || OP.commentDisplay().replace(/\n+/g, ' // ') || ((ref1 = OP.file) != null ? ref1.name : void 0) || ("No." + OP)); if (excerpt.length > 73) { return excerpt.slice(0, 70) + "..."; } return excerpt; }, threadFromRoot: function(root) { - return g.threads[g.BOARD + "." + root.id.slice(1)]; + var board; + if (root == null) { + return null; + } + board = root.dataset.board; + return g.threads.get((board ? encodeURIComponent(board) : g.BOARD.ID) + "." + (root.id.match(/\d*$/)[0])); }, threadFromNode: function(node) { - return Get.threadFromRoot($.x('ancestor::div[@class="thread"]', node)); + return Get.threadFromRoot($.x("ancestor-or-self::" + g.SITE.xpath.thread, node)); }, postFromRoot: function(root) { var index, post; if (root == null) { return null; } - post = g.posts[root.dataset.fullID]; + post = g.posts.get(root.dataset.fullID); index = root.dataset.clone; if (index) { - return post.clones[index]; + return post.clones[+index]; } else { return post; } }, postFromNode: function(root) { - return Get.postFromRoot($.x('(ancestor::div[contains(@class,"postContainer")][1]|following::div[contains(@class,"postContainer")][1])', root)); + return Get.postFromRoot($.x("ancestor-or-self::" + g.SITE.xpath.postContainer + "[1]", root)); }, postDataFromLink: function(link) { - var boardID, path, postID, ref, threadID; - if (link.hostname === 'boards.4chan.org') { - path = link.pathname.split(/\/+/); - boardID = path[1]; - threadID = path[3]; - postID = link.hash.slice(2); - } else { + var boardID, match, postID, ref, ref1, threadID; + if (link.dataset.postID) { ref = link.dataset, boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID; threadID || (threadID = 0); + } else { + match = link.href.match(g.SITE.regexp.quotelink); + ref1 = match.slice(1), boardID = ref1[0], threadID = ref1[1], postID = ref1[2]; + postID || (postID = threadID); } return { boardID: boardID, @@ -7842,7 +10643,7 @@ Get = (function() { ref = post.quotes; for (i = 0, len = ref.length; i < len; i++) { quote = ref[i]; - if (qPost = posts[quote]) { + if (qPost = posts.get(quote)) { handleQuotes(qPost, 'backlinks'); } } @@ -7852,17 +10653,6 @@ Get = (function() { ref1 = Get.postDataFromLink(quotelink), boardID = ref1.boardID, postID = ref1.postID; return boardID === post.board.ID && postID === post.ID; }); - }, - scriptData: function() { - var i, len, ref, script; - ref = $$('script:not([src])', d.head); - for (i = 0, len = ref.length; i < len; i++) { - script = ref[i]; - if (/\bcooldowns *=/.test(script.textContent)) { - return script.textContent; - } - } - return ''; } }; @@ -7871,18 +10661,28 @@ Get = (function() { }).call(this); Header = (function() { - var Header; + var Header, + slice = [].slice; Header = { init: function() { - var barFixedToggler, barPositionToggler, box, customNavToggler, editCustomNav, footerToggler, headerToggler, linkJustifyToggler, menuButton, scrollHeaderToggler, shortcutToggler; + var barFixedToggler, barPositionToggler, box, cs, customNavToggler, editCustomNav, footerToggler, headerToggler, linkJustifyToggler, menuButton, scrollHeaderToggler, shortcutToggler; + $.onExists(doc, 'body', (function(_this) { + return function() { + if (!Main.isThisPageLegit()) { + return; + } + $.add(_this.bar, [_this.noticesRoot, _this.toggle]); + $.prepend(d.body, _this.bar); + $.add(d.body, Header.hover); + return _this.setBarPosition(Conf['Bottom Header']); + }; + })(this)); this.menu = new UI.Menu('header'); menuButton = $.el('span', { className: 'menu-button' }); - $.extend(menuButton, { - innerHTML: "" - }); + $.extend(menuButton, {innerHTML: ""}); box = UI.checkbox; barFixedToggler = box('Fixed Header', 'Fixed Header'); headerToggler = box('Header auto-hide', 'Auto-hide header'); @@ -7955,65 +10755,52 @@ Header = (function() { } ] }); - $.on(window, 'load popstate', Header.hashScroll); - $.on(d, 'CreateNotification', this.createNotification); - $.asap((function() { - return d.body; - }), (function(_this) { - return function() { - if (!Main.isThisPageLegit()) { + $.on(window, 'load popstate', Header.hashScroll); + $.on(d, 'CreateNotification', this.createNotification); + this.setBoardList(); + $.onExists(doc, g.SITE.selectors.boardList + " + *", Header.generateFullBoardList); + Main.ready(function() { + var a, absbot, footer, i, len, ref; + if (g.SITE.software === 'yotsuba' && !(footer = $.id('boardNavDesktopFoot'))) { + if (!(absbot = $.id('absbot'))) { return; } - $.asap((function() { - return $.id('boardNavMobile') || d.readyState !== 'loading'; - }), function() { - var a, footer; - footer = $.id('boardNavDesktop').cloneNode(true); - footer.id = 'boardNavDesktopFoot'; - $('#navtopright', footer).id = 'navbotright'; - $('#settingsWindowLink', footer).id = 'settingsWindowLinkBot'; - Header.bottomBoardList = $('.boardList', footer); - if (a = $("a[href*='/" + g.BOARD + "/']", footer)) { - a.className = 'current'; - } - Main.ready(function() { - var absbot, oldFooter; - if ((oldFooter = $.id('boardNavDesktopFoot'))) { - return $.replace($('.boardList', oldFooter), Header.bottomBoardList); - } else if ((absbot = $.id('absbot'))) { - $.before(absbot, footer); - return $.globalEval('window.cloneTopNav = function() {};'); - } - }); - return Header.setBoardList(); + footer = $.id('boardNavDesktop').cloneNode(true); + footer.id = 'boardNavDesktopFoot'; + $('#navtopright', footer).id = 'navbotright'; + $('#settingsWindowLink', footer).id = 'settingsWindowLinkBot'; + $.before(absbot, footer); + $.global(function() { + return window.cloneTopNav = function() {}; }); - $.prepend(d.body, _this.bar); - $.add(d.body, Header.hover); - _this.setBarPosition(Conf['Bottom Header']); - return _this; - }; - })(this)); - Main.ready((function(_this) { - return function() { - var cs; - if (g.VIEW === 'catalog' || !Conf['Disable Native Extension']) { - cs = $.el('a', { - href: 'javascript:;' - }); - if (g.VIEW === 'catalog') { - cs.title = cs.textContent = 'Catalog Settings'; - cs.className = 'fa fa-book'; - } else { - cs.title = cs.textContent = '4chan Settings'; - cs.className = 'native-settings'; + } + if ((Header.bottomBoardList = $(g.SITE.selectors.boardListBottom))) { + ref = $$('a', Header.bottomBoardList); + for (i = 0, len = ref.length; i < len; i++) { + a = ref[i]; + if (a.hostname === location.hostname && a.pathname.split('/')[1] === g.BOARD.ID) { + a.className = 'current'; } - $.on(cs, 'click', function() { - return $.id('settingsWindowLink').click(); - }); - return _this.addShortcut('native', cs, 810); } - }; - })(this)); + return CatalogLinks.setLinks(Header.bottomBoardList); + } + }); + if (g.SITE.software === 'yotsuba' && (g.VIEW === 'catalog' || !Conf['Disable Native Extension'])) { + cs = $.el('a', { + href: 'javascript:;' + }); + if (g.VIEW === 'catalog') { + cs.title = cs.textContent = 'Catalog Settings'; + cs.className = 'fa fa-book'; + } else { + cs.title = cs.textContent = '4chan Settings'; + cs.className = 'native-settings'; + } + $.on(cs, 'click', function() { + return $.id('settingsWindowLink').click(); + }); + this.addShortcut('native', cs, 810); + } return this.enableDesktopNotifications(); }, bar: $.el('div', { @@ -8032,84 +10819,61 @@ Header = (function() { id: 'scroll-marker' }), setBoardList: function() { - var a, boardList, btn, chr, i, j, len, len1, node, nodes, ref, ref1, spacer, span; + var boardList, btn; Header.boardList = boardList = $.el('span', { id: 'board-list' }); - $.extend(boardList, { - innerHTML: "" - }); + $.extend(boardList, {innerHTML: ""}); btn = $('.hide-board-list-button', boardList); $.on(btn, 'click', Header.toggleBoardList); - nodes = []; - spacer = function() { - return $.el('span', { - className: 'spacer' - }); - }; - ref = $('#boardNavDesktop > .boardList').childNodes; - for (i = 0, len = ref.length; i < len; i++) { - node = ref[i]; - switch (node.nodeName) { - case '#text': - ref1 = node.nodeValue; - for (j = 0, len1 = ref1.length; j < len1; j++) { - chr = ref1[j]; - span = $.el('span', { - textContent: chr - }); - if (chr === ' ') { - span.className = 'space'; - } - if (chr === ']') { - nodes.push(spacer()); - } - nodes.push(span); - if (chr === '[') { - nodes.push(spacer()); - } - } - break; - case 'A': - a = node.cloneNode(true); - if (a.pathname.split('/')[1] === g.BOARD.ID) { - a.className = 'current'; - } - nodes.push(a); - } - } - $.add($('.boardList', boardList), nodes); - $.add(Header.bar, [Header.boardList, Header.shortcuts, Header.noticesRoot, Header.toggle]); + $.prepend(Header.bar, [Header.boardList, Header.shortcuts]); Header.setCustomNav(Conf['Custom Board Navigation']); Header.generateBoardList(Conf['boardnav']); $.sync('Custom Board Navigation', Header.setCustomNav); return $.sync('boardnav', Header.generateBoardList); }, + generateFullBoardList: function() { + var a, fullBoardList, i, len, nodes, ref; + if (g.SITE.transformBoardList) { + nodes = g.SITE.transformBoardList(); + } else { + nodes = slice.call($(g.SITE.selectors.boardList).cloneNode(true).childNodes); + } + fullBoardList = $('.boardList', Header.boardList); + $.add(fullBoardList, nodes); + ref = $$('a', fullBoardList); + for (i = 0, len = ref.length; i < len; i++) { + a = ref[i]; + if (a.hostname === location.hostname && a.pathname.split('/')[1] === g.BOARD.ID) { + a.className = 'current'; + } + } + return CatalogLinks.setLinks(fullBoardList); + }, generateBoardList: function(boardnav) { - var as, list, nodes, re, t; + var list, nodes, re, t; list = $('#custom-board-list', Header.boardList); $.rmAll(list); if (!boardnav) { return; } boardnav = boardnav.replace(/(\r\n|\n|\r)/g, ' '); - as = $$('#full-board-list a[title]', Header.boardList); - re = /[\w@]+(-(all|title|replace|full|index|catalog|archive|expired|(mode|sort|text):"[^"]+"(,"[^"]+")?))*|[^\w@]+/g; + re = /[\w@]+(-(all|title|replace|full|index|catalog|archive|expired|nt|(mode|sort|text):"[^"]+"(,"[^"]+")?))*|[^\w@]+/g; nodes = (function() { var i, len, ref, results; ref = boardnav.match(re); results = []; for (i = 0, len = ref.length; i < len; i++) { t = ref[i]; - results.push(Header.mapCustomNavigation(t, as)); + results.push(Header.mapCustomNavigation(t)); } return results; })(); $.add(list, nodes); - return $.ready(CatalogLinks.initBoardList); + return CatalogLinks.setLinks(list); }, - mapCustomNavigation: function(t, as) { - var a, boardID, href, indexOptions, m, text, url; + mapCustomNavigation: function(t) { + var a, boardID, href, indexOptions, m, ref, ref1, text, url, urlIC; if (/^[^\w@]/.test(t)) { return $.tn(t); } @@ -8140,14 +10904,39 @@ Header = (function() { textContent: text || '+', className: 'external' }); + if (/-nt/.test(t)) { + a.target = '_blank'; + a.rel = 'noopener'; + } return a; } boardID = t.split('-')[0]; if (boardID === 'current') { - boardID = g.BOARD.ID; + if ((ref = location.hostname) === 'boards.4chan.org' || ref === 'boards.4channel.org') { + boardID = g.BOARD.ID; + } else { + a = $.el('a', { + href: "/" + g.BOARD.ID + "/", + textContent: text || decodeURIComponent(g.BOARD.ID), + className: 'current' + }); + if (/-nt/.test(t)) { + a.target = '_blank'; + a.rel = 'noopener'; + } + if (/-index/.test(t)) { + a.dataset.only = 'index'; + } else if (/-catalog/.test(t)) { + a.dataset.only = 'catalog'; + a.href += 'catalog.html'; + } else if (/-(archive|expired)/.test(t)) { + a = a.firstChild; + } + return a; + } } a = (function() { - var i, len, ref; + var ref1, urlV; if (boardID === '@') { return $.el('a', { href: 'https://twitter.com/4chan', @@ -8155,29 +10944,31 @@ Header = (function() { textContent: '@' }); } - for (i = 0, len = as.length; i < len; i++) { - a = as[i]; - if (a.textContent === boardID) { - return a.cloneNode(true); - } - } a = $.el('a', { - href: "/" + boardID + "/", - textContent: boardID + href: "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/", + textContent: boardID, + title: BoardConfig.title(boardID) }); - if ((ref = g.VIEW) === 'catalog' || ref === 'archive') { - a.href += g.VIEW; + if (((ref1 = g.VIEW) === 'catalog' || ref1 === 'archive') && (urlV = Get.url(g.VIEW, { + siteID: '4chan.org', + boardID: boardID + }))) { + a.href = urlV; } - if (boardID === g.BOARD.ID) { + if (a.hostname === location.hostname && boardID === g.BOARD.ID) { a.className = 'current'; } return a; })(); - a.textContent = /-title/.test(t) || /-replace/.test(t) && boardID === g.BOARD.ID ? a.title || a.textContent : /-full/.test(t) ? ("/" + boardID + "/") + (a.title ? " - " + a.title : '') : text || boardID; + a.textContent = /-title/.test(t) || /-replace/.test(t) && a.hostname === location.hostname && boardID === g.BOARD.ID ? a.title || a.textContent : /-full/.test(t) ? ("/" + boardID + "/") + (a.title ? " - " + a.title : '') : text || boardID; if (m = t.match(/-(index|catalog)/)) { - if (!(boardID === 'f' && m[1] === 'catalog')) { + urlIC = CatalogLinks[m[1]]({ + siteID: '4chan.org', + boardID: boardID + }); + if (urlIC) { a.dataset.only = m[1]; - a.href = CatalogLinks[m[1]](boardID); + a.href = urlIC; if (m[1] === 'catalog') { $.addClass(a, 'catalog'); } @@ -8187,7 +10978,7 @@ Header = (function() { } if (Conf['JSON Index'] && indexOptions) { a.dataset.indexOptions = indexOptions; - if (a.hostname === 'boards.4chan.org' && a.pathname.split('/')[2] === '') { + if (((ref1 = a.hostname) === 'boards.4chan.org' || ref1 === 'boards.4channel.org') && a.pathname.split('/')[2] === '') { a.href += (a.hash ? '/' : '#') + indexOptions; } } @@ -8201,12 +10992,16 @@ Header = (function() { } } if (/-expired/.test(t)) { - if (boardID !== 'b' && boardID !== 'f' && boardID !== 'trash') { - a.href = "/" + boardID + "/archive"; + if (BoardConfig.isArchived(boardID)) { + a.href = "//" + (BoardConfig.domain(boardID)) + "/" + boardID + "/archive"; } else { return a.firstChild; } } + if (/-nt/.test(t)) { + a.target = '_blank'; + a.rel = 'noopener'; + } if (boardID === '@') { $.addClass(a, 'navSmall'); } @@ -8289,9 +11084,7 @@ Header = (function() { } $.off(window, 'scroll', Header.hideBarOnScroll); $.rmClass(Header.bar, 'scroll'); - if (!Conf['Header auto-hide']) { - return $.rmClass(Header.bar, 'autohide'); - } + return Header.bar.classList.toggle('autohide', Conf['Header auto-hide']); }, toggleHideBarOnScroll: function() { var hide; @@ -8310,8 +11103,10 @@ Header = (function() { return Header.previousOffset = offsetY; }, setBarPosition: function(bottom) { - var args; - Header.barPositionToggler.checked = bottom; + var args, ref; + if ((ref = Header.barPositionToggler) != null) { + ref.checked = bottom; + } $.event('CloseMenu'); args = bottom ? ['bottom-header', 'top-header', 'after'] : ['top-header', 'bottom-header', 'add']; $.addClass(doc, args[0]); @@ -8495,9 +11290,7 @@ Header = (function() { case 'denied': return; } - el = $.el('span', { - innerHTML: "4chan X needs your permission to show desktop notifications. [FAQ]
          or " - }); + el = $.el('span', {innerHTML: "4chan X needs your permission to show desktop notifications. [FAQ]
          or "}); ref = $$('button', el), authorize = ref[0], disable = ref[1]; $.on(authorize, 'click', function() { return Notification.requestPermission(function(status) { @@ -8528,11 +11321,26 @@ Index = (function() { Index = { showHiddenThreads: false, changed: {}, + enabledOn: function(arg) { + var boardID, siteID; + siteID = arg.siteID, boardID = arg.boardID; + return Conf['JSON Index'] && g.sites[siteID].software === 'yotsuba' && boardID !== 'f'; + }, init: function() { - var anchorEntry, input, j, k, label, len, len1, name, pinEntry, ref, ref1, ref2, ref3, ref4, ref5, ref6, refNavEntry, repliesEntry, select, sortEntry; - if (g.BOARD.ID === 'f' || !Conf['JSON Index'] || g.VIEW !== 'index') { + var arr, entries, i, input, inputs, k, l, label, len1, len2, name, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, select, sortEntry, tRaw, watchSettings; + if (g.VIEW !== 'index') { return; } + $.one(d, '4chanXInitFinished', this.cb.initFinished); + $.on(d, 'PostsInserted', this.cb.postsInserted); + if (!this.enabledOn(g.BOARD)) { + return; + } + this.enabled = true; + Callbacks.Post.push({ + name: 'Index Page Numbers', + cb: this.node + }); Callbacks.CatalogThread.push({ name: 'Catalog Features', cb: this.catalogNode @@ -8547,7 +11355,8 @@ Index = (function() { this.processHash(); $.addClass(doc, 'index-loading', (Conf['Index Mode'].replace(/\ /g, '-')) + "-mode"); $.on(window, 'popstate', this.cb.popstate); - $.on(d, 'scroll', Index.scroll); + $.on(d, 'scroll', this.scroll); + $.on(d, 'SortIndex', this.cb.resort); this.button = $.el('a', { className: 'fa fa-refresh', title: 'Refresh', @@ -8558,56 +11367,55 @@ Index = (function() { return Index.update(); }); Header.addShortcut('index-refresh', this.button, 590); - repliesEntry = { - el: UI.checkbox('Show Replies', 'Show replies') - }; - sortEntry = { - el: UI.checkbox('Per-Board Sort Type', 'Per-board sort type', typeof Conf['Index Sort'] === 'object') - }; - pinEntry = { - el: UI.checkbox('Pin Watched Threads', 'Pin watched threads') - }; - anchorEntry = { - el: UI.checkbox('Anchor Hidden Threads', 'Anchor hidden threads') - }; - refNavEntry = { - el: UI.checkbox('Refreshed Navigation', 'Refreshed navigation') - }; - sortEntry.el.title = 'Set the sorting order of each board independently.'; - pinEntry.el.title = 'Move watched threads to the start of the index.'; - anchorEntry.el.title = 'Move hidden threads to the end of the index.'; - refNavEntry.el.title = 'Refresh index when navigating through pages.'; - ref4 = [repliesEntry, pinEntry, anchorEntry, refNavEntry]; - for (j = 0, len = ref4.length; j < len; j++) { - label = ref4[j]; - input = label.el.firstChild; - name = input.name; - $.on(input, 'change', $.cb.checked); - switch (name) { - case 'Show Replies': - $.on(input, 'change', this.cb.replies); - break; - case 'Pin Watched Threads': - case 'Anchor Hidden Threads': - $.on(input, 'change', this.cb.resort); + entries = []; + this.inputs = inputs = $.dict(); + ref4 = Config.Index; + for (name in ref4) { + arr = ref4[name]; + if (!(arr instanceof Array)) { + continue; } + label = UI.checkbox(name, "" + name[0] + (name.slice(1).toLowerCase())); + label.title = arr[1]; + entries.push({ + el: label + }); + input = label.firstChild; + $.on(input, 'change', $.cb.checked); + inputs[name] = input; } - $.on(sortEntry.el.firstChild, 'change', this.cb.perBoardSort); + $.on(inputs['Show Replies'], 'change', this.cb.replies); + $.on(inputs['Catalog Hover Expand'], 'change', this.cb.hover); + $.on(inputs['Pin Watched Threads'], 'change', this.cb.resort); + $.on(inputs['Anchor Hidden Threads'], 'change', this.cb.resort); + watchSettings = function(e) { + if ((input = $.getOwn(inputs, e.target.name))) { + input.checked = e.target.checked; + return $.event('change', null, input); + } + }; + $.on(d, 'OpenSettings', function() { + return $.on($.id('fourchanx-settings'), 'change', watchSettings); + }); + sortEntry = UI.checkbox('Per-Board Sort Type', 'Per-board sort type', typeof Conf['Index Sort'] === 'object'); + sortEntry.title = 'Set the sorting order of each board independently.'; + $.on(sortEntry.firstChild, 'change', this.cb.perBoardSort); + entries.splice(3, 0, { + el: sortEntry + }); Header.menu.addEntry({ el: $.el('span', { textContent: 'Index Navigation' }), order: 100, - subEntries: [repliesEntry, sortEntry, pinEntry, anchorEntry, refNavEntry] + subEntries: entries }); this.navLinks = $.el('div', { className: 'navLinks json-index' }); - $.extend(this.navLinks, { - innerHTML: "Index Catalog Archive Bottom ×" - }); + $.extend(this.navLinks, {innerHTML: "Index Catalog Archive Bottom ×"}); $('.cataloglink a', this.navLinks).href = CatalogLinks.catalog(); - if ((ref5 = g.BOARD.ID) === 'b' || ref5 === 'trash') { + if (!BoardConfig.isArchived(g.BOARD.ID)) { $('.archlistlink', this.navLinks).hidden = true; } $.on($('#index-last-refresh a', this.navLinks), 'click', this.cb.refreshFront); @@ -8617,29 +11425,43 @@ Index = (function() { $.on($('#index-search-clear', this.navLinks), 'click', this.clearSearch); this.hideLabel = $('#hidden-label', this.navLinks); $.on($('#hidden-toggle a', this.navLinks), 'click', this.cb.toggleHiddenThreads); + this.selectRev = $('#index-rev', this.navLinks); this.selectMode = $('#index-mode', this.navLinks); this.selectSort = $('#index-sort', this.navLinks); this.selectSize = $('#index-size', this.navLinks); + $.on(this.selectRev, 'change', this.cb.sort); $.on(this.selectMode, 'change', this.cb.mode); $.on(this.selectSort, 'change', this.cb.sort); $.on(this.selectSize, 'change', $.cb.value); $.on(this.selectSize, 'change', this.cb.size); - ref6 = [this.selectMode, this.selectSize]; - for (k = 0, len1 = ref6.length; k < len1; k++) { - select = ref6[k]; + ref5 = [this.selectMode, this.selectSize]; + for (k = 0, len1 = ref5.length; k < len1; k++) { + select = ref5[k]; select.value = Conf[select.name]; } - this.selectSort.value = Index.currentSort; + this.selectRev.checked = /-rev$/.test(Index.currentSort); + this.selectSort.value = Index.currentSort.replace(/-rev$/, ''); + this.lastLongOptions = $('#lastlong-options', this.navLinks); + this.lastLongInputs = $$('input', this.lastLongOptions); + this.lastLongThresholds = [0, 0]; + this.lastLongOptions.hidden = this.selectSort.value !== 'lastlong'; + ref6 = this.lastLongInputs; + for (i = l = 0, len2 = ref6.length; l < len2; i = ++l) { + input = ref6[i]; + $.on(input, 'change', this.cb.lastLongThresholds); + tRaw = Conf["Last Long Reply Thresholds " + i]; + input.value = this.lastLongThresholds[i] = typeof tRaw === 'object' ? (ref7 = tRaw[g.BOARD.ID]) != null ? ref7 : 100 : tRaw; + } this.root = $.el('div', { className: 'board json-index' }); + $.on(this.root, 'click', this.cb.hoverToggle); this.cb.size(); + this.cb.hover(); this.pagelist = $.el('div', { className: 'pagelist json-index' }); - $.extend(this.pagelist, { - innerHTML: "
          " - }); + $.extend(this.pagelist, {innerHTML: "
          "}); $('.cataloglink a', this.pagelist).href = CatalogLinks.catalog(); $.on(this.pagelist, 'click', this.cb.pageNav); this.update(true); @@ -8647,25 +11469,25 @@ Index = (function() { return d.title = d.title.replace(/\ -\ Page\ \d+/, ''); }); $.onExists(doc, '.board > .thread > .postContainer, .board + *', function() { - var board, el, l, len2, len3, m, ref7, ref8, threadRoot, topNavPos; - Index.hat = $('.board > .thread > img:first-child'); - if (Index.hat) { - if (Index.nodes) { - ref7 = Index.nodes; - for (l = 0, len2 = ref7.length; l < len2; l++) { - threadRoot = ref7[l]; - $.prepend(threadRoot, Index.hat.cloneNode(false)); + var board, el, len3, m, ref8, timeEl, topNavPos; + g.SITE.Build.hat = $('.board > .thread > img:first-child'); + if (g.SITE.Build.hat) { + g.BOARD.threads.forEach(function(thread) { + if (thread.nodes.root) { + return $.prepend(thread.nodes.root, g.SITE.Build.hat.cloneNode(false)); } - } + }); $.addClass(doc, 'hats-enabled'); - $.addStyle(".catalog-thread::after {background-image: url(" + Index.hat.src + ");}"); + $.addStyle(".catalog-thread::after {background-image: url(" + g.SITE.Build.hat.src + ");}"); } board = $('.board'); $.replace(board, Index.root); - $.event('PostsInserted'); + if (Index.loaded) { + $.event('PostsInserted', null, Index.root); + } try { d.implementation.createDocument(null, null, null).appendChild(board); - } catch (_error) {} + } catch (error) {} ref8 = $$('.navLinks'); for (m = 0, len3 = ref8.length; m < len3; m++) { el = ref8[m]; @@ -8674,7 +11496,11 @@ Index = (function() { $.rm($.id('ctrl-top')); topNavPos = $.id('delform').previousElementSibling; $.before(topNavPos, $.el('hr')); - return $.before(topNavPos, Index.navLinks); + $.before(topNavPos, Index.navLinks); + timeEl = $('#index-last-refresh time', Index.navLinks); + if (timeEl.dataset.utc) { + return RelativeDates.update(timeEl); + } }); return Main.ready(function() { var pagelist; @@ -8685,7 +11511,7 @@ Index = (function() { }); }, scroll: function() { - var nodes, pageNum; + var pageNum, threadIDs; if (Index.req || !Index.liveThreadData || Conf['Index Mode'] !== 'infinite' || (window.scrollY <= doc.scrollHeight - (300 + window.innerHeight))) { return; } @@ -8696,11 +11522,8 @@ Index = (function() { if (pageNum > Index.pagesNum) { return Index.endNotice(); } - nodes = Index.buildSinglePage(pageNum); - if (Conf['Show Replies']) { - Index.buildReplies(nodes); - } - return Index.buildStructure(nodes); + threadIDs = Index.threadsOnPage(pageNum); + return Index.buildStructure(threadIDs); }, endNotice: (function() { var notify, reset; @@ -8719,16 +11542,14 @@ Index = (function() { })(), menu: { init: function() { - if (g.VIEW !== 'index' || !Conf['JSON Index'] || !Conf['Menu'] || !Conf['Thread Hiding Link'] || g.BOARD.ID === 'f') { + if (!(g.VIEW === 'index' && Conf['Menu'] && Conf['Thread Hiding Link'] && Index.enabledOn(g.BOARD))) { return; } return Menu.menu.addEntry({ el: $.el('a', { href: 'javascript:;', className: 'has-shortcut-text' - }, { - innerHTML: "Shift+click" - }), + }, {innerHTML: "Shift+click"}), order: 20, open: function(arg) { var thread; @@ -8750,24 +11571,26 @@ Index = (function() { }); } }, - catalogNode: function() { - return $.on(this.nodes.thumb.parentNode, 'click', Index.onClick); - }, - onClick: function(e) { - var thread; - if (e.button !== 0) { - return; - } - thread = g.threads[this.parentNode.dataset.fullID]; - if (e.shiftKey) { - Index.toggleHide(thread); - } else { + node: function() { + if (this.isReply || this.isClone || !(Index.threadPosition[this.ID] != null)) { return; } - return e.preventDefault(); + return this.thread.setPage(Math.floor(Index.threadPosition[this.ID] / Index.threadsNumPerPage) + 1); + }, + catalogNode: function() { + return $.on(this.nodes.root, 'mousedown click', (function(_this) { + return function(e) { + if (!(e.button === 0 && e.shiftKey)) { + return; + } + if (e.type === 'click') { + Index.toggleHide(_this.thread); + } + return e.preventDefault(); + }; + })(this)); }, toggleHide: function(thread) { - $.rm(thread.catalogView.nodes.root); if (Index.showHiddenThreads) { ThreadHiding.show(thread); if (!ThreadHiding.db.get({ @@ -8782,11 +11605,11 @@ Index = (function() { return ThreadHiding.saveHiddenState(thread); }, cycleSortType: function() { - var i, j, len, type, types; + var i, k, len1, type, types; types = slice.call(Index.selectSort.options).filter(function(option) { return !option.disabled; }); - for (i = j = 0, len = types.length; j < len; i = ++j) { + for (i = k = 0, len1 = types.length; k < len1; i = ++k) { type = types[i]; if (type.selected) { break; @@ -8796,6 +11619,28 @@ Index = (function() { return $.event('change', null, Index.selectSort); }, cb: { + initFinished: function() { + Index.initFinishedFired = true; + return $.queueTask(function() { + return Index.cb.postsInserted(); + }); + }, + postsInserted: function() { + var n; + if (!Index.initFinishedFired) { + return; + } + n = 0; + g.posts.forEach(function(post) { + if (!post.isFetchedQuote && !post.indexRefreshSeen && doc.contains(post.nodes.root)) { + post.indexRefreshSeen = true; + return n++; + } + }); + if (n) { + return $.event('IndexRefresh'); + } + }, toggleHiddenThreads: function() { $('#hidden-toggle a', Index.navLinks).textContent = (Index.showHiddenThreads = !Index.showHiddenThreads) ? 'Hide' : 'Show'; Index.sort(); @@ -8808,18 +11653,41 @@ Index = (function() { return Index.pageLoad(false); }, sort: function() { + var value; + value = Index.selectRev.checked ? Index.selectSort.value + "-rev" : Index.selectSort.value; Index.pushState({ - sort: this.value + sort: value }); return Index.pageLoad(false); }, - resort: function() { - Index.sort(); - return Index.buildIndex(); + resort: function(e) { + var ref; + Index.changed.order = true; + if (!(e != null ? (ref = e.detail) != null ? ref.deferred : void 0 : void 0)) { + return Index.pageLoad(false); + } }, perBoardSort: function() { - Conf['Index Sort'] = this.checked ? {} : ''; - return Index.saveSort(); + var i, k; + Conf['Index Sort'] = this.checked ? $.dict() : ''; + Index.saveSort(); + for (i = k = 0; k < 2; i = ++k) { + Conf["Last Long Reply Thresholds " + i] = this.checked ? $.dict() : ''; + Index.saveLastLongThresholds(i); + } + }, + lastLongThresholds: function() { + var i, value; + i = slice.call(this.parentNode.children).indexOf(this); + value = +this.value; + if (!Number.isFinite(value)) { + this.value = Index.lastLongThresholds[i]; + return; + } + Index.lastLongThresholds[i] = value; + Index.saveLastLongThresholds(i); + Index.changed.order = true; + return Index.pageLoad(false); }, size: function(e) { if (Conf['Index Mode'] !== 'catalog') { @@ -8837,10 +11705,23 @@ Index = (function() { } }, replies: function() { - Index.buildThreads(); - Index.sort(); return Index.buildIndex(); }, + hover: function() { + return doc.classList.toggle('catalog-hover-expand', Conf['Catalog Hover Expand']); + }, + hoverToggle: function(e) { + var input, thread; + if (Conf['Catalog Hover Toggle'] && $.hasClass(doc, 'catalog-mode') && !$.modifiedClick(e) && !$.x('ancestor-or-self::a', e.target)) { + input = Index.inputs['Catalog Hover Expand']; + input.checked = !input.checked; + $.event('change', null, input); + if ((thread = Get.threadFromNode(e.target))) { + Index.cb.catalogReplies.call(thread); + return Index.cb.hoverAdjust.call(thread.OP.nodes); + } + } + }, popstate: function(e) { var mode, nCommands, page, ref, searched, sort; if (e != null ? e.state : void 0) { @@ -8864,7 +11745,7 @@ Index = (function() { }, pageNav: function(e) { var a; - if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { + if ($.modifiedClick(e)) { return; } switch (e.target.nodeName) { @@ -8889,6 +11770,26 @@ Index = (function() { page: 1 }); return Index.update(); + }, + catalogReplies: function() { + if (Conf['Show Replies'] && $.hasClass(doc, 'catalog-hover-expand') && !this.catalogView.nodes.replies) { + return Index.buildCatalogReplies(this); + } + }, + hoverAdjust: function() { + var rect, style, x; + if (!$.hasClass(doc, 'catalog-hover-expand')) { + return; + } + rect = this.post.getBoundingClientRect(); + if ((x = $.minmax(0, -rect.left, doc.clientWidth - rect.right))) { + style = this.post.style; + style.left = x + "px"; + style.right = (-x) + "px"; + return $.one(this.root, 'mouseleave', function() { + return style.left = style.right = null; + }); + } } }, scrollToIndex: function() { @@ -8922,26 +11823,30 @@ Index = (function() { 'last-long-reply': 'lastlong', 'creation-date': 'birth', 'reply-count': 'replycount', - 'file-count': 'filecount' + 'file-count': 'filecount', + 'posts-per-minute': 'activity' } }, processHash: function() { - var command, commands, hash, j, leftover, len, mode, ref, sort, state; + var command, commands, hash, k, leftover, len1, mode, ref, sort, state; hash = ((ref = location.href.match(/#.*/)) != null ? ref[0] : void 0) || ''; state = { replace: true }; commands = hash.slice(1).split('/'); leftover = []; - for (j = 0, len = commands.length; j < len; j++) { - command = commands[j]; - if ((mode = Index.hashCommands.mode[command])) { + for (k = 0, len1 = commands.length; k < len1; k++) { + command = commands[k]; + if ((mode = $.getOwn(Index.hashCommands.mode, command))) { state.mode = mode; } else if (command === 'index') { state.mode = Conf['Previous Index Mode']; state.page = 1; - } else if ((sort = Index.hashCommands.sort[command])) { + } else if ((sort = $.getOwn(Index.hashCommands.sort, command.replace(/-rev$/, '')))) { state.sort = sort; + if (/-rev$/.test(command)) { + state.sort += '-rev'; + } } else if (/^s=/.test(command)) { state.search = decodeURIComponent(command.slice(2)).replace(/\+/g, ' ').trim(); } else { @@ -9009,27 +11914,35 @@ Index = (function() { return Index.changed.hash = true; } }, - saveSort: function() { - if (typeof Conf['Index Sort'] === 'object') { - Conf['Index Sort'][g.BOARD.ID] = Index.currentSort; + savePerBoard: function(key, value) { + if (typeof Conf[key] === 'object') { + Conf[key][g.BOARD.ID] = value; } else { - Conf['Index Sort'] = Index.currentSort; + Conf[key] = value; } - return $.set('Index Sort', Conf['Index Sort']); + return $.set(key, Conf[key]); + }, + saveSort: function() { + return Index.savePerBoard('Index Sort', Index.currentSort); + }, + saveLastLongThresholds: function(i) { + return Index.savePerBoard("Last Long Reply Thresholds " + i, Index.lastLongThresholds[i]); }, pageLoad: function(scroll) { - var hash, mode, page, ref, search, sort, threads; + var hash, mode, order, page, ref, search, sort, threads; if (scroll == null) { scroll = true; } if (!Index.liveThreadData) { return; } - ref = Index.changed, threads = ref.threads, search = ref.search, mode = ref.mode, sort = ref.sort, page = ref.page, hash = ref.hash; - if (threads || search || sort) { + ref = Index.changed, threads = ref.threads, order = ref.order, search = ref.search, mode = ref.mode, sort = ref.sort, page = ref.page, hash = ref.hash; + threads || (threads = search); + order || (order = sort); + if (threads || order) { Index.sort(); } - if (threads || search) { + if (threads) { Index.buildPagelist(); } if (search) { @@ -9041,10 +11954,10 @@ Index = (function() { if (sort) { Index.setupSort(); } - if (threads || search || mode || page || sort) { + if (threads || mode || page || order) { Index.buildIndex(); } - if (threads || search || mode || page) { + if (threads || page) { Index.setPage(); } if (scroll && !hash) { @@ -9056,10 +11969,10 @@ Index = (function() { return Index.changed = {}; }, setupMode: function() { - var j, len, mode, ref; + var k, len1, mode, ref; ref = ['paged', 'infinite', 'all pages', 'catalog']; - for (j = 0, len = ref.length; j < len; j++) { - mode = ref[j]; + for (k = 0, len1 = ref.length; k < len1; k++) { + mode = ref[k]; $[mode === Conf['Index Mode'] ? 'addClass' : 'rmClass'](doc, (mode.replace(/\ /g, '-')) + "-mode"); } Index.selectMode.value = Conf['Index Mode']; @@ -9068,11 +11981,13 @@ Index = (function() { return $('#hidden-toggle a', Index.navLinks).textContent = 'Show'; }, setupSort: function() { - return Index.selectSort.value = Index.currentSort; + Index.selectRev.checked = /-rev$/.test(Index.currentSort); + Index.selectSort.value = Index.currentSort.replace(/-rev$/, ''); + return Index.lastLongOptions.hidden = Index.selectSort.value !== 'lastlong'; }, getPagesNum: function() { if (Index.search) { - return Math.ceil(Index.sortedNodes.length / Index.threadsNumPerPage); + return Math.ceil(Index.sortedThreadIDs.length / Index.threadsNumPerPage); } else { return Index.pagesNum; } @@ -9081,12 +11996,12 @@ Index = (function() { return Math.max(1, Index.getPagesNum()); }, buildPagelist: function() { - var a, i, j, maxPageNum, nodes, pagesRoot, ref; + var a, i, k, maxPageNum, nodes, pagesRoot, ref; pagesRoot = $('.pages', Index.pagelist); maxPageNum = Index.getMaxPageNum(); if (pagesRoot.childElementCount !== maxPageNum) { nodes = []; - for (i = j = 1, ref = maxPageNum; j <= ref; i = j += 1) { + for (i = k = 1, ref = maxPageNum; k <= ref; i = k += 1) { a = $.el('a', { textContent: i, href: i === 1 ? './' : i @@ -9118,20 +12033,22 @@ Index = (function() { } else { strong = $.el('strong'); } - a = pagesRoot.children[pageNum - 1]; - $.before(a, strong); - return $.add(strong, a); + if ((a = pagesRoot.children[pageNum - 1])) { + $.before(a, strong); + return $.add(strong, a); + } }, updateHideLabel: function() { - var hiddenCount, ref, ref1, thread, threadID; + var hiddenCount, k, len1, ref, threadID; + if (!Index.hideLabel) { + return; + } hiddenCount = 0; - ref = g.BOARD.threads; - for (threadID in ref) { - thread = ref[threadID]; - if (thread.isHidden) { - if (ref1 = thread.ID, indexOf.call(Index.liveThreadIDs, ref1) >= 0) { - hiddenCount++; - } + ref = Index.liveThreadIDs; + for (k = 0, len1 = ref.length; k < len1; k++) { + threadID = ref[k]; + if (Index.isHidden(threadID)) { + hiddenCount++; } } if (!hiddenCount) { @@ -9145,56 +12062,46 @@ Index = (function() { return $('#hidden-count', Index.navLinks).textContent = hiddenCount === 1 ? '1 hidden thread' : hiddenCount + " hidden threads"; }, update: function(firstTime) { - var now, ref, ref1; - if ((ref = Index.req) != null) { - ref.abort(); - } - if ((ref1 = Index.notice) != null) { - ref1.close(); - } - if (Conf['Index Refresh Notifications'] && d.readyState !== 'loading') { - Index.notice = new Notice('info', 'Refreshing index...'); + var oldReq; + if ((oldReq = Index.req)) { + delete Index.req; + oldReq.abort(); + } + if (Conf['Index Refresh Notifications']) { + Index.notice || (Index.notice = new Notice('info', 'Refreshing index...')); + Index.nTimeout || (Index.nTimeout = setTimeout(function() { + var ref; + return (ref = Index.notice) != null ? ref.el.lastElementChild.textContent += ' (disable JSON Index if this takes too long)' : void 0; + }, 3 * $.SECOND)); } else { - now = Date.now(); - $.ready(function() { - return Index.nTimeout = setTimeout((function() { - if (Index.req && !Index.notice) { - return Index.notice = new Notice('info', 'Refreshing index...'); - } - }), 3 * $.SECOND - (Date.now() - now)); - }); + Index.nTimeout || (Index.nTimeout = setTimeout(function() { + return Index.notice || (Index.notice = new Notice('info', 'Refreshing index... (disable JSON Index if this takes too long)')); + }, 3 * $.SECOND)); } if (!firstTime && d.readyState !== 'loading' && !$('.board + *')) { location.reload(); return; } - Index.req = $.ajax("//a.4cdn.org/" + g.BOARD + "/catalog.json", { - onabort: Index.load, - onloadend: Index.load - }, { - whenModified: 'Index' - }); + Index.req = $.whenModified(g.SITE.urls.catalogJSON({ + boardID: g.BOARD.ID + }), 'Index', Index.load); return $.addClass(Index.button, 'fa-spin'); }, - load: function(e) { - var err, nTimeout, notice, ref, req, timeEl; + load: function() { + var err, nTimeout, notice, ref, timeEl; + if (this !== Index.req) { + return; + } $.rmClass(Index.button, 'fa-spin'); - req = Index.req, notice = Index.notice, nTimeout = Index.nTimeout; + notice = Index.notice, nTimeout = Index.nTimeout; if (nTimeout) { clearTimeout(nTimeout); } delete Index.nTimeout; delete Index.req; delete Index.notice; - if (e.type === 'abort') { - req.onloadend = null; - if (notice != null) { - notice.close(); - } - return; - } - if ((ref = req.status) !== 200 && ref !== 304) { - err = "Index refresh failed. " + (req.status ? "Error " + req.statusText + " (" + req.status + ")" : 'Connection Error'); + if ((ref = this.status) !== 200 && ref !== 304) { + err = "Index refresh failed. " + (this.status ? "Error " + this.statusText + " (" + this.status + ")" : 'Connection Error'); if (notice) { notice.setType('warning'); notice.el.lastElementChild.textContent = err; @@ -9205,13 +12112,13 @@ Index = (function() { return; } try { - if (req.status === 200) { - Index.parse(req.response); - } else if (req.status === 304) { + if (this.status === 200) { + Index.parse(this.response); + } else if (this.status === 304) { Index.pageLoad(); } - } catch (_error) { - err = _error; + } catch (error) { + err = error; c.error("Index failure: " + err.message, err.stack); if (notice) { notice.setType('error'); @@ -9232,20 +12139,19 @@ Index = (function() { } } timeEl = $('#index-last-refresh time', Index.navLinks); - timeEl.dataset.utc = Date.parse(req.getResponseHeader('Last-Modified')); + timeEl.dataset.utc = Date.parse(this.getResponseHeader('Last-Modified')); return RelativeDates.update(timeEl); }, parse: function(pages) { $.cleanCache(function(url) { - return /^\/\/a\.4cdn\.org\//.test(url); + return /^https?:\/\/a\.4cdn\.org\//.test(url); }); Index.parseThreadList(pages); - Index.buildThreads(); Index.changed.threads = true; return Index.pageLoad(); }, parseThreadList: function(pages) { - var ref; + var ID, data, i, k, l, len1, len2, obj, ref, ref1, ref2, reply, results; Index.pagesNum = pages.length; Index.threadsNumPerPage = ((ref = pages[0]) != null ? ref.threads.length : void 0) || 1; Index.liveThreadData = pages.reduce((function(arr, next) { @@ -9254,126 +12160,197 @@ Index = (function() { Index.liveThreadIDs = Index.liveThreadData.map(function(data) { return data.no; }); + Index.liveThreadDict = $.dict(); + Index.threadPosition = $.dict(); + Index.parsedThreads = $.dict(); + Index.replyData = $.dict(); + ref1 = Index.liveThreadData; + for (i = k = 0, len1 = ref1.length; k < len1; i = ++k) { + data = ref1[i]; + Index.liveThreadDict[data.no] = data; + Index.threadPosition[data.no] = i; + Index.parsedThreads[data.no] = obj = g.SITE.Build.parseJSON(data, g.BOARD); + obj.filterResults = results = Filter.test(obj); + obj.isOnTop = results.top; + obj.isHidden = results.hide || ThreadHiding.isHidden(obj.boardID, obj.threadID); + if (data.last_replies) { + ref2 = data.last_replies; + for (l = 0, len2 = ref2.length; l < len2; l++) { + reply = ref2[l]; + Index.replyData[g.BOARD + "." + reply.no] = reply; + } + } + } + if (Index.liveThreadData[0]) { + g.SITE.Build.spoilerRange[g.BOARD.ID] = Index.liveThreadData[0].custom_spoiler; + } g.BOARD.threads.forEach(function(thread) { - var ref1; - if (ref1 = thread.ID, indexOf.call(Index.liveThreadIDs, ref1) < 0) { + var ref3; + if (ref3 = thread.ID, indexOf.call(Index.liveThreadIDs, ref3) < 0) { return thread.collect(); } }); + $.event('IndexUpdate', { + threads: (function() { + var len3, m, ref3, results1; + ref3 = Index.liveThreadIDs; + results1 = []; + for (m = 0, len3 = ref3.length; m < len3; m++) { + ID = ref3[m]; + results1.push(g.BOARD + "." + ID); + } + return results1; + })() + }); }, - buildThreads: function() { - var err, errors, i, j, len, posts, ref, thread, threadData, threadRoot, threads; - if (!Index.liveThreadData) { - return; + isHidden: function(threadID) { + var thread; + if ((thread = g.BOARD.threads.get(threadID)) && thread.OP && !thread.OP.isFetchedQuote) { + return thread.isHidden; + } else { + return Index.parsedThreads[threadID].isHidden; } - Index.nodes = []; + }, + isHiddenReply: function(threadID, replyData) { + return PostHiding.isHidden(g.BOARD.ID, threadID, replyData.no) || Filter.isHidden(g.SITE.Build.parseJSON(replyData, g.BOARD)); + }, + buildThreads: function(threadIDs, isCatalog, withReplies) { + var ID, OP, err, errors, isStale, k, lastPost, len1, newPosts, newThreads, obj, opRoot, t, thread, threadData, threads; threads = []; - posts = []; - ref = Index.liveThreadData; - for (i = j = 0, len = ref.length; j < len; i = ++j) { - threadData = ref[i]; + newThreads = []; + newPosts = []; + for (k = 0, len1 = threadIDs.length; k < len1; k++) { + ID = threadIDs[k]; try { - threadRoot = Build.thread(g.BOARD, threadData); - if (Index.hat) { - $.prepend(threadRoot, Index.hat.cloneNode(false)); - } - if (thread = g.BOARD.threads[threadData.no]) { - thread.setCount('post', threadData.replies + 1, threadData.bumplimit); - thread.setCount('file', threadData.images + !!threadData.ext, threadData.imagelimit); - thread.setStatus('Sticky', !!threadData.sticky); - thread.setStatus('Closed', !!threadData.closed); + threadData = Index.liveThreadDict[ID]; + if ((thread = g.BOARD.threads.get(ID))) { + isStale = (thread.json !== threadData) && (JSON.stringify(thread.json) !== JSON.stringify(threadData)); + if (isStale) { + thread.setCount('post', threadData.replies + 1, threadData.bumplimit); + thread.setCount('file', threadData.images + !!threadData.ext, threadData.imagelimit); + thread.setStatus('Sticky', !!threadData.sticky); + thread.setStatus('Closed', !!threadData.closed); + } + if (thread.catalogView) { + $.rm(thread.catalogView.nodes.replies); + thread.catalogView.nodes.replies = null; + } } else { - thread = new Thread(threadData.no, g.BOARD); - threads.push(thread); + thread = new Thread(ID, g.BOARD); + newThreads.push(thread); + } + lastPost = threadData.last_replies && threadData.last_replies.length ? threadData.last_replies[threadData.last_replies.length - 1].no : ID; + if (lastPost > thread.lastPost) { + thread.lastPost = lastPost; + } + thread.json = threadData; + threads.push(thread); + if ((OP = thread.OP) && !OP.isFetchedQuote) { + OP.setCatalogOP(isCatalog); + thread.setPage(Math.floor(Index.threadPosition[ID] / Index.threadsNumPerPage) + 1); + } else { + obj = Index.parsedThreads[ID]; + opRoot = g.SITE.Build.post(obj); + OP = new Post(opRoot, thread, g.BOARD); + OP.filterResults = obj.filterResults; + newPosts.push(OP); } - Index.nodes.push(threadRoot); - if (!(thread.OP && !thread.OP.isFetchedQuote)) { - posts.push(new Post($('.opContainer', threadRoot), thread, g.BOARD)); + if (!(isCatalog && thread.nodes.root)) { + g.SITE.Build.thread(thread, threadData, withReplies); } - thread.setPage(Math.floor(i / Index.threadsNumPerPage) + 1); - } catch (_error) { - err = _error; + } catch (error) { + err = error; if (!errors) { errors = []; } errors.push({ message: "Parsing of Thread No." + thread + " failed. Thread will be skipped.", - error: err + error: err, + html: opRoot != null ? opRoot.outerHTML : void 0 }); } } if (errors) { Main.handleErrors(errors); } - $.nodes(Index.nodes); - Main.callbackNodes('Thread', threads); - Main.callbackNodes('Post', posts); + if (withReplies) { + newPosts = newPosts.concat(Index.buildReplies(threads)); + } + Main.callbackNodes('Thread', newThreads); + Main.callbackNodes('Post', newPosts); Index.updateHideLabel(); - return $.event('IndexRefresh'); + $.event('IndexRefreshInternal', { + threadIDs: (function() { + var l, len2, results1; + results1 = []; + for (l = 0, len2 = threads.length; l < len2; l++) { + t = threads[l]; + results1.push(t.fullID); + } + return results1; + })(), + isCatalog: isCatalog + }); + return threads; }, - buildReplies: function(threadRoots) { - var data, err, errors, i, j, k, lastReplies, len, len1, node, nodes, post, posts, thread, threadRoot; + buildReplies: function(threads) { + var data, err, errors, k, l, lastReplies, len1, len2, node, nodes, post, posts, thread; posts = []; - for (j = 0, len = threadRoots.length; j < len; j++) { - threadRoot = threadRoots[j]; - thread = Get.threadFromRoot(threadRoot); - i = Index.liveThreadIDs.indexOf(thread.ID); - if (!(lastReplies = Index.liveThreadData[i].last_replies)) { + for (k = 0, len1 = threads.length; k < len1; k++) { + thread = threads[k]; + if (!(lastReplies = Index.liveThreadDict[thread.ID].last_replies)) { continue; } nodes = []; - for (k = 0, len1 = lastReplies.length; k < len1; k++) { - data = lastReplies[k]; - if ((post = thread.posts[data.no]) && !post.isFetchedQuote) { + for (l = 0, len2 = lastReplies.length; l < len2; l++) { + data = lastReplies[l]; + if ((post = thread.posts.get(data.no)) && !post.isFetchedQuote) { nodes.push(post.nodes.root); continue; } - nodes.push(node = Build.postFromObject(data, thread.board.ID)); + nodes.push(node = g.SITE.Build.postFromObject(data, thread.board.ID)); try { posts.push(new Post(node, thread, thread.board)); - } catch (_error) { - err = _error; + } catch (error) { + err = error; if (!errors) { errors = []; } errors.push({ message: "Parsing of Post No." + data.no + " failed. Post will be skipped.", - error: err + error: err, + html: node != null ? node.outerHTML : void 0 }); } } - $.add(threadRoot, nodes); + $.add(thread.nodes.root, nodes); } if (errors) { Main.handleErrors(errors); } - return Main.callbackNodes('Post', posts); + return posts; }, - buildCatalogViews: function() { - var catalogThreads, j, len, thread, threads; - threads = Index.sortedNodes.map(function(threadRoot) { - return Get.threadFromRoot(threadRoot); - }).filter(function(thread) { - return !thread.isHidden !== Index.showHiddenThreads; - }); + buildCatalogViews: function(threads) { + var ID, catalogThreads, k, len1, page, root, thread; catalogThreads = []; - for (j = 0, len = threads.length; j < len; j++) { - thread = threads[j]; - if (!thread.catalogView) { - catalogThreads.push(new CatalogThread(Build.catalogThread(thread), thread)); + for (k = 0, len1 = threads.length; k < len1; k++) { + thread = threads[k]; + if (!(!thread.catalogView)) { + continue; } + ID = thread.ID; + page = Math.floor(Index.threadPosition[ID] / Index.threadsNumPerPage) + 1; + root = g.SITE.Build.catalogThread(thread, Index.liveThreadDict[ID], page); + catalogThreads.push(new CatalogThread(root, thread)); } Main.callbackNodes('CatalogThread', catalogThreads); - return threads.map(function(thread) { - return thread.catalogView.nodes.root; - }); }, - sizeCatalogViews: function(nodes) { - var height, j, len, node, ratio, ref, size, thumb, width; + sizeCatalogViews: function(threads) { + var height, k, len1, ratio, ref, size, thread, thumb, width; size = Conf['Index Size'] === 'small' ? 150 : 250; - for (j = 0, len = nodes.length; j < len; j++) { - node = nodes[j]; - thumb = $('.catalog-thumb', node); + for (k = 0, len1 = threads.length; k < len1; k++) { + thread = threads[k]; + thumb = thread.catalogView.nodes.thumb; ref = thumb.dataset, width = ref.width, height = ref.height; if (!width) { continue; @@ -9383,41 +12360,78 @@ Index = (function() { thumb.style.height = height * ratio + 'px'; } }, + buildCatalogReplies: function(thread) { + var data, k, lastReplies, len1, nodes, replies, reply; + nodes = thread.catalogView.nodes; + if (!(lastReplies = Index.liveThreadDict[thread.ID].last_replies)) { + return; + } + replies = []; + for (k = 0, len1 = lastReplies.length; k < len1; k++) { + data = lastReplies[k]; + if (Index.isHiddenReply(thread.ID, data)) { + continue; + } + reply = g.SITE.Build.catalogReply(thread, data); + RelativeDates.update($('time', reply)); + $.on($('.catalog-reply-preview', reply), 'mouseover', QuotePreview.mouseover); + replies.push(reply); + } + nodes.replies = $.el('div', { + className: 'catalog-replies' + }); + $.add(nodes.replies, replies); + $.add(thread.OP.nodes.post, nodes.replies); + }, sort: function() { - var j, lastlong, len, liveThreadData, liveThreadIDs, nodes, sortedNodes, sortedThreadIDs, threadID; + var lastlong, lastlongD, liveThreadData, liveThreadIDs, repliesAvailable, sortType, thread, threadIDs, tmp_time; liveThreadIDs = Index.liveThreadIDs, liveThreadData = Index.liveThreadData; if (!liveThreadData) { return; } - sortedThreadIDs = (function() { - switch (Index.currentSort) { + tmp_time = new Date().getTime() / 1000; + sortType = Index.currentSort.replace(/-rev$/, ''); + Index.sortedThreadIDs = (function() { + var k, len1; + switch (sortType) { case 'lastreply': - return slice.call(liveThreadData).sort(function(a, b) { - var num; - if ((num = a.last_replies)) { - a = num[num.length - 1]; - } - if ((num = b.last_replies)) { - b = num[num.length - 1]; - } - return b.no - a.no; - }).map(function(post) { - return post.no; - }); case 'lastlong': + repliesAvailable = liveThreadData.some(function(thread) { + var ref; + return (ref = thread.last_replies) != null ? ref.length : void 0; + }); lastlong = function(thread) { - var i, j, r, ref; + var i, k, len, r, ref, ref1; + if (!repliesAvailable) { + return thread.last_modified; + } ref = thread.last_replies || []; - for (i = j = ref.length - 1; j >= 0; i = j += -1) { + for (i = k = ref.length - 1; k >= 0; i = k += -1) { r = ref[i]; - if (r.com && Build.parseComment(r.com).replace(/[^a-z]/ig, '').length >= 100) { + if (Index.isHiddenReply(thread.no, r)) { + continue; + } + if (sortType === 'lastreply') { return r; } + len = r.com ? g.SITE.Build.parseComment(r.com).replace(/[^a-z]/ig, '').length : 0; + if (len >= Index.lastLongThresholds[+(!!r.ext)]) { + return r; + } + } + if (thread.omitted_posts && ((ref1 = thread.last_replies) != null ? ref1.length : void 0)) { + return thread.last_replies[0]; + } else { + return thread; } - return thread; }; + lastlongD = $.dict(); + for (k = 0, len1 = liveThreadData.length; k < len1; k++) { + thread = liveThreadData[k]; + lastlongD[thread.no] = lastlong(thread).no; + } return slice.call(liveThreadData).sort(function(a, b) { - return lastlong(b).no - lastlong(a).no; + return lastlongD[b.no] - lastlongD[a.no]; }).map(function(post) { return post.no; }); @@ -9439,105 +12453,134 @@ Index = (function() { }).map(function(post) { return post.no; }); + case 'activity': + return slice.call(liveThreadData).sort(function(a, b) { + return (tmp_time - a.time) / (a.replies + 1) - (tmp_time - b.time) / (b.replies + 1); + }).map(function(post) { + return post.no; + }); + default: + return liveThreadIDs; } })(); - Index.sortedNodes = sortedNodes = []; - nodes = Index.nodes; - for (j = 0, len = sortedThreadIDs.length; j < len; j++) { - threadID = sortedThreadIDs[j]; - sortedNodes.push(nodes[Index.liveThreadIDs.indexOf(threadID)]); + if (/-rev$/.test(Index.currentSort)) { + Index.sortedThreadIDs = slice.call(Index.sortedThreadIDs).reverse(); } - if (Index.search && (nodes = Index.querySearch(Index.search))) { - Index.sortedNodes = nodes; + if (Index.search && (threadIDs = Index.querySearch(Index.search))) { + Index.sortedThreadIDs = threadIDs; } - Index.sortOnTop(function(thread) { - return thread.isSticky; + Index.sortOnTop(function(obj) { + return obj.isSticky; }); - Index.sortOnTop(function(thread) { - return thread.isOnTop || Conf['Pin Watched Threads'] && ThreadWatcher.isWatched(thread); + Index.sortOnTop(function(obj) { + return obj.isOnTop || Conf['Pin Watched Threads'] && ThreadWatcher.isWatchedRaw(obj.boardID, obj.threadID); }); if (Conf['Anchor Hidden Threads']) { - return Index.sortOnTop(function(thread) { - return !thread.isHidden; + return Index.sortOnTop(function(obj) { + return !Index.isHidden(obj.threadID); }); } }, sortOnTop: function(match) { - var bottomNodes, j, len, ref, threadRoot, topNodes; - topNodes = []; - bottomNodes = []; - ref = Index.sortedNodes; - for (j = 0, len = ref.length; j < len; j++) { - threadRoot = ref[j]; - (match(Get.threadFromRoot(threadRoot)) ? topNodes : bottomNodes).push(threadRoot); + var ID, bottomThreads, k, len1, ref, topThreads; + topThreads = []; + bottomThreads = []; + ref = Index.sortedThreadIDs; + for (k = 0, len1 = ref.length; k < len1; k++) { + ID = ref[k]; + (match(Index.parsedThreads[ID]) ? topThreads : bottomThreads).push(ID); } - return Index.sortedNodes = topNodes.concat(bottomNodes); + return Index.sortedThreadIDs = topThreads.concat(bottomThreads); }, buildIndex: function() { - var i, nodes, page, post; + var threadIDs; if (!Index.liveThreadData) { return; } switch (Conf['Index Mode']) { case 'all pages': - nodes = Index.sortedNodes; + threadIDs = Index.sortedThreadIDs; break; case 'catalog': - nodes = Index.buildCatalogViews(); - Index.sizeCatalogViews(nodes); + threadIDs = Index.sortedThreadIDs.filter(function(ID) { + return !Index.isHidden(ID) !== Index.showHiddenThreads; + }); break; default: - if (Index.followedThreadID != null) { - i = 0; - while (Index.followedThreadID !== Get.threadFromRoot(Index.sortedNodes[i]).ID) { - i++; - } - page = Math.floor(i / Index.threadsNumPerPage) + 1; - if (page !== Index.currentPage) { - Index.currentPage = page; - Index.pushState({ - page: page - }); - Index.setPage(); - } - } - nodes = Index.buildSinglePage(Index.currentPage); + threadIDs = Index.threadsOnPage(Index.currentPage); } delete Index.pageNum; $.rmAll(Index.root); $.rmAll(Header.hover); + if (Index.loaded && Index.root.parentNode) { + $.event('PostsRemoved', null, Index.root); + } if (Conf['Index Mode'] === 'catalog') { - return $.add(Index.root, nodes); + Index.buildCatalog(threadIDs); } else { - if (Conf['Show Replies']) { - Index.buildReplies(nodes); - } - Index.buildStructure(nodes); - if ((Index.followedThreadID != null) && (post = g.posts[g.BOARD + "." + Index.followedThreadID])) { - return Header.scrollTo(post.nodes.root); - } + Index.buildStructure(threadIDs); } }, - buildSinglePage: function(pageNum) { + threadsOnPage: function(pageNum) { var nodesPerPage, offset; nodesPerPage = Index.threadsNumPerPage; offset = nodesPerPage * (pageNum - 1); - return Index.sortedNodes.slice(offset, offset + nodesPerPage); + return Index.sortedThreadIDs.slice(offset, offset + nodesPerPage); }, - buildStructure: function(nodes) { - var j, len, node, thumb; - for (j = 0, len = nodes.length; j < len; j++) { - node = nodes[j]; - if (thumb = $('img[data-src]', node)) { - thumb.src = thumb.dataset.src; - thumb.removeAttribute('data-src'); - } - $.add(Index.root, [node, $.el('hr')]); + buildStructure: function(threadIDs) { + var k, len1, nodes, thread, threads; + threads = Index.buildThreads(threadIDs, false, Conf['Show Replies']); + nodes = []; + for (k = 0, len1 = threads.length; k < len1; k++) { + thread = threads[k]; + nodes.push(thread.nodes.root, $.el('hr')); } - if (doc.contains(Index.root)) { - $.event('PostsInserted'); + $.add(Index.root, nodes); + if (Index.root.parentNode) { + $.event('PostsInserted', null, Index.root); } - return ThreadHiding.onIndexBuild(nodes); + Index.loaded = true; + }, + buildCatalog: function(threadIDs) { + var fn, i, n, node0; + i = 0; + n = threadIDs.length; + node0 = null; + fn = function() { + var j; + if (node0 && !node0.parentNode) { + return; + } + j = i > 0 && Index.root.parentNode ? n : i + 30; + node0 = Index.buildCatalogPart(threadIDs.slice(i, j))[0]; + i = j; + if (i < n) { + return $.queueTask(fn); + } else { + if (Index.root.parentNode) { + $.event('PostsInserted', null, Index.root); + } + return Index.loaded = true; + } + }; + fn(); + }, + buildCatalogPart: function(threadIDs) { + var k, len1, nodes, thread, threads; + threads = Index.buildThreads(threadIDs, true); + Index.buildCatalogViews(threads); + Index.sizeCatalogViews(threads); + nodes = []; + for (k = 0, len1 = threads.length; k < len1; k++) { + thread = threads[k]; + thread.OP.setCatalogOP(true); + $.add(thread.catalogView.nodes.root, thread.OP.nodes.root); + nodes.push(thread.catalogView.nodes.root); + $.on(thread.catalogView.nodes.root, 'mouseenter', Index.cb.catalogReplies.bind(thread)); + $.on(thread.OP.nodes.root, 'mouseenter', Index.cb.hoverAdjust.bind(thread.OP.nodes)); + } + $.add(Index.root, nodes); + return nodes; }, clearSearch: function() { Index.searchInput.value = ''; @@ -9565,21 +12608,34 @@ Index = (function() { return Index.pageLoad(false); }, querySearch: function(query) { - var keywords; + var keywords, match, regexp; + if ((match = query.match(/^([\w+]+):\/(.*)\/(\w*)$/))) { + try { + regexp = RegExp(match[2], match[3]); + } catch (error) { + return []; + } + return Index.sortedThreadIDs.filter(function(ID) { + return regexp.test(Filter.values(match[1], Index.parsedThreads[ID]).join('\n')); + }); + } if (!(keywords = query.toLowerCase().match(/\S+/g))) { return; } - return Index.sortedNodes.filter(function(threadRoot) { - return Index.searchMatch(Get.threadFromRoot(threadRoot), keywords); + return Index.sortedThreadIDs.filter(function(ID) { + return Index.searchMatch(Index.parsedThreads[ID], keywords); }); }, - searchMatch: function(thread, keywords) { - var file, info, j, k, key, keyword, len, len1, ref, ref1, text; - ref = thread.OP, info = ref.info, file = ref.file; + searchMatch: function(obj, keywords) { + var file, info, k, key, keyword, l, len1, len2, ref, text; + info = obj.info, file = obj.file; + if (info.comment == null) { + info.comment = g.SITE.Build.parseComment(info.commentHTML.innerHTML); + } text = []; - ref1 = ['comment', 'subject', 'name', 'tripcode', 'email']; - for (j = 0, len = ref1.length; j < len; j++) { - key = ref1[j]; + ref = ['comment', 'subject', 'name', 'tripcode']; + for (k = 0, len1 = ref.length; k < len1; k++) { + key = ref[k]; if (key in info) { text.push(info[key]); } @@ -9588,8 +12644,8 @@ Index = (function() { text.push(file.name); } text = text.join(' ').toLowerCase(); - for (k = 0, len1 = keywords.length; k < len1; k++) { - keyword = keywords[k]; + for (l = 0, len2 = keywords.length; l < len2; l++) { + keyword = keywords[l]; if (-1 === text.indexOf(keyword)) { return false; } @@ -9607,7 +12663,10 @@ Polyfill = (function() { Polyfill = { init: function() { - return this.toBlob(); + var base; + this.toBlob(); + $.global(this.toBlob); + (base = Element.prototype).matches || (base.matches = Element.prototype.mozMatchesSelector || Element.prototype.webkitMatchesSelector); }, toBlob: function() { if (HTMLCanvasElement.prototype.toBlob) { @@ -9615,9 +12674,6 @@ Polyfill = (function() { } HTMLCanvasElement.prototype.toBlob = function(cb, type, encoderOptions) { var data, i, j, l, ref, ui8a, url; - if (type == null) { - type = 'image/png'; - } url = this.toDataURL(type, encoderOptions); data = atob(url.slice(url.indexOf(',') + 1)); l = data.length; @@ -9626,10 +12682,9 @@ Polyfill = (function() { ui8a[i] = data.charCodeAt(i); } return cb(new Blob([ui8a], { - type: type + type: type || 'image/png' })); }; - return $.globalEval("HTMLCanvasElement.prototype.toBlob = (" + HTMLCanvasElement.prototype.toBlob + ");"); } }; @@ -9644,7 +12699,7 @@ Settings = (function() { Settings = { init: function() { - var add, link, settings; + var add, link; link = $.el('a', { className: 'settings-link fa fa-wrench', textContent: 'Settings', @@ -9663,14 +12718,25 @@ Settings = (function() { $.on(d, 'OpenSettings', function(e) { return Settings.open(e.detail); }); - if (Conf['Disable Native Extension']) { + if (g.SITE.software === 'yotsuba' && Conf['Disable Native Extension']) { if ($.hasStorage) { - settings = JSON.parse(localStorage.getItem('4chan-settings')) || {}; - if (settings.disableAll) { - return; - } - settings.disableAll = true; - return localStorage.setItem('4chan-settings', JSON.stringify(settings)); + return $.global(function() { + var settings; + try { + settings = JSON.parse(localStorage.getItem('4chan-settings')) || {}; + if (settings.disableAll) { + return; + } + settings.disableAll = true; + return localStorage.setItem('4chan-settings', JSON.stringify(settings)); + } catch (error) { + return Object.defineProperty(window, 'Config', { + value: { + disableAll: true + } + }); + } + }); } else { return $.global(function() { return Object.defineProperty(window, 'Config', { @@ -9683,21 +12749,14 @@ Settings = (function() { } }, open: function(openSection) { - var dialog, j, len, link, links, overlay, ref, section, sectionToOpen; - if (Settings.overlay) { + var dialog, j, len, link, links, ref, section, sectionToOpen; + if (Settings.dialog) { return; } $.event('CloseMenu'); Settings.dialog = dialog = $.el('div', { - id: 'fourchanx-settings', - className: 'dialog' - }); - $.extend(dialog, { - innerHTML: "
          " - }); - Settings.overlay = overlay = $.el('div', { id: 'overlay' - }); + }, {innerHTML: ""}); $.on($('.export', dialog), 'click', Settings["export"]); $.on($('.import', dialog), 'click', Settings["import"]); $.on($('.reset', dialog), 'click', Settings.reset); @@ -9723,9 +12782,12 @@ Settings = (function() { (sectionToOpen ? sectionToOpen : links[0]).click(); } $.on($('.close', dialog), 'click', Settings.close); - $.on(overlay, 'click', Settings.close); $.on(window, 'beforeunload', Settings.close); - $.add(d.body, [overlay, dialog]); + $.on(dialog, 'click', Settings.close); + $.on(dialog.firstElementChild, 'click', function(e) { + return e.stopPropagation(); + }); + $.add(d.body, dialog); return $.event('OpenSettings', null, dialog); }, close: function() { @@ -9736,9 +12798,7 @@ Settings = (function() { if ((ref = d.activeElement) != null) { ref.blur(); } - $.rm(Settings.overlay); $.rm(Settings.dialog); - delete Settings.overlay; return delete Settings.dialog; }, sections: [], @@ -9773,32 +12833,28 @@ Settings = (function() { if ($.cantSync) { why = $.cantSet ? 'save your settings' : 'synchronize settings between tabs'; return cb($.el('li', { - textContent: "4chan X needs local storage to " + why + ".\nEnable it on boards.4chan.org in your browser's privacy settings (may be listed as part of \"local data\" or \"cookies\")." + textContent: "4chan X needs local storage to " + why + ".\nEnable it on boards." + (location.hostname.split('.')[1]) + ".org in your browser's privacy settings (may be listed as part of \"local data\" or \"cookies\")." })); } }, ads: function(cb) { - return $.onExists(doc, '.ad-cnt', function(ad) { - return $.onExists(ad, 'img', function() { + return $.onExists(doc, '.adg-rects > .desktop', function(ad) { + return $.onExists(ad, 'iframe', function() { var url; url = Redirect.to('thread', { boardID: 'qa', threadID: 362590 }); - return cb($.el('li', { - innerHTML: "To protect yourself from malicious ads, you should block ads on 4chan." - })); + return cb($.el('li', {innerHTML: "To protect yourself from malicious ads, you should block ads on 4chan."})); }); }); } }, main: function(section) { - var addWarning, arr, button, container, containers, description, div, fs, input, inputs, items, key, level, obj, ref, ref1, warning, warnings; + var addCheckboxes, addWarning, button, div, fs, inputs, items, key, keyFS, obj, ref, ref1, warning, warnings; warnings = $.el('fieldset', { hidden: true - }, { - innerHTML: "Warnings
            " - }); + }, {innerHTML: "Warnings
              "}); addWarning = function(item) { $.add($('ul', warnings), item); return warnings.hidden = false; @@ -9809,28 +12865,24 @@ Settings = (function() { warning(addWarning); } $.add(section, warnings); - items = {}; - inputs = {}; - ref1 = Config.main; - for (key in ref1) { - obj = ref1[key]; - fs = $.el('fieldset', { - innerHTML: "" + E(key) + "" - }); - containers = [fs]; + items = $.dict(); + inputs = $.dict(); + addCheckboxes = function(root, obj) { + var arr, container, containers, description, div, input, level, results; + containers = [root]; + results = []; for (key in obj) { arr = obj[key]; - description = arr[1]; - div = $.el('div', { - innerHTML: ": " + E(description) + "" - }); - if ($.engine !== 'gecko' && key === 'Remember QR Size') { - div.hidden = true; + if (!(arr instanceof Array)) { + continue; } + description = arr[1]; + div = $.el('div', {innerHTML: ": " + E(description) + ""}); + div.dataset.name = key; input = $('input', div); + $.on(input, 'change', $.cb.checked); $.on(input, 'change', function() { - this.parentNode.parentNode.dataset.checked = this.checked; - return $.cb.checked.call(this); + return this.parentNode.parentNode.dataset.checked = this.checked; }); items[key] = Conf[key]; inputs[key] = input; @@ -9844,10 +12896,30 @@ Settings = (function() { } else if (containers.length > level + 1) { containers.splice(level + 1, containers.length - (level + 1)); } - $.add(containers[level], div); + results.push($.add(containers[level], div)); + } + return results; + }; + ref1 = Config.main; + for (keyFS in ref1) { + obj = ref1[keyFS]; + fs = $.el('fieldset', {innerHTML: "" + E(keyFS) + ""}); + addCheckboxes(fs, obj); + if (keyFS === 'Posting and Captchas') { + $.add(fs, $.el('p', {innerHTML: "For more info on captcha options and issues, see the captcha FAQ."})); } $.add(section, fs); } + addCheckboxes($('div[data-name="JSON Index"] > .suboption-list', section), Config.Index); + if ($.engine !== 'gecko') { + $('div[data-name="Remember QR Size"]', section).hidden = true; + } + if ($.perProtocolSettings || location.protocol !== 'https:') { + $('div[data-name="Redirect to HTTPS"]', section).hidden = true; + } + if ($.platform !== 'crx') { + $('div[data-name="Work around CORB Bug"]', section).hidden = true; + } $.get(items, function(items) { var val; for (key in items) { @@ -9856,25 +12928,46 @@ Settings = (function() { inputs[key].parentNode.parentNode.dataset.checked = val; } }); - div = $.el('div', { - innerHTML: ": Clear manually-hidden threads and posts on all boards. Reload the page to apply." - }); + div = $.el('div', {innerHTML: ": Clear manually-hidden threads and posts on all boards. Reload the page to apply."}); button = $('button', div); $.get({ - hiddenThreads: {}, - hiddenPosts: {} + hiddenThreads: $.dict(), + hiddenPosts: $.dict() }, function(arg) { - var ID, board, hiddenNum, hiddenPosts, hiddenThreads, ref2, ref3, thread; + var ID, board, hiddenNum, hiddenPosts, hiddenThreads, ref2, ref3, ref4, ref5, site, thread; hiddenThreads = arg.hiddenThreads, hiddenPosts = arg.hiddenPosts; hiddenNum = 0; - ref2 = hiddenThreads.boards; - for (ID in ref2) { - board = ref2[ID]; - hiddenNum += Object.keys(board).length; + for (ID in hiddenThreads) { + site = hiddenThreads[ID]; + if (ID !== 'boards') { + ref2 = site.boards; + for (ID in ref2) { + board = ref2[ID]; + hiddenNum += Object.keys(board).length; + } + } } - ref3 = hiddenPosts.boards; + ref3 = hiddenThreads.boards; for (ID in ref3) { board = ref3[ID]; + hiddenNum += Object.keys(board).length; + } + for (ID in hiddenPosts) { + site = hiddenPosts[ID]; + if (ID !== 'boards') { + ref4 = site.boards; + for (ID in ref4) { + board = ref4[ID]; + for (ID in board) { + thread = board[ID]; + hiddenNum += Object.keys(thread).length; + } + } + } + } + ref5 = hiddenPosts.boards; + for (ID in ref5) { + board = ref5[ID]; for (ID in board) { thread = board[ID]; hiddenNum += Object.keys(thread).length; @@ -9884,10 +12977,13 @@ Settings = (function() { }); $.on(button, 'click', function() { this.textContent = 'Hidden: 0'; - return $.get('hiddenThreads', {}, function(arg) { - var boardID, hiddenThreads; + return $.get('hiddenThreads', $.dict(), function(arg) { + var boardID, hiddenThreads, ref2; hiddenThreads = arg.hiddenThreads; - if ($.hasStorage) { + if ($.hasStorage && g.SITE.software === 'yotsuba') { + for (boardID in (ref2 = hiddenThreads['4chan.org']) != null ? ref2.boards : void 0) { + localStorage.removeItem("4chan-hide-t-" + boardID); + } for (boardID in hiddenThreads.boards) { localStorage.removeItem("4chan-hide-t-" + boardID); } @@ -9898,19 +12994,27 @@ Settings = (function() { return $.after($('input[name="Stubs"]', section).parentNode.parentNode, div); }, "export": function() { - return $.get(Conf, function(Conf) { + var Conf2; + Conf2 = $.dict(); + $.extend(Conf2, Conf); + return $.get(Conf2, function(Conf2) { + delete Conf2['boardConfig']; return Settings.downloadExport({ version: g.VERSION, date: Date.now(), - Conf: Conf + Conf: Conf2 }); }); }, downloadExport: function(data) { - var a, p; + var a, blob, p, url; + blob = new Blob([JSON.stringify(data, null, 2)], { + type: 'application/json' + }); + url = URL.createObjectURL(blob); a = $.el('a', { download: "4chan X 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); $.rmAll(p); @@ -9935,15 +13039,15 @@ Settings = (function() { reader.onload = function(e) { var err; try { - return Settings.loadSettings(JSON.parse(e.target.result), function(err) { + return Settings.loadSettings($.dict.json(e.target.result), function(err) { if (err) { return output.textContent = 'Import failed due to an error.'; } else if (confirm('Import successful. Reload now?')) { return window.location.reload(); } }); - } catch (_error) { - err = _error; + } catch (error) { + err = error; output.textContent = 'Import failed due to an error.'; return c.error(err.stack); } @@ -9968,17 +13072,20 @@ Settings = (function() { 'Disable 4chan\'s extension': 'Disable Native Extension', 'Comment Auto-Expansion': '', 'Remove Slug': '', + 'Always HTTPS': 'Redirect to HTTPS', 'Check for Updates': '', 'Recursive Filtering': 'Recursive Hiding', 'Reply Hiding': 'Reply Hiding Buttons', 'Thread Hiding': 'Thread Hiding Buttons', 'Show Stubs': 'Stubs', 'Image Auto-Gif': 'Replace GIF', + 'Expand All WebM': 'Expand videos', 'Reveal Spoilers': 'Reveal Spoiler Thumbnails', 'Expand From Current': 'Expand from here', 'Current Page': 'Page Count in Stats', 'Current Page Position': '', 'Alternative captcha': 'Use Recaptcha v1', + 'Alt index captcha': 'Use Recaptcha v1 on Index', 'Auto Submit': 'Post on Captcha Completion', 'Open Reply in New Tab': 'Open Post in New Tab', 'Remember QR size': 'Remember QR Size', @@ -10000,6 +13107,7 @@ Settings = (function() { 'spoiler': 'Spoiler tags', 'sageru': 'Toggle sage', 'code': 'Code tags', + 'sjis': 'SJIS tags', 'submit': 'Submit QR', 'watch': 'Watch', 'update': 'Update', @@ -10020,6 +13128,10 @@ Settings = (function() { 'Scrolling': 'Auto Scroll', 'Verbose': '' }); + if ('Always CDN' in data.Conf) { + data.Conf['fourchanImageHost'] = data.Conf['Always CDN'] ? 'i.4cdn.org' : ''; + delete data.Conf['Always CDN']; + } data.Conf.sauces = data.Conf.sauces.replace(/\$\d/g, function(c) { switch (c) { case '$1': @@ -10046,15 +13158,17 @@ Settings = (function() { } } if (data.WatchedThreads) { - data.Conf['watchedThreads'] = { - boards: {} - }; + data.Conf['watchedThreads'] = $.dict.clone({ + '4chan.org': { + boards: {} + } + }); ref1 = data.WatchedThreads; for (boardID in ref1) { threads = ref1[boardID]; for (threadID in threads) { threadData = threads[threadID]; - ((base = data.Conf['watchedThreads'].boards)[boardID] || (base[boardID] = {}))[threadID] = { + ((base = data.Conf['watchedThreads']['4chan.org'].boards)[boardID] || (base[boardID] = $.dict()))[threadID] = { excerpt: threadData.textContent }; } @@ -10064,11 +13178,16 @@ Settings = (function() { } }, upgrade: function(data, version) { - var addCSS, addSauces, boardID, changes, compareString, j, key, len, name, record, ref, ref1, ref2, ref3, ref4, ref5, rice, set, type, uids, value; - changes = {}; + var addCSS, addSauces, boardID, boards, changes, compareString, corrupted, db, hostname, j, k, key, l, lastChecked, len, len1, len2, len3, line, list, m, name, record, ref, ref1, ref10, ref11, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, rice, set, setD, siteProperties, software, type, uids, val, val2, value; + changes = $.dict(); set = function(key, value) { return data[key] = changes[key] = value; }; + setD = function(key, value) { + if (data[key] == null) { + return set(key, value); + } + }; addSauces = function(sauces) { if (data['sauces'] != null) { sauces = sauces.filter(function(s) { @@ -10087,9 +13206,35 @@ Settings = (function() { return set('usercss', css + '\n\n' + data['usercss']); } }; + if ((corrupted = version[0] === '"')) { + try { + version = JSON.parse(version); + } catch (error) {} + } compareString = version.replace(/\d+/g, function(x) { return ('0000' + x).slice(-5); }); + if (compareString < '00001.00013.00014.00008') { + for (key in data) { + val = data[key]; + if (!(typeof val === 'string' && typeof Conf[key] !== 'string' && (key !== 'Index Sort' && key !== 'Last Long Reply Thresholds 0' && key !== 'Last Long Reply Thresholds 1'))) { + continue; + } + corrupted = true; + break; + } + } + if (corrupted) { + for (key in data) { + val = data[key]; + if (typeof val === 'string') { + try { + val2 = JSON.parse(val); + set(key, val2); + } catch (error) {} + } + } + } if (compareString < '00001.00011.00008.00000') { if (data['Fixed Thread Watcher'] == null) { set('Fixed Thread Watcher', (ref = data['Toggleable Thread Watcher']) != null ? ref : true); @@ -10118,7 +13263,7 @@ Settings = (function() { record = ref2[boardID]; for (type in record) { name = record[type]; - if (name in uids) { + if ($.hasOwn(uids, name)) { record[type] = uids[name]; } } @@ -10147,7 +13292,7 @@ Settings = (function() { set('sauces', data['sauces'].replace(/^(#?\s*)http:\/\/iqdb\.org\//mg, '$1//iqdb.org/')); } } - if (compareString < '00001.00011.00019.00003' && !Settings.overlay) { + if (compareString < '00001.00011.00019.00003' && !Settings.dialog) { $.queueTask(function() { return Settings.warnings.ads(function(item) { return new Notice('warning', slice.call(item.childNodes)); @@ -10226,10 +13371,153 @@ Settings = (function() { addCSS('.qr-link-container-bottom {display: none;}'); } } + if (compareString < '00001.00012.00000.00006') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/^(#?\s*)https:\/\/(?:desustorage|cuckchan)\.org\//mg, '$1https://desuarchive.org/')); + } + } + if (compareString < '00001.00012.00001.00000') { + if ((data['Persistent Thread Watcher'] == null) && (data['Toggleable Thread Watcher'] != null)) { + set('Persistent Thread Watcher', !data['Toggleable Thread Watcher']); + } + } + if (compareString < '00001.00012.00003.00000') { + ref6 = ['Image Hover in Catalog', 'Auto Watch', 'Auto Watch Reply']; + for (k = 0, len1 = ref6.length; k < len1; k++) { + key = ref6[k]; + setD(key, false); + } + } + if (compareString < '00001.00013.00001.00002') { + addSauces(['#//www.bing.com/images/search?q=imgurl:%IMG&view=detailv2&iss=sbi#enterInsights']); + } + if (compareString < '00001.00013.00005.00000') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/^(#?\s*)http:\/\/regex\.info\/exif\.cgi/mg, '$1http://exif.regex.info/exif.cgi')); + } + addSauces(Config['sauces'].match(/# Known filename formats:(?:\n.+)*|$/)[0].split('\n')); + } + if (compareString < '00001.00013.00007.00002') { + setD('Require OP Quote Link', true); + } + if (compareString < '00001.00013.00008.00000') { + setD('Download Link', true); + } + if (compareString < '00001.00013.00009.00003') { + if (data['jsWhitelist'] != null) { + list = data['jsWhitelist'].split('\n'); + if (indexOf.call(list, 'https://cdnjs.cloudflare.com') < 0 && indexOf.call(list, 'https://cdn.mathjax.org') >= 0) { + set('jsWhitelist', data['jsWhitelist'] + '\n\nhttps://cdnjs.cloudflare.com'); + } + } + } + if (compareString < '00001.00014.00000.00006') { + if (data['siteSoftware'] != null) { + set('siteSoftware', data['siteSoftware'] + '\n4cdn.org yotsuba'); + } + } + if (compareString < '00001.00014.00003.00002') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/^(#?\s*)https:\/\/whatanime\.ga\//mg, '$1https://trace.moe/')); + } + } + if (compareString < '00001.00014.00004.00004') { + if ((data['siteSoftware'] != null) && !/^4channel\.org yotsuba$/m.test(data['siteSoftware'])) { + set('siteSoftware', data['siteSoftware'] + '\n4channel.org yotsuba'); + } + } + if (compareString < '00001.00014.00005.00000') { + ref7 = DataBoard.keys; + for (l = 0, len2 = ref7.length; l < len2; l++) { + db = ref7[l]; + if ((ref8 = data[db]) != null ? ref8.boards : void 0) { + ref9 = data[db], boards = ref9.boards, lastChecked = ref9.lastChecked; + data[db]['4chan.org'] = { + boards: boards, + lastChecked: lastChecked + }; + delete data[db].boards; + delete data[db].lastChecked; + set(db, data[db]); + } + } + if ((data['siteSoftware'] != null) && (data['siteProperties'] == null)) { + siteProperties = $.dict(); + ref10 = data['siteSoftware'].split('\n'); + for (m = 0, len3 = ref10.length; m < len3; m++) { + line = ref10[m]; + ref11 = line.split(' '), hostname = ref11[0], software = ref11[1]; + siteProperties[hostname] = { + software: software + }; + } + set('siteProperties', siteProperties); + } + } + if (compareString < '00001.00014.00006.00006') { + if (data['sauces'] != null) { + 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}-/')); + } + } + if (compareString < '00001.00014.00008.00000') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/https:\/\/www\.yandex\.com\/images\/search/g, 'https://yandex.com/images/search')); + } + } + if (compareString < '00001.00014.00009.00000') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/^(#?\s*)(?:http:)?\/\/(www\.pixiv\.net|www\.deviantart\.com|imgur\.com|flickr\.com)\//mg, '$1https://$2/')); + set('sauces', data['sauces'].replace(/https:\/\/yandex\.com\/images\/search\?rpt=imageview&img_url=%IMG/g, 'https://yandex.com/images/search?rpt=imageview&url=%IMG')); + } + } + if (compareString < '00001.00014.00009.00001') { + if ((data['Use Faster Image Host'] != null) && (data['fourchanImageHost'] == null)) { + set('fourchanImageHost', (data['Use Faster Image Host'] ? 'i.4cdn.org' : '')); + } + } + if (compareString < '00001.00014.00010.00001') { + if (data['Filter in Native Catalog'] == null) { + set('Filter in Native Catalog', false); + } + } + if (compareString < '00001.00014.00012.00008') { + if (data['boardnav'] == null) { + set('boardnav', "[ toggle-all ]\na-replace\nc-replace\ng-replace\nk-replace\nv-replace\nvg-replace\nvr-replace\nck-replace\nco-replace\nfit-replace\njp-replace\nmu-replace\nsp-replace\ntv-replace\nvp-replace\n[external-text:\"FAQ\",\"https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions\"]"); + } + } + if (compareString < '00001.00014.00016.00001') { + if (data['archiveLists'] != null) { + set('archiveLists', data['archiveLists'].replace('https://mayhemydg.github.io/archives.json/archives.json', 'https://nstepien.github.io/archives.json/archives.json')); + } + } + if (compareString < '00001.00014.00016.00007') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/https:\/\/www\.deviantart\.com\/gallery\/#\/d%\$1%\$2;regexp:\/\^\\w\+_by_\\w\+\[_-\]d\(\[\\da-z\]\{6\}\)\\b\|\^d\(\[\\da-z\]\{6\}\)-\[\\da-z\]\{8\}-\//g, 'javascript:void(open("https://www.deviantart.com/"+%$1.replace(/_/g,"-")+"/art/"+parseInt(%$2,36)));regexp:/^\\w+_by_(\\w+)[_-]d([\\da-z]{6})\\b/').replace(/\/\/imgops\.com\/%URL/g, '//imgops.com/start?url=%URL')); + } + } + if (compareString < '00001.00014.00017.00002') { + if (data['jsWhitelist'] != null) { + set('jsWhitelist', data['jsWhitelist'] + '\n\nhttps://hcaptcha.com\nhttps://*.hcaptcha.com'); + } + } + if (compareString < '00001.00014.00020.00004') { + if (data['archiveLists'] != null) { + set('archiveLists', data['archiveLists'].replace('https://nstepien.github.io/archives.json/archives.json', 'https://4chenz.github.io/archives.json/archives.json')); + } + } + if (compareString < '00001.00014.00022.00003') { + if (data['sauces'] != null) { + set('sauces', data['sauces'].replace(/^#?\s*https:\/\/www\.google\.com\/searchbyimage\?image_url=%(IMG|T?URL)&safe=off(?=$|;)/mg, 'https://www.google.com/searchbyimage?sbisrc=4chanx&image_url=%$1&safe=off')); + if (compareString === '00001.00014.00022.00002' && !/\bsbisrc=/.test(data['sauces'])) { + set('sauces', data['sauces'].replace(/^#?\s*https:\/\/lens\.google\.com\/uploadbyurl\?url=%(IMG|T?URL)(?=$|;)/m, 'https://www.google.com/searchbyimage?sbisrc=4chanx&image_url=%$1&safe=off')); + } + } + addSauces(['#https://lens.google.com/uploadbyurl?url=%IMG;text:lens']); + } return changes; }, loadSettings: function(data, cb) { - if (data.version.split('.')[0] === '2') { + if (data.version.split('.')[0] === '2' && "Disable 4chan's extension" in data.Conf) { data = Settings.convertFrom.loadletter(data); } else if (data.version !== g.VERSION) { Settings.upgrade(data.Conf, data.version); @@ -10254,58 +13542,59 @@ Settings = (function() { }, filter: function(section) { var select; - $.extend(section, { - innerHTML: "
              " - }); + $.extend(section, {innerHTML: "
              "}); select = $('select', section); $.on(select, 'change', Settings.selectFilter); return Settings.selectFilter.call(select); }, selectFilter: function() { - var div, name, ta; + var div, filterTypes, name, ta; div = this.nextElementSibling; if ((name = this.value) !== 'guide') { + if (!$.hasOwn(Config.filter, name)) { + return; + } $.rmAll(div); ta = $.el('textarea', { name: name, className: 'field', spellcheck: false }); + $.on(ta, 'change', $.cb.value); $.get(name, Conf[name], function(item) { - return ta.value = item[name]; + ta.value = item[name]; + return $.add(div, ta); }); - $.on(ta, 'change', $.cb.value); - $.add(div, ta); return; } - $.extend(div, { - innerHTML: "
              Filter is disabled.

              Use regular expressions, one per line.
              Lines starting with a # will be ignored.
              For example, /weeaboo/i will filter posts containing the string \`weeaboo\`, case-insensitive.
              MD5 filtering uses exact string matching, not regular expressions.

                You can use these settings with each regular expression, separate them with semicolons:
              • Per boards, separate them with commas. It is global if not specified.
                For example: boards:a,jp;.
              • In case of a global rule, select boards to be excluded from the filter.
                For example: exclude:vg,v;.
              • Filter OPs only along with their threads (\`only\`), replies only (\`no\`), or both (\`yes\`, this is default).
                For example: op:only;, op:no; or op:yes;.
              • Overrule the \`Show Stubs\` setting if specified: create a stub (\`yes\`) or not (\`no\`).
                For example: stub:yes; or stub:no;.
              • Highlight instead of hiding. You can specify a class name to use with a userstyle.
                For example: highlight; or highlight:wallpaper;.
              • Highlighted OPs will have their threads put on top of the board index by default.
                For example: top:yes; or top:no;.

              Note: If you're using the native catalog rather than 4chan X's catalog, 4chan X's filters do not apply there.
              The native catalog has its own separate filter list.

              " + filterTypes = Object.keys(Config.filter).filter(function(x) { + return x !== 'general'; + }).map(function(x, i) { + return {innerHTML: ((i) ? "," : "") + "" + E(x)}; }); + $.extend(div, {innerHTML: "
              Filter is disabled.

              Use regular expressions, one per line.
              Lines starting with a # will be ignored.
              For example, /weeaboo/i will filter posts containing the string `weeaboo`, case-insensitive.
              MD5 and Unique ID filtering use exact string matching, not regular expressions.

                You can use these settings with each regular expression, separate them with semicolons:
              • Per boards, separate them with commas. It is global if not specified. Use sfw and nsfw to reference all worksafe or not-worksafe boards.
                For example: boards:a,jp;.
                To specify boards on a particular site, put the beginning of the domain and a slash character before the list.
                Any initial www. should not be included, and all 4chan domains are considered 4chan.org.
                For example: boards:4:a,jp,sama:a,z;.
                An asterisk can be used to specify all boards on a site.
                For example: boards:4:*;.
              • Select boards to be excluded from the filter. The syntax is the same as for the boards: option above.
                For example: exclude:vg,v;.
              • Filter OPs only along with their threads (`only`) or replies only (`no`).
                For example: op:only; or op:no;.
              • Filter only posts with files (`only`) or only posts without files (`no`).
                For example: file:only; or file:no;.
              • Overrule the `Show Stubs` setting if specified: create a stub (`yes`) or not (`no`).
                For example: stub:yes; or stub:no;.
              • Highlight instead of hiding. You can specify a class name to use with a userstyle.
                For example: highlight; or highlight:wallpaper;.
              • Highlighted OPs will have their threads put on top of the board index by default.
                For example: top:yes; or top:no;.
              • Show a desktop notification instead of hiding.
                For example: notify;.
              • Filters in the \"General\" section apply to multiple fields, by default subject,name,filename,comment.
                The fields can be specified with the type option, separated by commas.
                For example: type:" + E.cat(filterTypes) + ";.
                Types can also be combined with a + sign; this indicates the filter applies to the given fields joined by newlines.
                For example: type:filename+filesize+dimensions;.
              "}); return $('.warning', div).hidden = Conf['Filter']; }, sauce: function(section) { var ta; - $.extend(section, { - innerHTML: "
              Sauce is disabled.
              Lines starting with a # will be ignored.
              You can specify a display text by appending ;text:[text] to the URL.
              You can specify the applicable boards by appending ;boards:[board1],[board2].
              You can specify the applicable file types by appending ;types:[extension1],[extension2].
                These parameters will be replaced by their corresponding values:
              • %TURL: Thumbnail URL.
              • %URL: Full image URL.
              • %IMG: Full image URL for GIF, JPG, and PNG; thumbnail URL for other types.
              • %MD5: MD5 hash in base64.
              • %sMD5: MD5 hash in base64 using - and _.
              • %hMD5: MD5 hash in hexadecimal.
              • %name: Original file name.
              • %board: Current board.
              • %%, %semi: Literal % and ;.
              " - }); + $.extend(section, {innerHTML: "
              Sauce is disabled.
              These parameters will be replaced by their corresponding values in the URL and displayed text:
              • %IMG: Full image URL for GIF, JPG, and PNG; thumbnail URL for other types.
              • %URL: Full image URL.
              • %TURL: Thumbnail URL.
              • %name: Original file name.
              • %board: Current board.
              • %MD5: MD5 hash in base64.
              • %sMD5: MD5 hash in base64 using - and _.
              • %hMD5: MD5 hash in hexadecimal.
              • %$0: Matched regular expression within the filename.
              • %$1, %$2, %$3, ... : Subexpressions within the matched regular expression.
              • %%, %semi: Literal % and ;.
              Lines starting with a # will be ignored.
              You can specify a display text by appending ;text:[text] to the URL.
              You can specify the applicable boards/sites by appending ;boards:[board1],[board2]. See the Filter guide for details.
              You can specify the applicable file types by appending ;types:[extension1],[extension2].
              You can specify a regular expression the filename must match by appending ;regexp:[regular expression].
              "}); $('.warning', section).hidden = Conf['Sauce']; ta = $('textarea', section); $.get('sauces', Conf['sauces'], function(item) { - return ta.value = item['sauces']; + ta.value = item['sauces']; + return ta.hidden = false; }); return $.on(ta, 'change', $.cb.value); }, advanced: function(section) { - var applyCSS, boardSelect, customCSS, event, input, inputs, interval, items, itemsArchive, j, k, l, len, len1, len2, len3, m, name, ref, ref1, ref2, ref3, table, updateArchives, warning; - $.extend(section, { - innerHTML: "
              Archives
              404 Redirect is disabled.
              Thread redirectionPost fetchingFile redirection

              Archive Lists: Each line below should be an archive list in this format or a URL to load an archive list from.
              Archive properties can be overriden by another item with the same uid (or if absent, its name).
              Last updated:
              Captcha Language
              Choose from list of language codes. Leave blank to autoselect.
              Custom Board Navigation
              New lines will be converted into spaces.

              In the following examples for /g/, g can be changed to a different board ID (a, b, etc...), the current board (current), or the Twitter link (@).
              Board link: g
              Archive link: g-archive
              Internal archive link: g-expired
              Title link: g-title
              Board link (Replace with title when on that board): g-replace
              Full text link: g-full
              Custom text link: g-text:"Install Gentoo"
              Index-only link: g-index
              Catalog-only link: g-catalog
              Index mode: g-mode:"infinite scrolling"
              Index sort: g-sort:"creation date"
              External link: external-text:"Google","http://www.google.com"
              Combinations are possible: g-index-text:"Technology Index"
              Full board list toggle: toggle-all

              [ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:"Piracy"]
              will give you
              [ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
              if you are on /g/.
              Time Formatting is disabled.
              :
              Day: %a, %A, %d, %e
              Month: %m, %b, %B
              Year: %y, %Y
              Hour: %k, %H, %l, %I, %p, %P
              Minute: %M
              Second: %S
              Literal %: %%
              Quote Backlinks formatting is disabled.
              :
              File Info Formatting is disabled.
              :
              Link: %l (truncated), %L (untruncated), %T (4chan filename)
              Filename: %n (truncated), %N (untruncated), %t (4chan filename)
              Download button: %d
              Spoiler indicator: %p
              Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
              Resolution: %r (Displays 'PDF' for PDF files)
              Tag: %g
              Literal %: %%
              Quick Reply Personas

              One item per line.
              Items will be added in the relevant input's auto-completion list.
              Password items will always be used, since there is no password input.
              Lines starting with a # will be ignored.

                You can use these settings with each item, separate them with semicolons:
              • Possible items are: name, options (or equivalently email), subject and password.
              • Wrap values of items with quotes, like this: options:"sage".
              • Force values as defaults with the always keyword, for example: options:"sage";always.
              • Select specific boards for an item, separated with commas, for example: options:"sage";boards:jp;always.
              Unread Favicon is disabled.
              Thread Updater is disabled.
              Interval: seconds
              Custom Cooldown Time
              Seconds:
              Javascript Whitelist
              Sources from which Javascript is allowed to be loaded by Content Security Policy.
              " - }); + var applyCSS, boardSelect, customCSS, event, input, inputs, interval, items, itemsArchive, j, k, l, len, len1, len2, len3, listImageHost, m, name, ref, ref1, ref2, ref3, ref4, table, textContent, updateArchives, warning; + $.extend(section, {innerHTML: "
              Archives
              404 Redirect is disabled.
              Thread redirectionPost fetchingFile redirection

              Archive Lists: Each line below should be an archive list in this format or a URL to load an archive list from.
              Archive properties can be overriden by another item with the same uid (or if absent, its name).
              Last updated:
              External Catalog
              External Catalog is disabled. This will be used only as a fallback.
              URLs of external catalog sites, where %board is to be replaced by the board name.
              Each URL should be followed by ;boards: and optionally ;exclude: and a list of supported/excluded boards in the format explained in the Filter guide.
              Override 4chan Image Host
              Change 4chan image links to this domain. Leave blank for no change.
              Captcha Language
              Choose from list of language codes. Leave blank to autoselect.
              Custom Board Navigation
              New lines will be converted into spaces.

              In the following examples for /g/, g can be changed to a different board ID (a, b, etc...), the current board (current), or the Twitter link (@).
              Board link: g
              Archive link: g-archive
              Internal archive link: g-expired
              Title link: g-title
              Board link (Replace with title when on that board): g-replace
              Full text link: g-full
              Custom text link: g-text:"Install Gentoo"
              Index-only link: g-index
              Catalog-only link: g-catalog
              Index mode: g-mode:"infinite scrolling"
              Index sort: g-sort:"creation date rev"
              External link: external-text:"Google","http://www.google.com"
              Open in new tab: g-nt
              Combinations are possible: g-index-text:"Technology Index"
              Full board list toggle: toggle-all

              [ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:"Piracy"]
              will give you
              [ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
              if you are on /g/.
              Time Formatting is disabled.
              :
              Day: %a, %A, %d, %e
              Month: %m, %b, %B
              Year: %y, %Y
              Hour: %k, %H, %l, %I, %p, %P
              Minute: %M
              Second: %S
              Literal %: %%
              Quote Backlinks formatting is disabled.
              :
              Default pasted content filename
              .png
              File Info Formatting is disabled.
              :
              Link: %l (truncated), %L (untruncated), %T (4chan filename)
              Filename: %n (truncated), %N (untruncated), %t (4chan filename)
              Download button: %d
              Quick filter MD5: %f
              Spoiler indicator: %p
              Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
              Resolution: %r (Displays 'PDF' for PDF files)
              Tag: %g
              Literal %: %%
              Quick Reply Personas

              One item per line.
              Items will be added in the relevant input's auto-completion list.
              Password items will always be used, since there is no password input.
              Lines starting with a # will be ignored.

                You can use these settings with each item, separate them with semicolons:
              • Possible items are: name, options (or equivalently email), subject and password.
              • Wrap values of items with quotes, like this: options:"sage".
              • Force values as defaults with the always keyword, for example: options:"sage";always.
              • Select specific boards for an item, separated with commas, for example: options:"sage";boards:jp;always.
              Unread Favicon is disabled.
              Thread Updater is disabled.
              Interval: seconds
              Custom Cooldown Time
              Seconds:
              For more information about customizing 4chan X's CSS, see the styling guide.
              Javascript Whitelist
              Sources from which Javascript is allowed to be loaded by Content Security Policy.
              Lines starting with a # will be ignored. Remove or comment out all lines to allow everything.
              Known Banners
              List of known banners, used for click-to-change feature.
              "}); ref = $$('.warning', section); for (j = 0, len = ref.length; j < len; j++) { warning = ref[j]; warning.hidden = Conf[warning.dataset.feature]; } - inputs = {}; + inputs = $.dict(); ref1 = $$('[name]', section); for (k = 0, len1 = ref1.length; k < len1; k++) { input = ref1[k]; @@ -10316,13 +13605,14 @@ Settings = (function() { Conf['lastarchivecheck'] = 0; return $.id('lastarchivecheck').textContent = 'never'; }); - items = {}; - ref2 = ['archiveLists', 'archiveAutoUpdate', 'captchaLanguage', 'boardnav', 'time', 'backlink', 'fileInfo', 'QR.personas', 'favicon', 'usercss', 'customCooldown', 'jsWhitelist']; - for (l = 0, len2 = ref2.length; l < len2; l++) { - name = ref2[l]; - items[name] = Conf[name]; + items = $.dict(); + for (name in inputs) { input = inputs[name]; - event = name === 'archiveLists' || name === 'archiveAutoUpdate' || name === 'QR.personas' || name === 'favicon' || name === 'usercss' ? 'change' : 'input'; + if (!(name !== 'Interval' && name !== 'Custom CSS')) { + continue; + } + items[name] = Conf[name]; + event = (input.nodeName === 'SELECT' || ((ref2 = input.type) === 'checkbox' || ref2 === 'radio') || (input.nodeName === 'TEXTAREA' && !(name in Settings))) ? 'change' : 'input'; $.on(input, event, $.cb[input.type === 'checkbox' ? 'checked' : 'value']); if (name in Settings) { $.on(input, event, Settings[name]); @@ -10334,11 +13624,20 @@ Settings = (function() { val = items[key]; input = inputs[key]; input[input.type === 'checkbox' ? 'checked' : 'value'] = val; + input.hidden = false; if (key in Settings) { Settings[key].call(input); } } }); + listImageHost = $.id('list-fourchanImageHost'); + ref3 = ImageHost.suggestions; + for (l = 0, len2 = ref3.length; l < len2; l++) { + textContent = ref3[l]; + $.add(listImageHost, $.el('option', { + textContent: textContent + })); + } interval = inputs['Interval']; customCSS = inputs['Custom CSS']; applyCSS = $('#apply-css', section); @@ -10351,10 +13650,10 @@ Settings = (function() { $.on(applyCSS, 'click', function() { return CustomCSS.update(); }); - itemsArchive = {}; - ref3 = ['archives', 'selectedArchives', 'lastarchivecheck']; - for (m = 0, len3 = ref3.length; m < len3; m++) { - name = ref3[m]; + itemsArchive = $.dict(); + ref4 = ['archives', 'selectedArchives', 'lastarchivecheck']; + for (m = 0, len3 = ref4.length; m < len3; m++) { + name = ref4[m]; itemsArchive[name] = Conf[name]; } $.get(itemsArchive, function(itemsArchive) { @@ -10383,7 +13682,7 @@ Settings = (function() { tbody = $('tbody', section); $.rmAll(boardSelect); $.rmAll(tbody); - archBoards = {}; + archBoards = $.dict(); ref = Conf['archives']; for (j = 0, len = ref.length; j < len; j++) { ref1 = ref[j], uid = ref1.uid, name = ref1.name, boards = ref1.boards, files = ref1.files, software = ref1.software; @@ -10472,9 +13771,7 @@ Settings = (function() { textContent: archive[1] })); } - $.extend(td, { - innerHTML: "" - }); + $.extend(td, {innerHTML: ""}); select = td.firstElementChild; if (!(select.disabled = length === 1)) { select.setAttribute('data-boardid', boardID); @@ -10489,7 +13786,7 @@ Settings = (function() { return function(arg) { var name1, selectedArchives; selectedArchives = arg.selectedArchives; - (selectedArchives[name1 = _this.dataset.boardid] || (selectedArchives[name1] = {}))[_this.dataset.type] = JSON.parse(_this.value); + (selectedArchives[name1 = _this.dataset.boardid] || (selectedArchives[name1] = $.dict()))[_this.dataset.type] = JSON.parse(_this.value); $.set('selectedArchives', selectedArchives); Conf['selectedArchives'] = selectedArchives; return Redirect.selectArchives(); @@ -10502,6 +13799,9 @@ Settings = (function() { time: function() { return this.nextElementSibling.textContent = Time.format(this.value, new Date()); }, + timeLocale: function() { + return Settings.time.call($('[name=time]', Settings.dialog)); + }, backlink: function() { return this.nextElementSibling.textContent = this.value.replace(/%(?:id|%)/g, function(x) { return { @@ -10515,7 +13815,7 @@ Settings = (function() { data = { isReply: true, file: { - url: '//i.4cdn.org/g/1334437723720.jpg', + url: "//" + (ImageHost.host()) + "/g/1334437723720.jpg", name: 'd9bb2efc98dd0df141a94399ff5880b7.jpg', size: '276 KB', sizeInBytes: 276 * 1024, @@ -10529,16 +13829,21 @@ Settings = (function() { return FileInfo.format(this.value, data, this.nextElementSibling); }, favicon: function() { - var img; + var f, i, icon, img, j, len, ref; Favicon["switch"](); if (g.VIEW === 'thread' && Conf['Unread Favicon']) { Unread.update(); } img = this.nextElementSibling.children; - img[0].src = Favicon["default"]; - img[1].src = Favicon.unreadSFW; - img[2].src = Favicon.unreadNSFW; - return img[3].src = Favicon.unreadDead; + f = Favicon; + ref = [f.SFW, f.unreadSFW, f.unreadSFWY, f.NSFW, f.unreadNSFW, f.unreadNSFWY, f.dead, f.unreadDead, f.unreadDeadY]; + for (i = j = 0, len = ref.length; j < len; i = ++j) { + icon = ref[i]; + if (!img[i]) { + $.add(this.nextElementSibling, $.el('img')); + } + img[i].src = icon; + } }, togglecss: function() { if ($('textarea[name=usercss]', $.x('ancestor::fieldset[1]', this)).disabled = $.id('apply-css').disabled = !this.checked) { @@ -10550,19 +13855,15 @@ Settings = (function() { }, keybinds: function(section) { var arr, input, inputs, items, key, ref, tbody, tr; - $.extend(section, { - innerHTML: "
              Keybinds are disabled.
              Allowed keys: a-z, 0-9, Ctrl, Shift, Alt, Meta, Enter, Esc, Up, Down, Right, Left.
              Press Backspace to disable a keybind.
              ActionsKeybinds
              " - }); + $.extend(section, {innerHTML: "
              Keybinds are disabled.
              Allowed keys: a-z, 0-9, Ctrl, Shift, Alt, Meta, Enter, Esc, Up, Down, Right, Left.
              Press Backspace to disable a keybind.
              ActionsKeybinds
              "}); $('.warning', section).hidden = Conf['Keybinds']; tbody = $('tbody', section); - items = {}; - inputs = {}; + items = $.dict(); + inputs = $.dict(); ref = Config.hotkeys; for (key in ref) { arr = ref[key]; - tr = $.el('tr', { - innerHTML: "" + E(arr[1]) + "" - }); + tr = $.el('tr', {innerHTML: "" + E(arr[1]) + ""}); input = $('input', tr); input.name = key; input.spellcheck = false; @@ -10598,22 +13899,24 @@ Settings = (function() { }).call(this); +Test = (function() { + return Test; + +}).call(this); + UI = (function() { - var Menu, checkbox, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove, + var Menu, UI, checkbox, dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, slice = [].slice; - dialog = function(id, position, properties) { + dialog = function(id, properties) { var child, el, i, len, move, ref; el = $.el('div', { className: 'dialog', id: id }); $.extend(el, properties); - el.style.cssText = position; - $.get(id + ".position", position, function(item) { - return el.style.cssText = item[id + ".position"]; - }); + el.style.cssText = Conf[id + ".position"]; move = $('.move', el); $.on(move, 'touchstart mousedown', dragstart); ref = move.children; @@ -10706,7 +14009,7 @@ UI = (function() { $.on(d, 'click CloseMenu', this.close); $.on(d, 'scroll', this.setPosition); $.on(window, 'resize', this.setPosition); - $.add(button, menu); + $.after(button, menu); this.setPosition(); entry = $('.entry', menu); this.focus(entry); @@ -10739,8 +14042,8 @@ UI = (function() { if (!entry.open(data)) { return; } - } catch (_error) { - err = _error; + } catch (error) { + err = error; Main.handleErrors({ message: "Error in building the " + this.type + " menu.", error: err @@ -10943,11 +14246,11 @@ UI = (function() { var bottom, clientX, clientY, left, right, style, top; clientX = e.clientX, clientY = e.clientY; left = clientX - this.dx; - left = left < 10 ? 0 : this.width - left < 10 ? null : left / this.screenWidth * 100 + '%'; + left = left < 10 ? 0 : this.width - left < 10 ? '' : left / this.screenWidth * 100 + '%'; top = clientY - this.dy; - top = top < (10 + this.topBorder) ? this.topBorder + 'px' : this.height - top < (10 + this.bottomBorder) ? null : top / this.screenHeight * 100 + '%'; - right = left === null ? 0 : null; - bottom = top === null ? this.bottomBorder + 'px' : null; + top = top < (10 + this.topBorder) ? this.topBorder + 'px' : this.height - top < (10 + this.bottomBorder) ? '' : top / this.screenHeight * 100 + '%'; + right = left === '' ? 0 : ''; + bottom = top === '' ? this.bottomBorder + 'px' : ''; style = this.style; style.left = left; style.right = right; @@ -10979,8 +14282,9 @@ UI = (function() { }; hoverstart = function(arg) { - var cb, el, endEvents, height, latestEvent, noRemove, o, ref, root; - root = arg.root, el = arg.el, latestEvent = arg.latestEvent, endEvents = arg.endEvents, height = arg.height, cb = arg.cb, noRemove = arg.noRemove; + var cb, el, endEvents, height, latestEvent, noRemove, o, rect, ref, root, width; + root = arg.root, el = arg.el, latestEvent = arg.latestEvent, endEvents = arg.endEvents, height = arg.height, width = arg.width, cb = arg.cb, noRemove = arg.noRemove; + rect = root.getBoundingClientRect(); o = { root: root, el: el, @@ -10992,7 +14296,10 @@ UI = (function() { clientHeight: doc.clientHeight, clientWidth: doc.clientWidth, height: height, - noRemove: noRemove + width: width, + noRemove: noRemove, + clientX: (rect.left + rect.right) / 2, + clientY: (rect.top + rect.bottom) / 2 }; o.hover = hover.bind(o); o.hoverend = hoverend.bind(o); @@ -11020,16 +14327,22 @@ UI = (function() { hoverstart.padding = 25; hover = function(e) { - var clientX, clientY, height, left, ref, right, style, threshold, top; + var clientX, clientY, height, left, marginX, ref, ref1, right, style, threshold, top, width; this.latestEvent = e; height = (this.height || this.el.offsetHeight) + hoverstart.padding; - clientX = e.clientX, clientY = e.clientY; + width = this.width || this.el.offsetWidth; + ref = Conf['Follow Cursor'] ? e : this, clientX = ref.clientX, clientY = ref.clientY; top = this.isImage ? Math.max(0, clientY * (this.clientHeight - height) / this.clientHeight) : Math.max(0, Math.min(this.clientHeight - height, clientY - 120)); threshold = this.clientWidth / 2; if (!this.isImage) { threshold = Math.max(threshold, this.clientWidth - 400); } - ref = clientX <= threshold ? [clientX + 45 + 'px', null] : [null, this.clientWidth - clientX + 45 + 'px'], left = ref[0], right = ref[1]; + marginX = (clientX <= threshold ? clientX : this.clientWidth - clientX) + 45; + if (this.isImage) { + marginX = Math.min(marginX, this.clientWidth - width); + } + marginX += 'px'; + ref1 = clientX <= threshold ? [marginX, ''] : ['', marginX], left = ref1[0], right = ref1[1]; style = this.style; style.top = top + 'px'; style.left = left; @@ -11067,13 +14380,15 @@ UI = (function() { return label; }; - return { + UI = { dialog: dialog, Menu: Menu, hover: hoverstart, checkbox: checkbox }; + return UI; + }).call(this); FappeTyme = (function() { @@ -11082,7 +14397,7 @@ FappeTyme = (function() { FappeTyme = { init: function() { var el, i, indicator, lc, len, ref, ref1, type; - if (!((Conf['Fappe Tyme'] || Conf['Werk Tyme']) && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { + if (!((Conf['Fappe Tyme'] || Conf['Werk Tyme']) && ((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive'))) { return; } this.nodes = {}; @@ -11115,7 +14430,7 @@ FappeTyme = (function() { }); $.on(indicator, 'click', function() { var check; - check = FappeTyme.nodes[this.parentNode.id.replace('shortcut-', '')]; + check = $.getOwn(FappeTyme.nodes, this.parentNode.id.replace('shortcut-', '')); check.checked = !check.checked; return $.event('change', null, check); }); @@ -11134,11 +14449,11 @@ FappeTyme = (function() { }); }, node: function() { - return this.nodes.root.classList.toggle('noFile', !this.file); + return this.nodes.root.classList.toggle('noFile', !this.files.length); }, catalogNode: function() { var file, filename; - file = this.thread.OP.file; + file = this.thread.OP.files[0]; if (!file) { return; } @@ -11170,7 +14485,7 @@ Gallery = (function() { Gallery = { init: function() { var el, ref; - if (!(this.enabled = Conf['Gallery'] && ((ref = g.VIEW) === 'index' || ref === 'thread') && g.BOARD.ID !== 'f')) { + if (!(this.enabled = Conf['Gallery'] && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { return; } this.delay = Conf['Slide Delay']; @@ -11188,20 +14503,28 @@ Gallery = (function() { }); }, node: function() { - var ref; - if (!((ref = this.file) != null ? ref.thumb : void 0)) { - return; - } - if (Gallery.nodes) { - Gallery.generateThumb(this); - Gallery.nodes.total.textContent = Gallery.images.length; - } - if (!Conf['Image Expansion']) { - return $.on(this.file.thumb.parentNode, 'click', Gallery.cb.image); + var file, i, len, ref, results; + ref = this.files; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + if (!file.thumb) { + continue; + } + if (Gallery.nodes) { + Gallery.generateThumb(this, file); + Gallery.nodes.total.textContent = Gallery.images.length; + } + if (!(Conf['Image Expansion'] || (g.SITE.software === 'tinyboard' && Main.jsEnabled))) { + results.push($.on(file.thumbLink, 'click', Gallery.cb.image)); + } else { + results.push(void 0); + } } + return results; }, build: function(image) { - var candidate, cb, dialog, entry, file, i, j, key, len, len1, menuButton, nodes, post, ref, ref1, ref2, ref3, thumb, value; + var candidate, cb, dialog, entry, file, i, j, k, key, len, len1, len2, menuButton, nodes, post, postThumb, ref, ref1, ref2, ref3, thumb, value; cb = Gallery.cb; if (Conf['Fullscreen Gallery']) { $.one(d, 'fullscreenchange mozfullscreenchange webkitfullscreenchange', function() { @@ -11216,20 +14539,19 @@ Gallery = (function() { } Gallery.images = []; nodes = Gallery.nodes = {}; - Gallery.fullIDs = {}; + Gallery.fileIDs = $.dict(); Gallery.slideshow = false; nodes.el = dialog = $.el('div', { id: 'a-gallery' }); - $.extend(dialog, { - innerHTML: "
              " - }); + $.extend(dialog, {innerHTML: "
              "}); ref = { buttons: '.gal-buttons', frame: '.gal-image', name: '.gal-name', count: '.count', total: '.total', + sauce: '.gal-sauce', thumbs: '.gal-thumbnails', next: '.gal-image a', current: '.gal-image img' @@ -11265,18 +14587,24 @@ Gallery = (function() { $.off(d, 'keydown', Keybinds.keydown); } $.on(window, 'resize', Gallery.cb.setHeight); - ref2 = $$('.post .file'); + ref2 = $$(g.SITE.selectors.file.thumb); for (j = 0, len1 = ref2.length; j < len1; j++) { - file = ref2[j]; - post = Get.postFromNode(file); - if (!((ref3 = post.file) != null ? ref3.thumb : void 0)) { + postThumb = ref2[j]; + if (!(post = Get.postFromNode(postThumb))) { continue; } - Gallery.generateThumb(post); - if (!image && Gallery.fullIDs[post.fullID]) { - candidate = post.file.thumb.parentNode; - if (Header.getTopOf(candidate) + candidate.getBoundingClientRect().height >= 0) { - image = candidate; + ref3 = post.files; + for (k = 0, len2 = ref3.length; k < len2; k++) { + file = ref3[k]; + if (!file.thumb) { + continue; + } + Gallery.generateThumb(post, file); + if (!image && Gallery.fileIDs[post.fullID + "." + file.index]) { + candidate = file.thumbLink; + if (Header.getTopOf(candidate) + candidate.getBoundingClientRect().height >= 0) { + image = candidate; + } } } } @@ -11294,27 +14622,28 @@ Gallery = (function() { doc.style.overflow = 'hidden'; return nodes.total.textContent = Gallery.images.length; }, - generateThumb: function(post) { + generateThumb: function(post, file) { var thumb, thumbImg; if (post.isClone || post.isHidden) { return; } - if (!(post.file && post.file.thumb && (post.file.isImage || post.file.isVideo || Conf['PDF in Gallery']))) { + if (!(file && file.thumb && (file.isImage || file.isVideo || Conf['PDF in Gallery']))) { return; } - if (Gallery.fullIDs[post.fullID]) { + if (Gallery.fileIDs[post.fullID + "." + file.index]) { return; } - Gallery.fullIDs[post.fullID] = true; + Gallery.fileIDs[post.fullID + "." + file.index] = true; thumb = $.el('a', { className: 'gal-thumb', - href: post.file.url, + href: file.url, target: '_blank', - title: post.file.name + title: file.name }); thumb.dataset.id = Gallery.images.length; thumb.dataset.post = post.fullID; - thumbImg = post.file.thumb.cloneNode(false); + thumb.dataset.file = file.index; + thumbImg = file.thumb.cloneNode(false); thumbImg.style.cssText = ''; $.add(thumb, thumbImg); $.on(thumb, 'click', Gallery.cb.open); @@ -11324,20 +14653,20 @@ Gallery = (function() { load: function(thumb, errorCB) { var elType, ext, file; ext = thumb.href.match(/\w*$/); - elType = { + elType = $.getOwn({ 'webm': 'video', + 'mp4': 'video', + 'ogv': 'video', 'pdf': 'iframe' - }[ext] || 'img'; - file = $.el(elType, { - title: thumb.title - }); + }, ext) || 'img'; + file = $.el(elType); $.extend(file.dataset, thumb.dataset); $.on(file, 'error', errorCB); file.src = thumb.href; return file; }, open: function(thumb) { - var el, file, newID, nodes, oldID, post, ref; + var el, file, i, len, link, newID, node, nodes, oldID, post, ref, ref1, sauces; nodes = Gallery.nodes; oldID = +nodes.current.dataset.id; newID = +thumb.dataset.id; @@ -11374,12 +14703,24 @@ Gallery = (function() { nodes.name.href = thumb.href; nodes.frame.scrollTop = 0; nodes.next.focus(); + $.rmAll(nodes.sauce); + if (Conf['Sauce'] && Sauce.links && (post = g.posts.get(file.dataset.post))) { + sauces = []; + ref1 = Sauce.links; + for (i = 0, len = ref1.length; i < len; i++) { + link = ref1[i]; + if ((node = Sauce.createSauceLink(link, post, post.files[+file.dataset.file]))) { + sauces.push($.tn(' '), node); + } + } + $.add(nodes.sauce, sauces); + } if (Gallery.slideshow && (newID > oldID || (oldID === Gallery.images.length - 1 && newID === 0))) { Gallery.setupTimer(); } else { Gallery.cb.stop(); } - if (Conf['Scroll to Post'] && (post = g.posts[file.dataset.post])) { + if (Conf['Scroll to Post'] && (post = g.posts.get(file.dataset.post))) { Header.scrollTo(post.nodes.root); } if (isNaN(oldID) || newID === (oldID + 1) % Gallery.images.length) { @@ -11387,19 +14728,21 @@ Gallery = (function() { } }, error: function() { - var ref; + var file, post, ref; if (((ref = this.error) != null ? ref.code : void 0) === MediaError.MEDIA_ERR_DECODE) { return new Notice('error', 'Corrupt or unplayable video', 30); } - if (this.src.split('/')[2] !== 'i.4cdn.org') { + if (ImageCommon.isFromArchive(this)) { return; } - return ImageCommon.error(this, g.posts[this.dataset.post], null, (function(_this) { + post = g.posts.get(this.dataset.post); + file = post.files[+this.dataset.file]; + return ImageCommon.error(this, post, file, null, (function(_this) { return function(url) { if (!url) { return; } - Gallery.images[_this.dataset.id].href = url; + Gallery.images[+_this.dataset.id].href = url; if (Gallery.nodes.current === _this) { return _this.src = url; } @@ -11454,17 +14797,22 @@ Gallery = (function() { case Conf['Close']: case Conf['Open Gallery']: return Gallery.cb.close; - case 'Right': + case Conf['Next Gallery Image']: return Gallery.cb.next; - case 'Enter': + case Conf['Advance Gallery']: return Gallery.cb.advance; - case 'Left': - case '': + case Conf['Previous Gallery Image']: return Gallery.cb.prev; case Conf['Pause']: return Gallery.cb.pause; case Conf['Slideshow']: return Gallery.cb.toggleSlideshow; + case Conf['Rotate image anticlockwise']: + return Gallery.cb.rotateLeft; + case Conf['Rotate image clockwise']: + return Gallery.cb.rotateRight; + case Conf['Download Gallery Image']: + return Gallery.cb.download; } })(); if (!cb) { @@ -11518,6 +14866,11 @@ Gallery = (function() { toggleSlideshow: function() { return Gallery.cb[Gallery.slideshow ? 'stop' : 'start'](); }, + download: function() { + var name; + name = $('.gal-name'); + return name.click(); + }, pause: function() { var current; Gallery.cb.stop(); @@ -11544,6 +14897,22 @@ Gallery = (function() { $.rmClass(Gallery.nodes.buttons, 'gal-playing'); return Gallery.slideshow = false; }, + rotateLeft: function() { + return Gallery.cb.rotate(270); + }, + rotateRight: function() { + return Gallery.cb.rotate(90); + }, + rotate: $.debounce(100, function(delta) { + var current; + current = Gallery.nodes.current; + if (current.nodeName === 'IFRAME') { + return; + } + current.dataRotate = ((current.dataRotate || 0) + delta) % 360; + current.style.transform = "rotate(" + current.dataRotate + "deg)"; + return Gallery.cb.setHeight(); + }), close: function() { $.off(Gallery.nodes.current, 'error', Gallery.error); ImageCommon.pause(Gallery.nodes.current); @@ -11559,7 +14928,7 @@ Gallery = (function() { } } delete Gallery.nodes; - delete Gallery.fullIDs; + delete Gallery.fileIDs; doc.style.overflow = ''; $.off(d, 'keydown', Gallery.cb.keybinds); if (Conf['Keybinds']) { @@ -11572,16 +14941,29 @@ Gallery = (function() { return (this.checked ? $.addClass : $.rmClass)(doc, "gal-" + (this.name.toLowerCase().replace(/\s+/g, '-'))); }, setHeight: $.debounce(100, function() { - var current, dim, frame, height, minHeight, ref, ref1, ref2, style, width; + var containerHeight, containerWidth, current, dim, frame, height, margin, minHeight, ref, ref1, ref2, ref3, style, width; ref = Gallery.nodes, current = ref.current, frame = ref.frame; style = current.style; - if (Conf['Stretch to Fit'] && (dim = (ref1 = g.posts[current.dataset.post]) != null ? ref1.file.dimensions : void 0)) { + if (Conf['Stretch to Fit'] && (dim = (ref1 = g.posts.get(current.dataset.post)) != null ? ref1.files[+current.dataset.file].dimensions : void 0)) { ref2 = dim.split('x'), width = ref2[0], height = ref2[1]; - minHeight = Math.min(doc.clientHeight - 25, height / width * frame.clientWidth); + containerWidth = frame.clientWidth; + containerHeight = doc.clientHeight - 25; + if ((current.dataRotate || 0) % 180 === 90) { + ref3 = [containerHeight, containerWidth], containerWidth = ref3[0], containerHeight = ref3[1]; + } + minHeight = Math.min(containerHeight, height / width * containerWidth); style.minHeight = minHeight + 'px'; - return style.minWidth = (width / height * minHeight) + 'px'; + style.minWidth = (width / height * minHeight) + 'px'; } else { - return style.minHeight = style.minWidth = null; + style.minHeight = style.minWidth = ''; + } + if ((current.dataRotate || 0) % 180 === 90) { + style.maxWidth = Conf['Fit Height'] ? (doc.clientHeight - 25) + "px" : 'none'; + style.maxHeight = Conf['Fit Width'] ? frame.clientWidth + "px" : 'none'; + margin = (current.clientWidth - current.clientHeight) / 2; + return style.margin = margin + "px " + (-margin) + "px"; + } else { + return style.maxWidth = style.maxHeight = style.margin = ''; } }), setDelay: function() { @@ -11632,9 +15014,7 @@ Gallery = (function() { } return results; })(); - delayLabel = $.el('label', { - innerHTML: "Slide Delay: " - }); + delayLabel = $.el('label', {innerHTML: "Slide Delay: "}); delayInput = delayLabel.firstElementChild; delayInput.value = Gallery.delay; $.on(delayInput, 'change', Gallery.cb.setDelay); @@ -11652,7 +15032,8 @@ Gallery = (function() { }).call(this); ImageCommon = (function() { - var ImageCommon; + var ImageCommon, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; ImageCommon = { pause: function(video) { @@ -11663,6 +15044,17 @@ ImageCommon = (function() { $.off(video, 'volumechange', Volume.change); return video.muted = true; }, + rewind: function(el) { + if (el.nodeName === 'VIDEO') { + if (el.readyState >= el.HAVE_METADATA) { + return el.currentTime = 0; + } + } else if (/\.gif$/.test(el.src)) { + return $.queueTask(function() { + return el.src = el.src; + }); + } + }, pushCache: function(el) { ImageCommon.cache = el; return $.on(el, 'error', ImageCommon.cacheError); @@ -11679,74 +15071,95 @@ ImageCommon = (function() { return delete ImageCommon.cache; } }, - decodeError: function(file, post) { + decodeError: function(file, fileObj) { var message, ref; if (((ref = file.error) != null ? ref.code : void 0) !== MediaError.MEDIA_ERR_DECODE) { return false; } - if (!(message = $('.warning', post.file.thumb.parentNode))) { + if (!(message = $('.warning', fileObj.thumb.parentNode))) { message = $.el('div', { className: 'warning' }); - $.after(post.file.thumb, message); + $.after(fileObj.thumb, message); } message.textContent = 'Error: Corrupt or unplayable video'; return true; }, - error: function(file, post, delay, cb) { - var URL, redirect, src, timeoutID; - src = post.file.url.split('/'); - URL = Redirect.to('file', { - boardID: post.board.ID, - filename: src[src.length - 1] - }); - if (!(Conf['404 Redirect'] && URL && Redirect.securityCheck(URL))) { - URL = null; + isFromArchive: function(file) { + return g.SITE.software === 'yotsuba' && !ImageHost.test(file.src.split('/')[2]); + }, + error: function(file, post, fileObj, delay, cb) { + var base, parseJSON, redirect, src, threadJSON, timeoutID, url; + src = fileObj.url.split('/'); + url = null; + if (g.SITE.software === 'yotsuba' && Conf['404 Redirect']) { + url = Redirect.to('file', { + boardID: post.board.ID, + filename: src[src.length - 1] + }); + } + if (!(url && Redirect.securityCheck(url))) { + url = null; } - if ((post.isDead || post.file.isDead) && file.src.split('/')[2] === 'i.4cdn.org') { - return cb(URL); + if ((post.isDead || fileObj.isDead) && !ImageCommon.isFromArchive(file)) { + return cb(url); } if (delay != null) { timeoutID = setTimeout((function() { - return cb(URL); + return cb(url); }), delay); } - if (post.isDead || post.file.isDead) { + if (post.isDead || fileObj.isDead) { return; } redirect = function() { - if (file.src.split('/')[2] === 'i.4cdn.org') { + if (!ImageCommon.isFromArchive(file)) { if (delay != null) { clearTimeout(timeoutID); } - return cb(URL); + return cb(url); + } + }; + threadJSON = typeof (base = g.SITE.urls).threadJSON === "function" ? base.threadJSON(post) : void 0; + if (!threadJSON) { + return; + } + parseJSON = function(isArchiveURL) { + var archivedThreadJSON, base1, i, len, postObj, ref, ref1; + if (this.status === 404) { + if (!isArchiveURL && (archivedThreadJSON = typeof (base1 = g.SITE.urls).archivedThreadJSON === "function" ? base1.archivedThreadJSON(post) : void 0)) { + $.ajax(archivedThreadJSON, { + onloadend: function() { + return parseJSON.call(this, true); + } + }); + } else { + post.kill(!post.isClone, fileObj.index); + } + } + if (this.status !== 200) { + return redirect(); + } + ref = this.response.posts; + for (i = 0, len = ref.length; i < len; i++) { + postObj = ref[i]; + if (postObj.no === post.ID) { + break; + } + } + if (postObj.no !== post.ID) { + post.kill(); + return redirect(); + } else if (ref1 = fileObj.docIndex, indexOf.call(g.SITE.Build.parseJSON(postObj, post.board).filesDeleted, ref1) >= 0) { + post.kill(true); + return redirect(); + } else { + return url = fileObj.url; } }; - return $.ajax("//a.4cdn.org/" + post.board + "/thread/" + post.thread + ".json", { - onload: function() { - var i, len, postObj, ref; - if (this.status === 404) { - post.kill(!post.isClone); - } - if (this.status !== 200) { - return redirect(); - } - ref = this.response.posts; - for (i = 0, len = ref.length; i < len; i++) { - postObj = ref[i]; - if (postObj.no === post.ID) { - break; - } - } - if (postObj.no !== post.ID) { - post.kill(); - return redirect(); - } else if (postObj.filedeleted) { - post.kill(true); - return redirect(); - } else { - return URL = post.file.url; - } + return $.ajax(threadJSON, { + onloadend: function() { + return parseJSON.call(this); } }); }, @@ -11768,12 +15181,12 @@ ImageCommon = (function() { return (Conf['Show Controls'] && Conf['Click Passthrough'] && e.target.nodeName === 'VIDEO') || (e.target.controls && e.target.getBoundingClientRect().bottom - e.clientY < 35); }, download: function(e) { - var download, href; + var download, href, ref; if (this.protocol === 'blob:') { return true; } e.preventDefault(); - href = this.href, download = this.download; + ref = this, href = ref.href, download = ref.download; return CrossOrigin.file(href, function(blob) { var a; if (blob) { @@ -11803,7 +15216,7 @@ ImageExpand = (function() { ImageExpand = { init: function() { var ref; - if (!(this.enabled = Conf['Image Expansion'] && ((ref = g.VIEW) === 'index' || ref === 'thread') && g.BOARD.ID !== 'f')) { + if (!(this.enabled = Conf['Image Expansion'] && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { return; } this.EAI = $.el('a', { @@ -11818,9 +15231,7 @@ ImageExpand = (function() { this.videoControls = $.el('span', { className: 'video-controls' }); - $.extend(this.videoControls, { - innerHTML: " contract" - }); + $.extend(this.videoControls, {innerHTML: " contract"}); return Callbacks.Post.push({ name: 'Image Expansion', cb: this.node @@ -11831,7 +15242,7 @@ ImageExpand = (function() { if (!(this.file && (this.file.isImage || this.file.isVideo))) { return; } - $.on(this.file.thumb.parentNode, 'click', ImageExpand.cb.toggle); + $.on(this.file.thumbLink, 'click', ImageExpand.cb.toggle); if (this.isClone) { if (this.file.isExpanding) { ImageExpand.contract(this); @@ -11848,7 +15259,7 @@ ImageExpand = (function() { cb: { toggle: function(e) { var file, post, ref; - if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { + if ($.modifiedClick(e)) { return; } post = Get.postFromNode(this); @@ -11864,15 +15275,16 @@ ImageExpand = (function() { } }, toggleAll: function() { - var func, toggle; + var func, threadRoot, toggle; $.event('CloseMenu'); + threadRoot = Nav.getThread(); toggle = function(post) { var file; file = post.file; if (!(file && (file.isImage || file.isVideo) && doc.contains(post.nodes.root))) { return; } - if (ImageExpand.on && (!Conf['Expand spoilers'] && file.isSpoiler || !Conf['Expand videos'] && file.isVideo || Conf['Expand from here'] && Header.getTopOf(file.thumb) < 0)) { + if (ImageExpand.on && (!Conf['Expand spoilers'] && file.isSpoiler || !Conf['Expand videos'] && file.isVideo || Conf['Expand from here'] && Header.getTopOf(file.thumb) < 0 || Conf['Expand thread only'] && g.VIEW === 'index' && !(threadRoot != null ? threadRoot.contains(file.thumb) : void 0))) { return; } return $.queueTask(func, post); @@ -11953,8 +15365,8 @@ ImageExpand = (function() { $.rmClass(post.nodes.root, 'expanded-image'); $.rmClass(file.thumb, 'expanding'); $.rm(file.videoControls); - file.thumb.parentNode.href = file.url; - file.thumb.parentNode.target = '_blank'; + file.thumbLink.href = file.url; + file.thumbLink.target = '_blank'; ref = ['isExpanding', 'isExpanded', 'videoControls', 'wasPlaying', 'scrollIntoView']; for (i = 0, len = ref.length; i < len; i++) { x = ref[i]; @@ -11983,6 +15395,9 @@ ImageExpand = (function() { $.off(el, eventName, cb); } } + if (Conf['Restart when Opened']) { + ImageCommon.rewind(file.thumb); + } delete file.fullImage; return $.queueTask(function() { if (file.isExpanding || file.isExpanded) { @@ -11996,9 +15411,9 @@ ImageExpand = (function() { }); }, expand: function(post, src) { - var el, file, isVideo, ref, thumb; + var el, file, isVideo, ref, thumb, thumbLink; file = post.file; - thumb = file.thumb, isVideo = file.isVideo; + thumb = file.thumb, thumbLink = file.thumbLink, isVideo = file.isVideo; if (post.isHidden || file.isExpanding || file.isExpanded) { return; } @@ -12006,25 +15421,28 @@ ImageExpand = (function() { file.isExpanding = true; if (file.fullImage) { el = file.fullImage; - } else if (((ref = ImageCommon.cache) != null ? ref.dataset.fullID : void 0) === post.fullID) { + } else if (((ref = ImageCommon.cache) != null ? ref.dataset.fileID : void 0) === (post.fullID + "." + file.index)) { el = file.fullImage = ImageCommon.popCache(); $.on(el, 'error', ImageExpand.error); + if (Conf['Restart when Opened'] && el.id !== 'ihover') { + ImageCommon.rewind(el); + } el.removeAttribute('id'); } else { el = file.fullImage = $.el((isVideo ? 'video' : 'img')); - el.dataset.fullID = post.fullID; + el.dataset.fileID = post.fullID + "." + file.index; $.on(el, 'error', ImageExpand.error); el.src = src || file.url; } el.className = 'full-image'; $.after(thumb, el); if (isVideo) { - if (Conf['Show Controls'] && Conf['Click Passthrough'] && !file.videoControls) { + if (!file.videoControls) { file.videoControls = ImageExpand.videoControls.cloneNode(true); $.add(file.text, file.videoControls); } - thumb.parentNode.removeAttribute('href'); - thumb.parentNode.removeAttribute('target'); + thumbLink.removeAttribute('href'); + thumbLink.removeAttribute('target'); el.loop = true; Volume.setup(el); ImageExpand.setupVideoCB(post); @@ -12109,7 +15527,7 @@ ImageExpand = (function() { } }, mouseout: function(e) { - if (mousedown && e.clientX <= this.getBoundingClientRect().left) { + if (((e.buttons & 1) || mousedown) && e.clientX <= this.getBoundingClientRect().left) { return ImageExpand.toggle(Get.postFromNode(this)); } } @@ -12136,13 +15554,13 @@ ImageExpand = (function() { if (!(post.file.isExpanding || post.file.isExpanded)) { return; } - if (ImageCommon.decodeError(this, post)) { + if (ImageCommon.decodeError(this, post.file)) { return ImageExpand.contract(post); } - if (this.src.split('/')[2] !== 'i.4cdn.org') { + if (ImageCommon.isFromArchive(this)) { return ImageExpand.contract(post); } - return ImageCommon.error(this, post, 10 * $.SECOND, function(URL) { + return ImageCommon.error(this, post, post.file, 10 * $.SECOND, function(URL) { if (post.file.isExpanding || post.file.isExpanded) { ImageExpand.contract(post); if (URL) { @@ -12195,6 +15613,68 @@ ImageExpand = (function() { }).call(this); +ImageHost = (function() { + var ImageHost; + + ImageHost = { + init: function() { + var ref; + if (!((this.useFaster = /\S/.test(Conf['fourchanImageHost'])) && g.SITE.software === 'yotsuba' && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { + return; + } + return Callbacks.Post.push({ + name: 'Image Host Rewriting', + cb: this.node + }); + }, + suggestions: ['i.4cdn.org', 'is2.4chan.org'], + host: function() { + return Conf['fourchanImageHost'].trim() || 'i.4cdn.org'; + }, + flashHost: function() { + return 'i.4cdn.org'; + }, + thumbHost: function() { + return 'i.4cdn.org'; + }, + test: function(hostname) { + return hostname === 'i.4cdn.org' || ImageHost.regex.test(hostname); + }, + regex: /^is\d*\.4chan(?:nel)?\.org$/, + node: function() { + var host; + if (this.isClone) { + return; + } + host = ImageHost.host(); + if (this.file && ImageHost.test(this.file.url.split('/')[2]) && !/\.swf$/.test(this.file.url)) { + this.file.link.hostname = host; + if (this.file.thumbLink) { + this.file.thumbLink.hostname = host; + } + this.file.url = this.file.link.href; + } + return ImageHost.fixLinks($$('a', this.nodes.comment)); + }, + fixLinks: function(links) { + var host, i, len, link; + for (i = 0, len = links.length; i < len; i++) { + link = links[i]; + if (!(ImageHost.test(link.hostname) && !/\.swf$/.test(link.pathname))) { + continue; + } + host = ImageHost.host(); + if (link.hostname !== host) { + link.hostname = host; + } + } + } + }; + + return ImageHost; + +}).call(this); + ImageHover = (function() { var ImageHover; @@ -12218,40 +15698,49 @@ ImageHover = (function() { } }, node: function() { - if (!(this.file && (this.file.isImage || this.file.isVideo))) { - return; + var file, i, len, ref, results; + ref = this.files; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + if ((file.isImage || file.isVideo) && file.thumb) { + results.push($.on(file.thumb, 'mouseover', ImageHover.mouseover(this, file))); + } } - return $.on(this.file.thumb, 'mouseover', ImageHover.mouseover(this)); + return results; }, catalogNode: function() { var file; - file = this.thread.OP.file; + file = this.thread.OP.files[0]; if (!(file && (file.isImage || file.isVideo))) { return; } - return $.on(this.nodes.thumb, 'mouseover', ImageHover.mouseover(this.thread.OP)); + return $.on(this.nodes.thumb, 'mouseover', ImageHover.mouseover(this.thread.OP, file)); }, - mouseover: function(post) { + mouseover: function(post, file) { return function(e) { - var el, error, file, height, isVideo, left, maxHeight, maxWidth, ref, ref1, ref2, right, scale, width, x; + var base, el, error, height, isVideo, maxHeight, maxWidth, ref, ref1, scale, width, x; if (!doc.contains(this)) { return; } - file = post.file; isVideo = file.isVideo; - if (file.isExpanding || file.isExpanded) { + if (file.isExpanding || file.isExpanded || (typeof (base = g.SITE).isThumbExpanded === "function" ? base.isThumbExpanded(file) : void 0)) { return; } - error = ImageHover.error(post); - if (((ref = ImageCommon.cache) != null ? ref.dataset.fullID : void 0) === post.fullID) { + error = ImageHover.error(post, file); + if (((ref = ImageCommon.cache) != null ? ref.dataset.fileID : void 0) === (post.fullID + "." + file.index)) { el = ImageCommon.popCache(); $.on(el, 'error', error); } else { el = $.el((isVideo ? 'video' : 'img')); - el.dataset.fullID = post.fullID; + el.dataset.fileID = post.fullID + "." + file.index; $.on(el, 'error', error); el.src = file.url; } + if (Conf['Restart when Opened']) { + ImageCommon.rewind(el); + ImageCommon.rewind(this); + } el.id = 'ihover'; $.add(Header.hover, el); if (isVideo) { @@ -12265,28 +15754,32 @@ ImageHover = (function() { } } } - ref1 = (function() { - var i, len, ref1, results; - ref1 = file.dimensions.split('x'); - results = []; - for (i = 0, len = ref1.length; i < len; i++) { - x = ref1[i]; - results.push(+x); - } - return results; - })(), width = ref1[0], height = ref1[1]; - ref2 = this.getBoundingClientRect(), left = ref2.left, right = ref2.right; - maxWidth = Math.max(left, doc.clientWidth - right); - maxHeight = doc.clientHeight - UI.hover.padding; - scale = Math.min(1, maxWidth / width, maxHeight / height); - el.style.maxWidth = (scale * width) + "px"; - el.style.maxHeight = (scale * height) + "px"; + if (file.dimensions) { + ref1 = (function() { + var i, len, ref1, results; + ref1 = file.dimensions.split('x'); + results = []; + for (i = 0, len = ref1.length; i < len; i++) { + x = ref1[i]; + results.push(+x); + } + return results; + })(), width = ref1[0], height = ref1[1]; + maxWidth = doc.clientWidth; + maxHeight = doc.clientHeight - UI.hover.padding; + scale = Math.min(1, maxWidth / width, maxHeight / height); + width *= scale; + height *= scale; + el.style.maxWidth = width + "px"; + el.style.maxHeight = height + "px"; + } return UI.hover({ root: this, el: el, latestEvent: e, endEvents: 'mouseout click', - height: scale * height, + height: height, + width: width, noRemove: true, cb: function() { $.off(el, 'error', error); @@ -12298,12 +15791,12 @@ ImageHover = (function() { }); }; }, - error: function(post) { + error: function(post, file) { return function() { - if (ImageCommon.decodeError(this, post)) { + if (ImageCommon.decodeError(this, file)) { return; } - return ImageCommon.error(this, post, 3 * $.SECOND, (function(_this) { + return ImageCommon.error(this, post, file, 3 * $.SECOND, (function(_this) { return function(URL) { if (URL) { return _this.src = URL + (_this.src === URL ? '?' + Date.now() : ''); @@ -12326,11 +15819,12 @@ ImageLoader = (function() { ImageLoader = { init: function() { - var prefetch, ref; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && g.BOARD.ID !== 'f')) { + var el, ref, ref1, replace; + if ((ref = g.VIEW) !== 'index' && ref !== 'thread' && ref !== 'archive') { return; } - if (!(Conf['Image Prefetching'] || Conf['Replace JPG'] || Conf['Replace PNG'] || Conf['Replace GIF'] || Conf['Replace WEBM'])) { + replace = Conf['Replace JPG'] || Conf['Replace PNG'] || Conf['Replace GIF'] || Conf['Replace WEBM']; + if (!(Conf['Image Prefetching'] || replace)) { return; } Callbacks.Post.push({ @@ -12338,36 +15832,41 @@ ImageLoader = (function() { cb: this.node }); $.on(d, 'PostsInserted', function() { - return g.posts.forEach(ImageLoader.prefetch); + if (ImageLoader.prefetchEnabled || replace) { + return g.posts.forEach(ImageLoader.prefetchAll); + } }); if (Conf['Replace WEBM']) { $.on(d, 'scroll visibilitychange 4chanXInitFinished PostsInserted', this.playVideos); } - if (!Conf['Image Prefetching']) { + if (!(Conf['Image Prefetching'] && ((ref1 = g.VIEW) === 'index' || ref1 === 'thread'))) { return; } - prefetch = $.el('label', { - innerHTML: " Prefetch Images" - }); - this.el = prefetch.firstElementChild; - $.on(this.el, 'change', this.toggle); - return Header.menu.addEntry({ - el: prefetch, - order: 98 + el = $.el('a', { + href: 'javascript:;', + title: 'Prefetch Images', + className: 'fa fa-bolt disabled', + textContent: 'Prefetch' }); + $.on(el, 'click', this.toggle); + return Header.addShortcut('prefetch', el, 525); }, node: function() { - if (this.isClone || !this.file) { + var file, i, len, ref; + if (this.isClone) { return; } - if (Conf['Replace WEBM'] && this.file.isVideo) { - ImageLoader.replaceVideo(this); + ref = this.files; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + if (Conf['Replace WEBM'] && file.isVideo) { + ImageLoader.replaceVideo(this, file); + } + ImageLoader.prefetch(this, file); } - return ImageLoader.prefetch(this); }, - replaceVideo: function(post) { - var attr, file, i, len, ref, thumb, video; - file = post.file; + replaceVideo: function(post, file) { + var attr, i, len, ref, thumb, video; thumb = file.thumb; video = $.el('video', { preload: 'none', @@ -12389,19 +15888,25 @@ ImageLoader = (function() { file.thumb = video; return file.videoThumb = true; }, - prefetch: function(post) { - var clone, el, file, i, isImage, isVideo, len, match, ref, replace, thumb, type, url; - file = post.file; - if (!file) { - return; - } + prefetch: function(post, file) { + var clone, el, i, isImage, isVideo, len, ref, ref1, replace, thumb, type, url; isImage = file.isImage, isVideo = file.isVideo, thumb = file.thumb, url = file.url; if (file.isPrefetched || !(isImage || isVideo) || post.isHidden || post.thread.isHidden) { return; } - type = (match = url.match(/\.([^.]+)$/)[1].toUpperCase()) === 'JPEG' ? 'JPG' : match; + if (isVideo) { + type = 'WEBM'; + } else { + type = (ref = url.match(/\.([^.]+)$/)) != null ? ref[1].toUpperCase() : void 0; + if (type === 'JPEG') { + type = 'JPG'; + } + } replace = Conf["Replace " + type] && !/spoiler/.test(thumb.src || thumb.dataset.src); - if (!(replace || Conf['prefetch'])) { + if (!(replace || ImageLoader.prefetchEnabled)) { + return; + } + if ($.hasClass(doc, 'catalog-mode')) { return; } if (![post].concat(slice.call(post.clones)).some(function(clone) { @@ -12411,9 +15916,9 @@ ImageLoader = (function() { } file.isPrefetched = true; if (file.videoThumb) { - ref = post.clones; - for (i = 0, len = ref.length; i < len; i++) { - clone = ref[i]; + ref1 = post.clones; + for (i = 0, len = ref1.length; i < len; i++) { + clone = ref1[i]; clone.file.thumb.preload = 'auto'; } thumb.preload = 'auto'; @@ -12425,41 +15930,57 @@ ImageLoader = (function() { return; } el = $.el(isImage ? 'img' : 'video'); + if (isVideo) { + el.preload = 'auto'; + } if (replace && isImage) { $.on(el, 'load', function() { - var j, len1, ref1; - ref1 = post.clones; - for (j = 0, len1 = ref1.length; j < len1; j++) { - clone = ref1[j]; + var j, len1, ref2; + ref2 = post.clones; + for (j = 0, len1 = ref2.length; j < len1; j++) { + clone = ref2[j]; clone.file.thumb.src = url; } - thumb.src = url; - return thumb.removeAttribute('data-src'); + return thumb.src = url; }); } return el.src = url; }, + prefetchAll: function(post) { + var file, i, len, ref; + ref = post.files; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + ImageLoader.prefetch(post, file); + } + }, toggle: function() { - if (Conf['prefetch'] = this.checked) { - g.posts.forEach(ImageLoader.prefetch); + ImageLoader.prefetchEnabled = !ImageLoader.prefetchEnabled; + this.classList.toggle('disabled', !ImageLoader.prefetchEnabled); + if (ImageLoader.prefetchEnabled) { + g.posts.forEach(ImageLoader.prefetchAll); } }, playVideos: function() { var qpClone, ref; qpClone = (ref = $.id('qp')) != null ? ref.firstElementChild : void 0; return g.posts.forEach(function(post) { - var i, len, ref1, ref2, thumb; + var file, i, j, len, len1, ref1, ref2, thumb; ref1 = [post].concat(slice.call(post.clones)); for (i = 0, len = ref1.length; i < len; i++) { post = ref1[i]; - if (!((ref2 = post.file) != null ? ref2.videoThumb : void 0)) { - continue; - } - thumb = post.file.thumb; - if (Header.isNodeVisible(thumb) || post.nodes.root === qpClone) { - thumb.play(); - } else { - thumb.pause(); + ref2 = post.files; + for (j = 0, len1 = ref2.length; j < len1; j++) { + file = ref2[j]; + if (!file.videoThumb) { + continue; + } + thumb = file.thumb; + if (Header.isNodeVisible(thumb) || post.nodes.root === qpClone) { + thumb.play(); + } else { + thumb.pause(); + } } } }); @@ -12476,7 +15997,7 @@ Metadata = (function() { Metadata = { init: function() { var ref; - if (!(Conf['WEBM Metadata'] && ((ref = g.VIEW) === 'index' || ref === 'thread') && g.BOARD.ID !== 'f')) { + if (!(Conf['WEBM Metadata'] && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { return; } return Callbacks.Post.push({ @@ -12485,29 +16006,34 @@ Metadata = (function() { }); }, node: function() { - var el; - if (!(this.file && /webm$/i.test(this.file.url))) { - return; - } - if (this.isClone) { - el = $('.webm-title', this.file.text); - } else { - el = $.el('span', { - className: 'webm-title' - }); - $.extend(el, { - innerHTML: "" - }); - $.add(this.file.text, [$.tn(' '), el]); - } - if (el.children.length === 1) { - return $.one(el.lastElementChild, 'mouseover focus', Metadata.load); + var el, file, i, j, len1, ref; + ref = this.files; + for (i = j = 0, len1 = ref.length; j < len1; i = ++j) { + file = ref[i]; + if (!(/webm$/i.test(file.url))) { + continue; + } + if (this.isClone) { + el = $('.webm-title', file.text); + } else { + el = $.el('span', { + className: 'webm-title' + }); + el.dataset.index = i; + $.extend(el, {innerHTML: ""}); + $.add(file.text, [$.tn(' '), el]); + } + if (el.children.length === 1) { + $.one(el.lastElementChild, 'mouseover focus', Metadata.load); + } } }, load: function() { + var index; $.rmClass(this.parentNode, 'error'); $.addClass(this.parentNode, 'loading'); - return CrossOrigin.binary(Get.postFromNode(this).file.url, (function(_this) { + index = this.parentNode.dataset.index; + return CrossOrigin.binary(Get.postFromNode(this).files[+index].url, (function(_this) { return function(data) { var output, title; $.rmClass(_this.parentNode, 'loading'); @@ -12577,7 +16103,7 @@ RevealSpoilers = (function() { RevealSpoilers = { init: function() { var ref; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Reveal Spoiler Thumbnails'])) { + if (!(((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Reveal Spoiler Thumbnails'])) { return; } return Callbacks.Post.push({ @@ -12586,17 +16112,24 @@ RevealSpoilers = (function() { }); }, node: function() { - var thumb; - if (!(!this.isClone && this.file && this.file.thumb && this.file.isSpoiler)) { + var file, i, len, ref, thumb; + if (this.isClone) { return; } - thumb = this.file.thumb; - thumb.removeAttribute('style'); - thumb.style.maxHeight = thumb.style.maxWidth = this.isReply ? '125px' : '250px'; - if (thumb.src) { - return thumb.src = this.file.thumbURL; - } else { - return thumb.dataset.src = this.file.thumbURL; + ref = this.files; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + if (!(file.thumb && file.isSpoiler)) { + continue; + } + thumb = file.thumb; + thumb.removeAttribute('style'); + thumb.style.maxHeight = thumb.style.maxWidth = this.isReply ? '125px' : '250px'; + if (thumb.src) { + thumb.src = file.thumbURL; + } else { + thumb.dataset.src = file.thumbURL; + } } } }; @@ -12611,20 +16144,17 @@ Sauce = (function() { Sauce = { init: function() { - var err, j, len, link, links, ref, ref1; + var j, len, link, linkData, links, ref, ref1; if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Sauce'])) { return; } + $.addClass(doc, 'show-sauce'); links = []; ref1 = Conf['sauces'].split('\n'); for (j = 0, len = ref1.length; j < len; j++) { link = ref1[j]; - try { - if (link[0] !== '#') { - links.push(link.trim()); - } - } catch (_error) { - err = _error; + if (link[0] !== '#' && (linkData = this.parseLink(link))) { + links.push(linkData); } } if (!links.length) { @@ -12640,13 +16170,13 @@ Sauce = (function() { cb: this.node }); }, - createSauceLink: function(link, post) { - var a, ext, i, j, key, len, m, part, parts, ref, ref1, ref2, skip; + parseLink: function(link) { + var err, i, j, len, m, part, parts, ref, ref1, regexp; if (!(link = link.trim())) { return null; } - parts = {}; - ref = link.split(/;(?=(?:text|boards|types|sandbox):?)/); + parts = $.dict(); + ref = link.split(/;(?=(?:text|boards|types|regexp|sandbox):?)/); for (i = j = 0, len = ref.length; j < len; i = ++j) { part = ref[i]; if (i === 0) { @@ -12657,15 +16187,55 @@ Sauce = (function() { } } parts['text'] || (parts['text'] = ((ref1 = parts['url'].match(/(\w+)\.\w+\//)) != null ? ref1[1] : void 0) || '?'); - ext = post.file.url.match(/[^.]*$/)[0]; - skip = false; - for (key in parts) { - parts[key] = parts[key].replace(/%(T?URL|IMG|[sh]?MD5|board|name|%|semi)/g, function(_, parameter) { + if ('boards' in parts) { + parts['boards'] = Filter.parseBoards(parts['boards']); + } + if ('regexp' in parts) { + try { + if ((regexp = parts['regexp'].match(/^\/(.*)\/(\w*)$/))) { + parts['regexp'] = RegExp(regexp[1], regexp[2]); + } else { + parts['regexp'] = RegExp(parts['regexp']); + } + } catch (error) { + err = error; + new Notice('warning', [$.tn("Invalid regexp for Sauce link:"), $.el('br'), $.tn(link), $.el('br'), $.tn(err.message)], 60); + return null; + } + } + return parts; + }, + createSauceLink: function(link, post, file) { + var a, base, ext, j, key, len, matches, missing, parts, ref; + ext = file.url.match(/[^.]*$/)[0]; + parts = $.dict(); + $.extend(parts, link); + if (!(!parts['boards'] || parts['boards'][post.siteID + "/" + post.boardID] || parts['boards'][post.siteID + "/*"])) { + return null; + } + if (!(!parts['types'] || indexOf.call(parts['types'].split(','), ext) >= 0)) { + return null; + } + if (!(!parts['regexp'] || (matches = file.name.match(parts['regexp'])))) { + return null; + } + missing = []; + ref = ['url', 'text']; + for (j = 0, len = ref.length; j < len; j++) { + key = ref[j]; + parts[key] = parts[key].replace(/%(T?URL|IMG|[sh]?MD5|board|name|%|semi|\$\d+)/g, function(orig, parameter) { var type; - type = Sauce.formatters[parameter](post, ext); - if (type == null) { - skip = true; - return ''; + if (parameter[0] === '$') { + if (!matches) { + return orig; + } + type = matches[parameter.slice(1)] || ''; + } else { + type = Sauce.formatters[parameter](post, file, ext); + if (type == null) { + missing.push(parameter); + return ''; + } } if (key === 'url' && (parameter !== '%' && parameter !== 'semi')) { if (/^javascript:/i.test(parts['url'])) { @@ -12676,13 +16246,14 @@ Sauce = (function() { return type; }); } - if (skip) { - return null; - } - if (!(!parts['boards'] || (ref2 = post.board.ID, indexOf.call(parts['boards'].split(','), ref2) >= 0))) { - return null; + if ((typeof (base = g.SITE).areMD5sDeferred === "function" ? base.areMD5sDeferred(post.board) : void 0) && missing.length && !missing.filter(function(x) { + return !/^.?MD5$/.test(x); + }).length) { + a = Sauce.link.cloneNode(false); + a.dataset.skip = '1'; + return a; } - if (!(!parts['types'] || indexOf.call(parts['types'].split(','), ext) >= 0)) { + if (missing.length) { return null; } a = Sauce.link.cloneNode(false); @@ -12694,62 +16265,69 @@ Sauce = (function() { return a; }, node: function() { - var j, len, link, node, nodes, observer, ref, skipped; - if (this.isClone || !this.file) { + var file, j, len, ref; + if (this.isClone) { return; } + ref = this.files; + for (j = 0, len = ref.length; j < len; j++) { + file = ref[j]; + Sauce.file(this, file); + } + }, + file: function(post, file) { + var j, len, link, node, nodes, observer, ref, skipped; nodes = []; skipped = []; ref = Sauce.links; for (j = 0, len = ref.length; j < len; j++) { link = ref[j]; - if (!(node = Sauce.createSauceLink(link, this))) { - node = Sauce.link.cloneNode(false); - skipped.push([link, node]); - } - nodes.push($.tn(' '), node); - } - $.add(this.file.text, nodes); - if (this.board.ID === 'f') { - observer = new MutationObserver((function(_this) { - return function() { - var k, len1, node2, ref1; - if (_this.file.text.dataset.md5) { - for (k = 0, len1 = skipped.length; k < len1; k++) { - ref1 = skipped[k], link = ref1[0], node = ref1[1]; - if ((node2 = Sauce.createSauceLink(link, _this))) { - $.replace(node, node2); - } + if ((node = Sauce.createSauceLink(link, post, file))) { + nodes.push($.tn(' '), node); + if (node.dataset.skip) { + skipped.push([link, node]); + } + } + } + $.add(file.text, nodes); + if (skipped.length) { + observer = new MutationObserver(function() { + var k, len1, node2, ref1; + if (file.text.dataset.md5) { + for (k = 0, len1 = skipped.length; k < len1; k++) { + ref1 = skipped[k], link = ref1[0], node = ref1[1]; + if ((node2 = Sauce.createSauceLink(link, post, file))) { + $.replace(node, node2); } - return observer.disconnect(); } - }; - })(this)); - return observer.observe(this.file.text, { + return observer.disconnect(); + } + }); + return observer.observe(file.text, { attributes: true }); } }, formatters: { - TURL: function(post) { - return post.file.thumbURL; + TURL: function(post, file) { + return file.thumbURL; }, - URL: function(post) { - return post.file.url; + URL: function(post, file) { + return file.url; }, - IMG: function(post, ext) { - if (ext === 'gif' || ext === 'jpg' || ext === 'png') { - return post.file.url; + IMG: function(post, file, ext) { + if (ext === 'gif' || ext === 'jpg' || ext === 'jpeg' || ext === 'png') { + return file.url; } else { - return post.file.thumbURL; + return file.thumbURL; } }, - MD5: function(post) { - return post.file.MD5; + MD5: function(post, file) { + return file.MD5; }, - sMD5: function(post) { + sMD5: function(post, file) { var ref; - return (ref = post.file.MD5) != null ? ref.replace(/[+\/=]/g, function(c) { + return (ref = file.MD5) != null ? ref.replace(/[+\/=]/g, function(c) { return { '+': '-', '/': '_', @@ -12757,12 +16335,12 @@ Sauce = (function() { }[c]; }) : void 0; }, - hMD5: function(post) { + hMD5: function(post, file) { var c; - if (post.file.MD5) { + if (file.MD5) { return ((function() { var j, len, ref, results; - ref = atob(post.file.MD5); + ref = atob(file.MD5); results = []; for (j = 0, len = ref.length; j < len; j++) { c = ref[j]; @@ -12775,8 +16353,8 @@ Sauce = (function() { board: function(post) { return post.board.ID; }, - name: function(post) { - return post.file.name; + name: function(post, file) { + return file.name; }, '%': function() { return '%'; @@ -12796,7 +16374,7 @@ Volume = (function() { Volume = { init: function() { - var ref, ref1, unmuteEntry, volumeEntry; + var base, ref, unmuteEntry, volumeEntry; if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && (Conf['Image Expansion'] || Conf['Image Hover'] || Conf['Image Hover in Catalog'] || Conf['Gallery']))) { return; } @@ -12816,7 +16394,7 @@ Volume = (function() { cb: this.node }); } - if ((ref1 = g.BOARD.ID) !== 'gif' && ref1 !== 'wsg') { + if (typeof (base = g.SITE).noAudio === "function" ? base.noAudio(g.BOARD) : void 0) { return; } if (Conf['Mouse Wheel Volume']) { @@ -12830,9 +16408,7 @@ Volume = (function() { volumeEntry = $.el('label', { title: 'Default volume for videos.' }); - $.extend(volumeEntry, { - innerHTML: " Volume" - }); + $.extend(volumeEntry, {innerHTML: " Volume"}); this.inputs = { unmute: unmuteEntry.firstElementChild, volume: volumeEntry.firstElementChild @@ -12854,8 +16430,8 @@ Volume = (function() { return $.on(video, 'volumechange', Volume.change); }, change: function() { - var items, key, muted, val, volume; - muted = this.muted, volume = this.volume; + var items, key, muted, ref, val, volume; + ref = this, muted = ref.muted, volume = ref.volume; items = { 'Allow Sound': !muted, 'Default Volume': volume @@ -12874,16 +16450,25 @@ Volume = (function() { } }, node: function() { - var ref, ref1; - if (!(((ref = this.board.ID) === 'gif' || ref === 'wsg') && ((ref1 = this.file) != null ? ref1.isVideo : void 0))) { + var base, file, i, len, ref; + if (typeof (base = g.SITE).noAudio === "function" ? base.noAudio(this.board) : void 0) { return; } - $.on(this.file.thumb, 'wheel', Volume.wheel.bind(Header.hover)); - return $.on($('a', this.file.text), 'wheel', Volume.wheel.bind(this.file.thumb.parentNode)); + ref = this.files; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + if (!file.isVideo) { + continue; + } + if (file.thumb) { + $.on(file.thumb, 'wheel', Volume.wheel.bind(Header.hover)); + } + $.on($('.file-info', file.text) || file.link, 'wheel', Volume.wheel.bind(file.thumbLink)); + } }, catalogNode: function() { var file; - file = this.thread.OP.file; + file = this.thread.OP.files[0]; if (!(file != null ? file.isVideo : void 0)) { return; } @@ -12917,34 +16502,47 @@ Volume = (function() { }).call(this); Embedding = (function() { - var Embedding; + var Embedding, + slice = [].slice; Embedding = { init: function() { - var j, len, ref, type; - if (!(Conf['Embedding'] || Conf['Link Title'])) { + var j, len, ref, ref1, type; + if (!(((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Linkify'] && (Conf['Embedding'] || Conf['Link Title'] || Conf['Cover Preview']))) { return; } - this.types = {}; - ref = this.ordered_types; - for (j = 0, len = ref.length; j < len; j++) { - type = ref[j]; + this.types = $.dict(); + ref1 = this.ordered_types; + for (j = 0, len = ref1.length; j < len; j++) { + type = ref1[j]; this.types[type.key] = type; } - if (Conf['Floating Embeds']) { - this.dialog = UI.dialog('embedding', 'top: 50px; right: 0px;', { - innerHTML: "
              " - }); + if (Conf['Embedding'] && g.VIEW !== 'archive') { + this.dialog = UI.dialog('embedding', {innerHTML: "
              "}); this.media = $('#media-embed', this.dialog); $.one(d, '4chanXInitFinished', this.ready); + $.on(d, 'IndexRefreshInternal', function() { + return g.posts.forEach(function(post) { + var embed, k, l, len1, len2, ref2, ref3; + ref2 = [post].concat(slice.call(post.clones)); + for (k = 0, len1 = ref2.length; k < len1; k++) { + post = ref2[k]; + ref3 = post.nodes.embedlinks; + for (l = 0, len2 = ref3.length; l < len2; l++) { + embed = ref3[l]; + Embedding.cb.catalogRemove.call(embed); + } + } + }); + }); } if (Conf['Link Title']) { return $.on(d, '4chanXInitFinished PostsInserted', function() { - var key, ref1, ref2, service; - ref1 = Embedding.types; - for (key in ref1) { - service = ref1[key]; - if ((ref2 = service.title) != null ? ref2.batchSize : void 0) { + var key, ref2, ref3, service; + ref2 = Embedding.types; + for (key in ref2) { + service = ref2[key]; + if ((ref3 = service.title) != null ? ref3.batchSize : void 0) { Embedding.flushTitles(service.title); } } @@ -12952,22 +16550,33 @@ Embedding = (function() { } }, events: function(post) { - var el, i, items; - if (!Conf['Embedding']) { + var data, el, i, items; + if (g.VIEW === 'archive') { return; } - i = 0; - items = $$('.embedder', post.nodes.comment); - while (el = items[i++]) { - $.on(el, 'click', Embedding.cb.toggle); - if ($.hasClass(el, 'embedded')) { - Embedding.cb.toggle.call(el); + if (Conf['Embedding']) { + i = 0; + items = post.nodes.embedlinks = $$('.embedder', post.nodes.comment); + while (el = items[i++]) { + $.on(el, 'click', Embedding.cb.click); + if ($.hasClass(el, 'embedded')) { + Embedding.cb.toggle.call(el); + } + } + } + if (Conf['Cover Preview']) { + i = 0; + items = $$('.linkify', post.nodes.comment); + while (el = items[i++]) { + if ((data = Embedding.services(el))) { + Embedding.preview(data); + } } } }, process: function(link, post) { var data; - if (!(Conf['Embedding'] || Conf['Link Title'])) { + if (!(Conf['Embedding'] || Conf['Link Title'] || Conf['Cover Preview'])) { return; } if ($.x('ancestor::pre', link)) { @@ -12975,11 +16584,14 @@ Embedding = (function() { } if (data = Embedding.services(link)) { data.post = post; - if (Conf['Embedding']) { + if (Conf['Embedding'] && g.VIEW !== 'archive') { Embedding.embed(data); } if (Conf['Link Title']) { - return Embedding.title(data); + Embedding.title(data); + } + if (Conf['Cover Preview'] && g.VIEW !== 'archive') { + return Embedding.preview(data); } } }, @@ -13003,15 +16615,11 @@ Embedding = (function() { var embed, href, key, link, name, options, post, ref, uid, value; key = data.key, uid = data.uid, options = data.options, link = data.link, post = data.post; href = link.href; - if (Embedding.types[key].httpOnly && location.protocol !== 'http:') { - return; - } $.addClass(link, key.toLowerCase()); embed = $.el('a', { className: 'embedder', - href: 'javascript:;', - textContent: '(embed)' - }); + href: 'javascript:;' + }, {innerHTML: "(unembed)"}); ref = { key: key, uid: uid, @@ -13022,17 +16630,21 @@ Embedding = (function() { value = ref[name]; embed.dataset[name] = value; } - $.on(embed, 'click', Embedding.cb.toggle); + $.on(embed, 'click', Embedding.cb.click); $.after(link, [$.tn(' '), embed]); + post.nodes.embedlinks.push(embed); if (Conf['Auto-embed'] && !Conf['Floating Embeds'] && !post.isFetchedQuote) { - return $.asap((function() { - return doc.contains(embed); - }), function() { + if ($.hasClass(doc, 'catalog-mode')) { + return $.addClass(embed, 'embed-removed'); + } else { return Embedding.cb.toggle.call(embed); - }); + } } }, ready: function() { + if (!Main.isThisPageLegit()) { + return; + } $.addClass(Embedding.dialog, 'empty'); $.on($('.close', Embedding.dialog), 'click', Embedding.closeFloat); $.on($('.move', Embedding.dialog), 'mousedown', Embedding.dragEmbed); @@ -13054,12 +16666,12 @@ Embedding = (function() { if (Embedding.dragEmbed.mouseup) { $.off(d, 'mouseup', Embedding.dragEmbed); Embedding.dragEmbed.mouseup = false; - style.visibility = ''; + style.pointerEvents = ''; return; } $.on(d, 'mouseup', Embedding.dragEmbed); Embedding.dragEmbed.mouseup = true; - return style.visibility = 'hidden'; + return style.pointerEvents = 'none'; }, title: function(data) { var key, link, options, post, service, uid; @@ -13074,19 +16686,13 @@ Embedding = (function() { return Embedding.flushTitles(service); } } else { - if (!$.cache(service.api(uid), (function() { + return CrossOrigin.cache(service.api(uid), (function() { return Embedding.cb.title(this, data); - }), { - responseType: 'json' - })) { - return $.extend(link, { - innerHTML: "[" + E(key) + "] Title Link Blocked (are you using NoScript?)" - }); - } + })); } }, flushTitles: function(service) { - var cb, data, j, len, queue; + var cb, data, queue; queue = service.queue; if (!(queue != null ? queue.length : void 0)) { return; @@ -13099,7 +16705,7 @@ Embedding = (function() { Embedding.cb.title(this, data); } }; - if (!$.cache(service.api((function() { + return CrossOrigin.cache(service.api((function() { var j, len, results; results = []; for (j = 0, len = queue.length; j < len; j++) { @@ -13107,61 +16713,98 @@ Embedding = (function() { results.push(data.uid); } return results; - })()), cb, { - responseType: 'json' - })) { - for (j = 0, len = queue.length; j < len; j++) { - data = queue[j]; - $.extend(data.link, { - innerHTML: "[" + E(data.key) + "] Title Link Blocked (are you using NoScript?)" - }); - } + })()), cb); + }, + preview: function(data) { + var key, link, service, uid; + key = data.key, uid = data.uid, link = data.link; + if (!(service = Embedding.types[key].preview)) { + return; } + return $.on(link, 'mouseover', function(e) { + var el, height, src; + src = service.url(uid); + height = service.height; + el = $.el('img', { + src: src, + id: 'ihover' + }); + $.add(Header.hover, el); + return UI.hover({ + root: link, + el: el, + latestEvent: e, + endEvents: 'mouseout click', + height: height + }); + }); }, cb: { - toggle: function(e) { + click: function(e) { var div; - if (e != null) { - e.preventDefault(); - } - if (Conf['Floating Embeds']) { + e.preventDefault(); + if (!$.hasClass(this, 'embedded') && (Conf['Floating Embeds'] || $.hasClass(doc, 'catalog-mode'))) { if (!(div = Embedding.media.firstChild)) { return; } $.replace(div, Embedding.cb.embed(this)); Embedding.lastEmbed = Get.postFromNode(this).nodes.root; - $.rmClass(Embedding.dialog, 'empty'); - return; + return $.rmClass(Embedding.dialog, 'empty'); + } else { + return Embedding.cb.toggle.call(this); } + }, + toggle: function() { if ($.hasClass(this, "embedded")) { $.rm(this.nextElementSibling); - this.textContent = '(embed)'; } else { $.after(this, Embedding.cb.embed(this)); - this.textContent = '(unembed)'; } return $.toggleClass(this, 'embedded'); }, embed: function(a) { var container, el, type; - container = $.el('div'); + container = $.el('div', { + className: 'media-embed' + }); $.add(container, el = (type = Embedding.types[a.dataset.key]).el(a)); el.style.cssText = type.style != null ? type.style : 'border: none; width: 640px; height: 360px;'; return container; }, + catalogRemove: function() { + var isCatalog; + isCatalog = $.hasClass(doc, 'catalog-mode'); + if ((isCatalog && $.hasClass(this, 'embedded')) || (!isCatalog && $.hasClass(this, 'embed-removed'))) { + Embedding.cb.toggle.call(this); + return $.toggleClass(this, 'embed-removed'); + } + }, title: function(req, data) { var base1, j, k, key, len, len1, link, link2, options, post, post2, ref, ref1, service, status, text, uid; key = data.key, uid = data.uid, options = data.options, link = data.link, post = data.post; - status = req.status; service = Embedding.types[key].title; + status = req.status; + if ((status === 200 || status === 304) && service.status) { + status = service.status(req.response)[0]; + } + if (!status) { + return; + } text = "[" + key + "] " + ((function() { switch (status) { case 200: case 304: - return service.text(req.response, uid); + text = service.text(req.response, uid); + if (typeof text === 'string') { + return text; + } else { + return text = link.textContent; + } + break; case 404: return "Not Found"; case 403: + case 401: return "Forbidden or Private"; default: return status + "'d"; @@ -13189,7 +16832,7 @@ Embedding = (function() { ordered_types: [ { key: 'audio', - regExp: /^[^?#]+\.(?:mp3|oga|wav)(?:[?#]|$)/i, + regExp: /^[^?#]+\.(?:mp3|m4a|oga|wav|flac)(?:[?#]|$)/i, style: '', el: function(a) { return $.el('audio', { @@ -13200,12 +16843,10 @@ Embedding = (function() { } }, { key: 'image', - regExp: /^[^?#]+\.(?:gif|png|jpg|jpeg|bmp)(?:[?#]|$)/i, + regExp: /^[^?#]+\.(?:gif|png|jpg|jpeg|bmp|webp)(?::\w+)?(?:[?#]|$)/i, style: '', el: function(a) { - return $.el('div', { - innerHTML: "" - }); + return $.el('div', {innerHTML: ""}); } }, { key: 'video', @@ -13218,7 +16859,7 @@ Embedding = (function() { controls: true, preload: 'auto', src: a.dataset.href, - loop: /^https?:\/\/i\.4cdn\.org\//.test(a.dataset.href) + loop: ImageHost.test(a.dataset.href.split('/')[2]) }); $.on(el, 'loadedmetadata', function() { if (el.videoHeight === 0 && el.parentNode) { @@ -13230,19 +16871,45 @@ Embedding = (function() { return el; } }, { - key: 'Clyp', - regExp: /^\w+:\/\/(?:www\.)?clyp\.it\/(\w+)/, - style: '', + key: 'PeerTube', + regExp: /^(\w+:\/\/[^\/]+\/videos\/watch\/\w{8}-\w{4}-\w{4}-\w{4}-\w{12})(.*)/, + el: function(a) { + var el, options, start; + options = (start = a.dataset.options.match(/[?&](start=\w+)/)) ? "?" + start[1] : ''; + el = $.el('iframe', { + src: a.dataset.uid.replace('/videos/watch/', '/videos/embed/') + options + }); + el.setAttribute("allowfullscreen", "true"); + return el; + } + }, { + key: 'BitChute', + regExp: /^\w+:\/\/(?:www\.)?bitchute\.com\/video\/([\w\-]+)/, el: function(a) { - var el, type; - el = $.el('audio', { - controls: true, - preload: 'auto' + var el; + el = $.el('iframe', { + src: "https://www.bitchute.com/embed/" + a.dataset.uid + "/" }); - type = el.canPlayType('audio/ogg') ? 'ogg' : 'mp3'; - el.src = "https://clyp.it/" + a.dataset.uid + "." + type; + el.setAttribute("allowfullscreen", "true"); return el; } + }, { + key: 'Clyp', + regExp: /^\w+:\/\/(?:www\.)?clyp\.it\/(\w{8})/, + style: 'border: 0; width: 640px; height: 160px;', + el: function(a) { + return $.el('iframe', { + src: "https://clyp.it/" + a.dataset.uid + "/widget" + }); + }, + title: { + api: function(uid) { + return "https://api.clyp.it/oembed?url=https://clyp.it/" + uid; + }, + text: function(_) { + return _.title; + } + } }, { key: 'Dailymotion', regExp: /^\w+:\/\/(?:(?:www\.)?dailymotion\.com\/(?:embed\/)?video|dai\.ly)\/([A-Za-z0-9]+)[^?]*(.*)/, @@ -13262,29 +16929,50 @@ Embedding = (function() { text: function(_) { return _.title; } + }, + preview: { + url: function(uid) { + return "https://www.dailymotion.com/thumbnail/video/" + uid; + }, + height: 240 } }, { key: 'Gfycat', regExp: /^\w+:\/\/(?:www\.)?gfycat\.com\/(?:iframe\/)?(\w+)/, el: function(a) { - var div; - return div = $.el('iframe', { - src: "//gfycat.com/iframe/" + a.dataset.uid + var el; + el = $.el('iframe', { + src: "//gfycat.com/ifr/" + a.dataset.uid }); + el.setAttribute("allowfullscreen", "true"); + return el; } }, { key: 'Gist', regExp: /^\w+:\/\/gist\.github\.com\/[\w\-]+\/(\w+)/, - el: function(a) { - var content, el; - el = $.el('iframe'); - el.setAttribute('sandbox', 'allow-scripts'); - content = { - innerHTML: "" + E(a.dataset.uid) + "" + style: '', + el: (function() { + var counter; + counter = 0; + return function(a) { + var el; + el = $.el('pre', { + hidden: true, + id: "gist-embed-" + (counter++) + }); + CrossOrigin.cache("https://api.github.com/gists/" + a.dataset.uid, function() { + el.textContent = Object.values(this.response.files)[0].content; + el.className = 'prettyprint'; + $.global(function() { + return typeof window.prettyPrint === "function" ? window.prettyPrint((function() {}), document.getElementById(document.currentScript.dataset.id).parentNode) : void 0; + }, { + id: el.id + }); + return el.hidden = false; + }); + return el; }; - el.src = E.url(content); - return el; - }, + })(), title: { api: function(uid) { return "https://api.github.com/gists/" + uid; @@ -13309,19 +16997,18 @@ Embedding = (function() { } }, { key: 'LiveLeak', - regExp: /^\w+:\/\/(?:\w+\.)?liveleak\.com\/.*\?.*i=(\w+)/, - httpOnly: true, + regExp: /^\w+:\/\/(?:\w+\.)?liveleak\.com\/.*\?.*[tif]=(\w+)/, el: function(a) { var el; el = $.el('iframe', { - src: "http://www.liveleak.com/ll_embed?i=" + a.dataset.uid + src: "https://www.liveleak.com/e/" + a.dataset.uid }); el.setAttribute("allowfullscreen", "true"); return el; } }, { key: 'Loopvid', - regExp: /^\w+:\/\/(?:www\.)?loopvid.appspot.com\/#?((?:pf|kd|lv|gd|gh|db|dx|nn|cp|wu|ig|ky|mf|m2|pc|1c|pi|wl|ko|gc)\/[\w\-\/]+(?:,[\w\-\/]+)*|fc\/\w+\/\d+|https?:\/\/.+)/, + regExp: /^\w+:\/\/(?:www\.)?loopvid.appspot.com\/#?((?:pf|kd|lv|gd|gh|db|dx|nn|cp|wu|ig|ky|mf|m2|pc|1c|pi|ni|wl|ko|mm|ic|gc)\/[\w\-\/]+(?:,[\w\-\/]+)*|fc\/\w+\/\d+|https?:\/\/.+)/, style: 'max-width: 80vw; max-height: 80vh;', el: function(a) { var _, base, el, host, j, k, l, len, len1, len2, name, names, ref, ref1, type, types, url, urls; @@ -13360,9 +17047,9 @@ Embedding = (function() { case 'pf': return ["https://kastden.org/_loopvid_media/pf/" + base, "https://web.archive.org/web/2/http://a.pomf.se/" + base]; case 'kd': - return ["http://kastden.org/loopvid/" + base]; + return ["https://kastden.org/loopvid/" + base]; case 'lv': - return ["http://lv.kastden.org/" + base]; + return ["https://lv.kastden.org/" + base]; case 'gd': return ["https://docs.google.com/uc?export=download&id=" + base]; case 'gh': @@ -13372,7 +17059,7 @@ Embedding = (function() { case 'dx': return ["https://dl.dropboxusercontent.com/" + base]; case 'nn': - return ["http://naenara.eu/loopvids/" + base]; + return ["https://kastden.org/_loopvid_media/nn/" + base]; case 'cp': return ["https://copy.com/" + base]; case 'wu': @@ -13380,23 +17067,29 @@ Embedding = (function() { case 'ig': return ["https://i.imgur.com/" + base]; case 'ky': - return ["https://kiyo.me/" + base]; + return ["https://kastden.org/_loopvid_media/ky/" + base]; case 'mf': return ["https://kastden.org/_loopvid_media/mf/" + base, "https://web.archive.org/web/2/https://d.maxfile.ro/" + base]; case 'm2': return ["https://kastden.org/_loopvid_media/m2/" + base]; case 'pc': - return ["http://a.pomf.cat/" + base]; + return ["https://kastden.org/_loopvid_media/pc/" + base, "https://web.archive.org/web/2/http://a.pomf.cat/" + base]; case '1c': return ["http://b.1339.cf/" + base]; case 'pi': - return ["https://u.pomf.is/" + base]; + return ["https://kastden.org/_loopvid_media/pi/" + base, "https://web.archive.org/web/2/https://u.pomf.is/" + base]; + case 'ni': + return ["https://kastden.org/_loopvid_media/ni/" + base, "https://web.archive.org/web/2/https://u.nya.is/" + base]; case 'wl': return ["http://webm.land/media/" + base]; case 'ko': return ["https://kordy.kastden.org/loopvid/" + base]; + case 'mm': + return ["https://kastden.org/_loopvid_media/mm/" + base, "https://web.archive.org/web/2/https://my.mixtape.moe/" + base]; + case 'ic': + return ["https://media.8ch.net/file_store/" + base]; case 'fc': - return ["//i.4cdn.org/" + base + ".webm"]; + return ["//" + (ImageHost.host()) + "/" + base + ".webm"]; case 'gc': return ["https://" + type + ".gfycat.com/" + name + ".webm"]; } @@ -13413,15 +17106,15 @@ Embedding = (function() { } }, { key: 'Openings.moe', - regExp: /^\w+:\/\/openings.moe\/\?video=([^&=]+\.webm)/, - style: 'max-width: 80vw; max-height: 80vh;', + regExp: /^\w+:\/\/openings.moe\/\?video=([^.&=]+)/, + style: 'width: 1280px; height: 720px; max-width: 80vw; max-height: 80vh;', el: function(a) { - return $.el('video', { - controls: true, - preload: 'auto', - src: "//openings.moe/video/" + a.dataset.uid, - loop: true + var el; + el = $.el('iframe', { + src: "https://openings.moe/?video=" + a.dataset.uid }); + el.setAttribute("allowfullscreen", "true"); + return el; } }, { key: 'Pastebin', @@ -13443,7 +17136,7 @@ Embedding = (function() { }, title: { api: function(uid) { - return "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid)); + return location.protocol + "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid)); }, text: function(_) { return _.title; @@ -13455,18 +17148,42 @@ Embedding = (function() { style: 'border: 0; width: 600px; height: 406px;', el: function(a) { return $.el('iframe', { - src: "//www.strawpoll.me/embed_1/" + a.dataset.uid + src: "https://www.strawpoll.me/embed_1/" + a.dataset.uid + }); + } + }, { + key: 'Streamable', + regExp: /^\w+:\/\/(?:www\.)?streamable\.com\/(\w+)/, + el: function(a) { + var el; + el = $.el('iframe', { + src: "https://streamable.com/o/" + a.dataset.uid }); + el.setAttribute("allowfullscreen", "true"); + return el; + }, + title: { + api: function(uid) { + return "https://api.streamable.com/oembed?url=https://streamable.com/" + uid; + }, + text: function(_) { + return _.title; + } } }, { key: 'TwitchTV', - regExp: /^\w+:\/\/(?:www\.|secure\.)?twitch\.tv\/(\w[^#\&\?]*)/, + regExp: /^\w+:\/\/(?:www\.|secure\.|clips\.|m\.)?twitch\.tv\/(\w[^#\&\?]*)/, el: function(a) { var el, m, time, url; - m = a.dataset.uid.match(/(\w+)(?:\/v\/(\d+))?/); - url = "//player.twitch.tv/?" + (m[2] ? "video=v" + m[2] : "channel=" + m[1]) + "&autoplay=false"; - if ((time = a.dataset.href.match(/\bt=(\w+)/))) { - url += "&time=" + time[1]; + m = a.dataset.href.match(/^\w+:\/\/(?:(clips\.)|\w+\.)?twitch\.tv\/(?:\w+\/)?(clip\/)?(\w[^#\&\?]*)/); + if (m[1] || m[2]) { + url = "//clips.twitch.tv/embed?clip=" + m[3] + "&parent=" + location.hostname; + } else { + m = a.dataset.uid.match(/(\w+)(?:\/(?:v\/)?(\d+))?/); + url = "//player.twitch.tv/?" + (m[2] ? "video=v" + m[2] : "channel=" + m[1]) + "&autoplay=false&parent=" + location.hostname; + if ((time = a.dataset.href.match(/\bt=(\w+)/))) { + url += "&time=" + time[1]; + } } el = $.el('iframe', { src: url @@ -13476,11 +17193,45 @@ Embedding = (function() { } }, { key: 'Twitter', - regExp: /^\w+:\/\/(?:www\.)?twitter\.com\/(\w+\/status\/\d+)/, + regExp: /^\w+:\/\/(?:www\.|mobile\.)?twitter\.com\/(\w+\/status\/\d+)/, + style: 'border: none; width: 550px; height: 250px; overflow: hidden; resize: both;', el: function(a) { - return $.el('iframe', { - src: "https://twitframe.com/show?url=https://twitter.com/" + a.dataset.uid + var cont, el, onMessage; + el = $.el('iframe'); + $.on(el, 'load', function() { + return this.contentWindow.postMessage({ + element: 't', + query: 'height' + }, 'https://twitframe.com'); + }); + onMessage = function(e) { + if (e.source === el.contentWindow && e.origin === 'https://twitframe.com') { + $.off(window, 'message', onMessage); + return (cont || el).style.height = (+$.minmax(e.data.height, 250, 0.8 * doc.clientHeight)) + "px"; + } + }; + $.on(window, 'message', onMessage); + el.src = "https://twitframe.com/show?url=https://twitter.com/" + a.dataset.uid; + if ($.engine === 'gecko') { + el.style.cssText = 'border: none; width: 100%; height: 100%;'; + cont = $.el('div'); + $.add(cont, el); + return cont; + } else { + return el; + } + } + }, { + key: 'VidLii', + regExp: /^\w+:\/\/(?:www\.)?vidlii\.com\/watch\?v=(\w{11})/, + style: 'border: none; width: 640px; height: 392px;', + el: function(a) { + var el; + el = $.el('iframe', { + src: "https://www.vidlii.com/embed?v=" + a.dataset.uid + "&a=0" }); + el.setAttribute("allowfullscreen", "true"); + return el; } }, { key: 'Vimeo', @@ -13512,21 +17263,20 @@ Embedding = (function() { } }, { key: 'Vocaroo', - regExp: /^\w+:\/\/(?:www\.)?vocaroo\.com\/i\/(\w+)/, + regExp: /^\w+:\/\/(?:(?:www\.|old\.)?vocaroo\.com|voca\.ro)\/((?:i\/)?\w+)/, style: '', el: function(a) { - var el, type; - el = $.el('audio', { - controls: true, - preload: 'auto' - }); - type = el.canPlayType('audio/webm') ? 'webm' : 'mp3'; - el.src = "http://vocaroo.com/media_command.php?media=" + a.dataset.uid + "&command=download_" + type; + var el; + el = $.el('iframe'); + el.width = 300; + el.height = 60; + el.setAttribute('frameborder', 0); + el.src = "https://vocaroo.com/embed/" + (a.dataset.uid.replace(/^i\//, '')) + "?autoplay=0"; return el; } }, { key: 'YouTube', - regExp: /^\w+:\/\/(?:youtu.be\/|[\w.]*youtube[\w.]*\/.*(?:v=|\bembed\/|\bv\/))([\w\-]{11})(.*)/, + regExp: /^\w+:\/\/(?:youtu.be\/|[\w.]*youtube[\w.]*\/.*(?:v=|\bembed\/|\bv\/|live\/|shorts\/))([\w\-]{11})(.*)/, el: function(a) { var el, start; start = a.dataset.options.match(/\b(?:star)?t\=(\w+)/); @@ -13538,30 +17288,33 @@ Embedding = (function() { start = 3600 * start.match(/(\d+)h/)[1] + 60 * start.match(/(\d+)m/)[1] + 1 * start.match(/(\d+)s/)[1]; } el = $.el('iframe', { - src: "//www.youtube.com/embed/" + a.dataset.uid + "?wmode=opaque" + (start ? '&start=' + start : '') + src: "//www.youtube.com/embed/" + a.dataset.uid + "?rel=0&wmode=opaque" + (start ? '&start=' + start : '') }); el.setAttribute("allowfullscreen", "true"); return el; }, title: { - batchSize: 50, - api: function(uids) { - var ids, key; - ids = encodeURIComponent(uids.join(',')); - key = 'AIzaSyB5_zaen_-46Uhz1xGR-lz1YoUMHqCD6CE'; - return "https://www.googleapis.com/youtube/v3/videos?part=snippet&id=" + ids + "&fields=items%28id%2Csnippet%28title%29%29&key=" + key; + api: function(uid) { + return "https://www.youtube.com/oembed?url=https%3A//www.youtube.com/watch%3Fv%3D" + uid + "&format=json"; }, - text: function(data, uid) { - var item, j, len, ref; - ref = data.items; - for (j = 0, len = ref.length; j < len; j++) { - item = ref[j]; - if (item.id === uid) { - return item.snippet.title; - } + text: function(_) { + return _.title; + }, + status: function(_) { + var m; + if (_.error) { + m = _.error.match(/^(\d*)\s*(.*)/); + return [+m[1], m[2]]; + } else { + return [200, 'OK']; } - return 'Not Found'; } + }, + preview: { + url: function(uid) { + return "https://img.youtube.com/vi/" + uid + "/0.jpg"; + }, + height: 360 } } ] @@ -13577,7 +17330,7 @@ Linkify = (function() { Linkify = { init: function() { var ref; - if (((ref = g.VIEW) !== 'index' && ref !== 'thread') || !Conf['Linkify']) { + if (((ref = g.VIEW) !== 'index' && ref !== 'thread' && ref !== 'archive') || !Conf['Linkify']) { return; } if (Conf['Comment Expansion']) { @@ -13587,38 +17340,37 @@ Linkify = (function() { name: 'Linkify', cb: this.node }); - Callbacks.CatalogThread.push({ - name: 'Linkify', - cb: this.catalogNode - }); return Embedding.init(); }, node: function() { - var j, k, len, len1, link, links, ref; + var base, j, k, len, len1, link, links, ref; if (this.isClone) { return Embedding.events(this); } if (!Linkify.regString.test(this.info.comment)) { return; } - ref = $$('a[href^="http://i.4cdn.org/"], a[href^="https://i.4cdn.org/"]', this.nodes.comment); + ref = $$('a', this.nodes.comment); for (j = 0, len = ref.length; j < len; j++) { link = ref[j]; + if (!(typeof (base = g.SITE).isLinkified === "function" ? base.isLinkified(link) : void 0)) { + continue; + } $.addClass(link, 'linkify'); + if (ImageHost.useFaster) { + ImageHost.fixLinks([link]); + } Embedding.process(link, this); } links = Linkify.process(this.nodes.comment); + if (ImageHost.useFaster) { + ImageHost.fixLinks(links); + } for (k = 0, len1 = links.length; k < len1; k++) { link = links[k]; Embedding.process(link, this); } }, - catalogNode: function() { - if (!Linkify.regString.test(this.thread.OP.info.comment)) { - return; - } - return Linkify.process(this.nodes.comment); - }, process: function(node) { var data, end, endNode, i, index, length, links, part1, part2, ref, ref1, result, saved, snapshot, space, test, word; test = /[^\s"]+/g; @@ -13638,13 +17390,16 @@ Linkify = (function() { if ((length = index + word.length) === data.length) { test.lastIndex = 0; while ((saved = snapshot.snapshotItem(i++))) { - if (saved.nodeName === 'BR') { + if (saved.nodeName === 'BR' || (saved.parentElement.nodeName === 'P' && !saved.previousSibling)) { if ((part1 = word.match(/(https?:\/\/)?([a-z\d-]+\.)*[a-z\d-]+$/i)) && (part2 = (ref = snapshot.snapshotItem(i)) != null ? (ref1 = ref.data) != null ? ref1.match(/^(\.[a-z\d-]+)*\//i) : void 0 : void 0) && (part1[0] + part2[0]).search(Linkify.regString) === 0) { continue; } else { break; } } + if (saved.parentElement.nodeName === "A" && !Linkify.regString.test(word)) { + break; + } endNode = saved; data = saved.data; if (end = space.exec(data)) { @@ -13743,7 +17498,7 @@ ArchiveLink = (function() { ArchiveLink = { init: function() { var div, entry, i, len, ref, ref1, type; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Menu'] && Conf['Archive Link'])) { + if (!(g.SITE.software === 'yotsuba' && ((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Menu'] && Conf['Archive Link'])) { return; } div = $.el('div', { @@ -13751,7 +17506,7 @@ ArchiveLink = (function() { }); entry = { el: div, - order: 90, + order: 60, open: function(arg) { var ID, board, thread; ID = arg.ID, thread = arg.thread, board = arg.board; @@ -13786,14 +17541,15 @@ ArchiveLink = (function() { }); return true; } : function(post) { - var value; - value = type === 'country' ? post.info.flagCode : Filter[type](post); + var ref, typeParam, value; + typeParam = type === 'country' && post.info.flagCodeTroll ? 'troll_country' : type; + value = type === 'country' ? post.info.flagCode || ((ref = post.info.flagCodeTroll) != null ? ref.toLowerCase() : void 0) : Filter.values(type, post)[0]; if (!value) { return false; } el.href = Redirect.to('search', { boardID: post.board.ID, - type: type, + type: typeParam, value: value, isSearch: true }); @@ -13810,11 +17566,54 @@ ArchiveLink = (function() { }).call(this); +CopyTextLink = (function() { + var CopyTextLink; + + CopyTextLink = { + init: function() { + var a, ref; + if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Menu'] && Conf['Copy Text Link'])) { + return; + } + a = $.el('a', { + className: 'copy-text-link', + href: 'javascript:;', + textContent: 'Copy Text' + }); + $.on(a, 'click', CopyTextLink.copy); + return Menu.menu.addEntry({ + el: a, + order: 12, + open: function(post) { + CopyTextLink.text = (post.origin || post).commentOrig(); + return true; + } + }); + }, + copy: function() { + var el; + el = $.el('textarea', { + className: 'copy-text-element', + value: CopyTextLink.text + }); + $.add(d.body, el); + el.select(); + try { + d.execCommand('copy'); + } catch (error) {} + return $.rm(el); + } + }; + + return CopyTextLink; + +}).call(this); + DeleteLink = (function() { var DeleteLink; DeleteLink = { - auto: [{}, {}], + auto: [$.dict(), $.dict()], init: function() { var div, fileEl, fileEntry, postEl, postEntry, ref; if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Menu'] && Conf['Delete Link'])) { @@ -13915,27 +17714,28 @@ DeleteLink = (function() { onlyimgdel: fileOnly, pwd: QR.persona.getPassword() }; - form[post.ID] = 'delete'; + form[+post.ID] = 'delete'; return $.ajax($.id('delform').action.replace("/" + g.BOARD + "/", "/" + post.board + "/"), { responseType: 'document', withCredentials: true, - onload: function() { + onloadend: function() { return DeleteLink.load(link, post, fileOnly, this.response); }, - onerror: function() { - return DeleteLink.error(link, post); - } - }, { form: $.formData(form) }); }, load: function(link, post, fileOnly, resDoc) { var el, msg; + if (!resDoc) { + new Notice('warning', 'Connection error, please retry.', 20); + if (post.fullID === DeleteLink.post.fullID) { + $.on(link, 'click', DeleteLink.toggle); + } + return; + } link.textContent = DeleteLink.linkText(fileOnly); if (resDoc.title === '4chan - Banned') { - el = $.el('span', { - innerHTML: "You can't delete posts because you are banned." - }); + el = $.el('span', {innerHTML: "You can't delete posts because you are banned."}); return new Notice('warning', el, 20); } else if (msg = resDoc.getElementById('errmsg')) { new Notice('warning', msg.textContent, 20); @@ -13959,14 +17759,8 @@ DeleteLink = (function() { } } }, - error: function(link, post) { - new Notice('warning', 'Connection error, please retry.', 20); - if (post.fullID === DeleteLink.post.fullID) { - return $.on(link, 'click', DeleteLink.toggle); - } - }, cooldown: { - seconds: {}, + seconds: $.dict(), start: function(post, seconds) { if (DeleteLink.cooldown.seconds[post.fullID] != null) { return; @@ -14053,9 +17847,7 @@ Menu = (function() { className: 'menu-button', href: 'javascript:;' }); - $.extend(this.button, { - innerHTML: "" - }); + $.extend(this.button, {innerHTML: ""}); this.menu = new UI.Menu('post'); Callbacks.Post.push({ name: 'Menu', @@ -14071,7 +17863,7 @@ Menu = (function() { if (this.isClone) { button = $('.menu-button', this.nodes.info); $.rmClass(button, 'active'); - $.rm($('.dialog', button)); + $.rm($('.dialog', this.nodes.info)); Menu.makeButton(this, button); return; } @@ -14104,26 +17896,21 @@ ReportLink = (function() { } a = $.el('a', { className: 'report-link', - href: 'javascript:;' + href: 'javascript:;', + textContent: 'Report' }); $.on(a, 'click', ReportLink.report); return Menu.menu.addEntry({ el: a, order: 10, open: function(post) { - if (!(post.isDead || (post.thread.isDead && !post.thread.isArchived))) { - a.textContent = 'Report'; - ReportLink.url = "//sys.4chan.org/" + post.board + "/imgboard.php?mode=report&no=" + post; - if ((Conf['Use Recaptcha v1 in Reports'] && Main.jsEnabled) || d.cookie.indexOf('pass_enabled=1') >= 0) { - ReportLink.url += '&altc=1'; - ReportLink.dims = 'width=350,height=275'; - } else { - ReportLink.dims = 'width=400,height=550'; - } + ReportLink.url = "//sys." + (location.hostname.split('.')[1]) + ".org/" + post.board + "/imgboard.php?mode=report&no=" + post; + if (d.cookie.indexOf('pass_enabled=1') >= 0) { + ReportLink.dims = 'width=350,height=275'; } else { - ReportLink.url = ''; + ReportLink.dims = 'width=400,height=550'; } - return !!ReportLink.url; + return true; } }); }, @@ -14164,10 +17951,6 @@ AntiAutoplay = (function() { name: 'Disable Autoplaying Sounds', cb: this.node }); - Callbacks.CatalogThread.push({ - name: 'Disable Autoplaying Sounds', - cb: this.node - }); return $.ready((function(_this) { return function() { return _this.process(d.body); @@ -14187,22 +17970,27 @@ AntiAutoplay = (function() { return $.addClass(audio, 'controls-added'); }, node: function() { - return AntiAutoplay.process(this.nodes.root); + return AntiAutoplay.process(this.nodes.comment); }, process: function(root) { var i, iframe, j, len, len1, object, ref, ref1; ref = $$('iframe[src*="youtube"][src*="autoplay=1"]', root); for (i = 0, len = ref.length; i < len; i++) { iframe = ref[i]; - iframe.src = iframe.src.replace(/\?autoplay=1&?/, '?').replace('&autoplay=1', ''); - $.addClass(iframe, 'autoplay-removed'); + AntiAutoplay.processVideo(iframe, 'src'); } ref1 = $$('object[data*="youtube"][data*="autoplay=1"]', root); for (j = 0, len1 = ref1.length; j < len1; j++) { object = ref1[j]; - object.data = object.data.replace(/\?autoplay=1&?/, '?').replace('&autoplay=1', ''); - $.addClass(object, 'autoplay-removed'); + AntiAutoplay.processVideo(object, 'data'); + } + }, + processVideo: function(el, attr) { + el[attr] = el[attr].replace(/\?autoplay=1&?/, '?').replace('&autoplay=1', ''); + if (window.getComputedStyle(el).display === 'none') { + el.style.display = 'block'; } + return $.addClass(el, 'autoplay-removed'); } }; @@ -14215,7 +18003,6 @@ Banner = (function() { slice = [].slice; Banner = { - banners: ["0.jpg","1.jpg","2.jpg","4.jpg","6.jpg","7.jpg","8.jpg","9.jpg","10.jpg","11.jpg","12.jpg","13.jpg","14.jpg","16.jpg","17.jpg","18.jpg","19.jpg","20.jpg","21.jpg","22.jpg","24.jpg","25.jpg","26.jpg","28.jpg","29.jpg","33.jpg","38.jpg","39.jpg","43.jpg","44.jpg","45.jpg","46.jpg","47.jpg","52.jpg","54.jpg","57.jpg","59.jpg","60.jpg","61.jpg","64.jpg","66.jpg","67.jpg","69.jpg","71.jpg","72.jpg","76.jpg","77.jpg","81.jpg","82.jpg","83.jpg","84.jpg","88.jpg","90.jpg","91.jpg","96.jpg","98.jpg","99.jpg","100.jpg","104.jpg","106.jpg","116.jpg","119.jpg","137.jpg","140.jpg","148.jpg","149.jpg","150.jpg","154.jpg","156.jpg","157.jpg","158.jpg","159.jpg","161.jpg","162.jpg","164.jpg","165.jpg","166.jpg","167.jpg","168.jpg","169.jpg","170.jpg","171.jpg","172.jpg","173.jpg","174.jpg","175.jpg","176.jpg","178.jpg","179.jpg","180.jpg","181.jpg","182.jpg","183.jpg","186.jpg","189.jpg","190.jpg","192.jpg","193.jpg","194.jpg","197.jpg","198.jpg","200.jpg","201.jpg","202.jpg","203.jpg","205.jpg","206.jpg","207.jpg","208.jpg","210.jpg","213.jpg","214.jpg","215.jpg","216.jpg","218.jpg","219.jpg","220.jpg","221.jpg","222.jpg","223.jpg","224.jpg","227.jpg","0.png","1.png","2.png","3.png","5.png","6.png","9.png","10.png","11.png","12.png","14.png","16.png","19.png","20.png","21.png","22.png","23.png","24.png","26.png","27.png","28.png","29.png","30.png","31.png","32.png","33.png","34.png","37.png","39.png","40.png","41.png","42.png","43.png","44.png","45.png","48.png","49.png","50.png","51.png","52.png","53.png","57.png","58.png","59.png","64.png","66.png","67.png","68.png","69.png","70.png","71.png","72.png","76.png","78.png","79.png","81.png","82.png","85.png","86.png","87.png","89.png","95.png","98.png","100.png","101.png","102.png","105.png","106.png","107.png","109.png","110.png","111.png","112.png","113.png","114.png","115.png","116.png","118.png","119.png","120.png","121.png","122.png","123.png","126.png","128.png","130.png","134.png","136.png","138.png","139.png","140.png","142.png","145.png","146.png","149.png","150.png","151.png","152.png","153.png","154.png","155.png","156.png","157.png","158.png","159.png","160.png","163.png","164.png","165.png","166.png","167.png","168.png","169.png","170.png","171.png","172.png","173.png","174.png","178.png","179.png","180.png","181.png","182.png","184.png","186.png","188.png","190.png","192.png","193.png","194.png","195.png","196.png","197.png","198.png","200.png","202.png","203.png","205.png","206.png","207.png","209.png","212.png","213.png","214.png","216.png","217.png","218.png","219.png","220.png","221.png","222.png","223.png","224.png","225.png","226.png","229.png","231.png","232.png","233.png","234.png","235.png","237.png","238.png","239.png","240.png","241.png","242.png","244.png","245.png","246.png","247.png","248.png","249.png","250.png","253.png","254.png","255.png","256.png","257.png","258.png","259.png","260.png","262.png","268.png","0.gif","1.gif","2.gif","3.gif","4.gif","5.gif","6.gif","7.gif","8.gif","9.gif","10.gif","12.gif","13.gif","14.gif","15.gif","16.gif","18.gif","19.gif","20.gif","21.gif","22.gif","23.gif","24.gif","28.gif","29.gif","30.gif","33.gif","34.gif","35.gif","36.gif","37.gif","39.gif","40.gif","42.gif","44.gif","45.gif","46.gif","48.gif","50.gif","52.gif","54.gif","55.gif","57.gif","58.gif","59.gif","60.gif","61.gif","63.gif","64.gif","66.gif","67.gif","68.gif","69.gif","70.gif","72.gif","73.gif","75.gif","76.gif","77.gif","78.gif","80.gif","81.gif","82.gif","83.gif","86.gif","87.gif","88.gif","92.gif","93.gif","94.gif","95.gif","96.gif","97.gif","98.gif","99.gif","100.gif","101.gif","102.gif","103.gif","104.gif","105.gif","106.gif","108.gif","109.gif","110.gif","111.gif","112.gif","113.gif","115.gif","116.gif","117.gif","118.gif","119.gif","120.gif","122.gif","123.gif","124.gif","127.gif","129.gif","130.gif","131.gif","134.gif","135.gif","136.gif","138.gif","139.gif","141.gif","144.gif","146.gif","148.gif","149.gif","153.gif","154.gif","155.gif","157.gif","158.gif","159.gif","160.gif","161.gif","162.gif","164.gif","166.gif","167.gif","168.gif","169.gif","170.gif","171.gif","172.gif","173.gif","174.gif","175.gif","176.gif","177.gif","178.gif","181.gif","182.gif","183.gif","185.gif","186.gif","187.gif","188.gif","189.gif","190.gif","191.gif","192.gif","193.gif","195.gif","196.gif","197.gif","200.gif","201.gif","202.gif","203.gif","204.gif","205.gif","206.gif","207.gif","208.gif","209.gif","210.gif","211.gif","212.gif","213.gif","214.gif","215.gif","216.gif","217.gif","219.gif","220.gif","221.gif","222.gif","224.gif","225.gif","226.gif","227.gif","228.gif","230.gif","232.gif","233.gif","234.gif","235.gif","238.gif","240.gif","241.gif","243.gif","244.gif","245.gif","246.gif","247.gif","249.gif","250.gif","251.gif","253.gif"], init: function() { if (Conf['Custom Board Titles']) { this.db = new DataBoard('customTitles', null, true); @@ -14237,6 +18024,9 @@ Banner = (function() { var banner, children; banner = $(".boardBanner"); children = banner.children; + if (g.VIEW === 'thread' && Conf['Remove Thread Excerpt']) { + Banner.setTitle(children[1].textContent); + } children[0].title = "Click to change"; $.on(children[0], 'click', Banner.cb.toggle); if (Conf['Custom Board Titles']) { @@ -14269,7 +18059,7 @@ Banner = (function() { toggle: function() { var banner, i, ref; if (!((ref = Banner.choices) != null ? ref.length : void 0)) { - Banner.choices = Banner.banners.slice(); + Banner.choices = Conf['knownBanners'].split(',').slice(); } i = Math.floor(Banner.choices.length * Math.random()); banner = Banner.choices.splice(i, 1); @@ -14324,7 +18114,7 @@ Banner = (function() { } } }, - original: {}, + original: $.dict(), custom: function(child) { var className, data, event, j, len, ref; className = child.className; @@ -14362,7 +18152,7 @@ CatalogLinks = (function() { CatalogLinks = { init: function() { var el, input, selector; - if ((Conf['External Catalog'] || Conf['JSON Index']) && !(Conf['JSON Index'] && g.VIEW === 'index')) { + if (g.SITE.software === 'yotsuba' && (Conf['External Catalog'] || Conf['JSON Index']) && !(Conf['JSON Index'] && g.VIEW === 'index')) { selector = (function() { switch (g.VIEW) { case 'thread': @@ -14375,7 +18165,7 @@ CatalogLinks = (function() { } })(); $.ready(function() { - var catalogLink, i, len, link, ref; + var base, catalogLink, catalogURL, i, len, link, link2, ref; ref = $$(selector); for (i = 0, len = ref.length; i < len; i++) { link = ref[i]; @@ -14389,26 +18179,23 @@ CatalogLinks = (function() { case "/" + g.BOARD + "/catalog": link.href = CatalogLinks.catalog(); } - if (g.VIEW === 'catalog' && Conf['JSON Index'] && Conf['Use 4chan X Catalog']) { + if (g.VIEW === 'catalog' && (catalogURL = CatalogLinks.catalog()) !== (typeof (base = g.SITE.urls).catalog === "function" ? base.catalog(g.BOARD) : void 0)) { catalogLink = link.parentNode.cloneNode(true); - catalogLink.firstElementChild.textContent = '4chan X Catalog'; - catalogLink.firstElementChild.href = CatalogLinks.catalog(); + link2 = catalogLink.firstElementChild; + link2.href = catalogURL; + link2.textContent = link2.hostname === location.hostname ? '4chan X Catalog' : 'External Catalog'; $.after(link.parentNode, [$.tn(' '), catalogLink]); } } }); } - if (Conf['JSON Index'] && Conf['Use 4chan X Catalog']) { + if (g.SITE.software === 'yotsuba' && Conf['JSON Index'] && Conf['Use 4chan X Catalog']) { Callbacks.Post.push({ name: 'Catalog Link Rewrite', cb: this.node }); - Callbacks.CatalogThread.push({ - name: 'Catalog Link Rewrite', - cb: this.node - }); } - if (Conf['Catalog Links']) { + if ((this.enabled = Conf['Catalog Links'])) { CatalogLinks.el = el = UI.checkbox('Header catalog links', 'Catalog Links'); el.id = 'toggleCatalog'; input = $('input', el); @@ -14425,66 +18212,121 @@ CatalogLinks = (function() { ref = $$('a', this.nodes.comment); for (i = 0, len = ref.length; i < len; i++) { a = ref[i]; - if (m = a.href.match(/^https?:\/\/boards\.4chan\.org\/([^\/]+)\/catalog(#s=.*)?/)) { - a.href = "//boards.4chan.org/" + m[1] + "/" + (m[2] || '#catalog'); + if (m = a.href.match(/^https?:\/\/(boards\.4chan(?:nel)?\.org\/[^\/]+)\/catalog(#s=.*)?/)) { + a.href = "//" + m[1] + "/" + (m[2] || '#catalog'); } } }, - initBoardList: function() { - if (!CatalogLinks.el) { - return; - } - return CatalogLinks.set(Conf['Header catalog links']); - }, toggle: function() { $.event('CloseMenu'); $.set('Header catalog links', this.checked); return CatalogLinks.set(this.checked); }, set: function(useCatalog) { - var a, board, i, len, ref, ref1; - ref = $$('a:not([data-only])', Header.boardList).concat($$('a', Header.bottomBoardList)); + Conf['Header catalog links'] = useCatalog; + CatalogLinks.setLinks(Header.boardList); + CatalogLinks.setLinks(Header.bottomBoardList); + CatalogLinks.el.title = "Turn catalog links " + (useCatalog ? 'off' : 'on') + "."; + return $('input', CatalogLinks.el).checked = useCatalog; + }, + setLinks: function(list) { + var VIEW, a, board, boardID, i, len, ref, ref1, ref2, ref3, siteID, tail, url; + if (!(((ref = CatalogLinks.enabled) != null ? ref : Conf['Catalog Links']) && list)) { + return; + } + tail = /(?:index)?(?:\.\w+)?$/; + ref1 = $$('a:not([data-only])', list); + for (i = 0, len = ref1.length; i < len; i++) { + a = ref1[i]; + ref2 = a.dataset, siteID = ref2.siteID, boardID = ref2.boardID; + if (!(siteID && boardID)) { + ref3 = Site.parseURL(a), siteID = ref3.siteID, boardID = ref3.boardID, VIEW = ref3.VIEW; + if (!(siteID && boardID && (VIEW === 'index' || VIEW === 'catalog') && (a.dataset.indexOptions || a.href.replace(tail, '') === (Get.url(VIEW, { + siteID: siteID, + boardID: boardID + }) || '').replace(tail, '')))) { + continue; + } + $.extend(a.dataset, { + siteID: siteID, + boardID: boardID + }); + } + board = { + siteID: siteID, + boardID: boardID + }; + url = Conf['Header catalog links'] ? CatalogLinks.catalog(board) : Get.url('index', board); + if (url) { + a.href = url; + if (a.dataset.indexOptions && url.split('#')[0] === Get.url('index', board)) { + a.href += (a.hash ? '/' : '#') + a.dataset.indexOptions; + } + } + } + }, + externalParse: function() { + var board, boards, excludes, i, len, line, ref, ref1, ref2, url; + CatalogLinks.externalList = $.dict(); + ref = Conf['externalCatalogURLs'].split('\n'); for (i = 0, len = ref.length; i < len; i++) { - a = ref[i]; - if (((ref1 = a.hostname) !== 'boards.4chan.org' && ref1 !== 'catalog.neet.tv') || !(board = a.pathname.split('/')[1]) || (board === 'f' || board === 'status' || board === '4chan') || a.pathname.split('/')[2] === 'archive' || $.hasClass(a, 'external')) { + line = ref[i]; + if (line[0] === '#') { continue; } - a.href = useCatalog ? CatalogLinks.catalog(board) : "/" + board + "/"; - if (a.dataset.indexOptions && a.hostname === 'boards.4chan.org' && a.pathname.split('/')[2] === '') { - a.href += (a.hash ? '/' : '#') + a.dataset.indexOptions; + url = line.split(';')[0]; + boards = Filter.parseBoards(((ref1 = line.match(/;boards:([^;]+)/)) != null ? ref1[1] : void 0) || '*'); + excludes = Filter.parseBoards((ref2 = line.match(/;exclude:([^;]+)/)) != null ? ref2[1] : void 0) || $.dict(); + for (board in boards) { + if (!(excludes[board] || excludes[board.split('/')[0] + '/*'])) { + CatalogLinks.externalList[board] = url; + } } } - CatalogLinks.el.title = "Turn catalog links " + (useCatalog ? 'off' : 'on') + "."; - return $('input', CatalogLinks.el).checked = useCatalog; + }, + external: function(arg) { + var boardID, external, siteID; + siteID = arg.siteID, boardID = arg.boardID; + if (!CatalogLinks.externalList) { + CatalogLinks.externalParse(); + } + external = CatalogLinks.externalList[siteID + "/" + boardID] || CatalogLinks.externalList[siteID + "/*"]; + if (external) { + return external.replace(/%board/g, boardID); + } else { + return void 0; + } + }, + jsonIndex: function(board, hash) { + if (g.SITE.ID === board.siteID && g.BOARD.ID === board.boardID && g.VIEW === 'index') { + return hash; + } else { + return Get.url('index', board) + hash; + } }, catalog: function(board) { + var external, nativeCatalog; if (board == null) { - board = g.BOARD.ID; - } - if (Conf['External Catalog'] && (board === 'a' || board === 'c' || board === 'g' || board === 'biz' || board === 'k' || board === 'm' || board === 'o' || board === 'p' || board === 'v' || board === 'vg' || board === 'vr' || board === 'w' || board === 'wg' || board === 'cm' || board === '3' || board === 'adv' || board === 'an' || board === 'asp' || board === 'cgl' || board === 'ck' || board === 'co' || board === 'diy' || board === 'fa' || board === 'fit' || board === 'gd' || board === 'int' || board === 'jp' || board === 'lit' || board === 'mlp' || board === 'mu' || board === 'n' || board === 'out' || board === 'po' || board === 'sci' || board === 'sp' || board === 'tg' || board === 'toy' || board === 'trv' || board === 'tv' || board === 'vp' || board === 'wsg' || board === 'x' || board === 'f' || board === 'pol' || board === 's4s' || board === 'lgbt')) { - return "http://catalog.neet.tv/" + board + "/"; - } else if (Conf['JSON Index'] && Conf['Use 4chan X Catalog']) { - if (g.BOARD.ID === board && g.VIEW === 'index') { - return '#catalog'; - } else { - return "/" + board + "/#catalog"; - } + board = g.BOARD; + } + if (Conf['External Catalog'] && (external = CatalogLinks.external(board))) { + return external; + } else if (Index.enabledOn(board) && Conf['Use 4chan X Catalog']) { + return CatalogLinks.jsonIndex(board, '#catalog'); + } else if ((nativeCatalog = Get.url('catalog', board))) { + return nativeCatalog; } else { - return "/" + board + "/catalog"; + return CatalogLinks.external(board); } }, index: function(board) { if (board == null) { - board = g.BOARD.ID; + board = g.BOARD; } - if (Conf['JSON Index'] && board !== 'f') { - if (g.BOARD.ID === board && g.VIEW === 'index') { - return '#index'; - } else { - return "/" + board + "/#index"; - } + if (Index.enabledOn(board)) { + return CatalogLinks.jsonIndex(board, '#index'); } else { - return "/" + board + "/"; + return Get.url('index', board); } } }; @@ -14504,7 +18346,7 @@ CustomCSS = (function() { return this.addStyle(); }, addStyle: function() { - return this.style = $.addStyle(Conf['usercss'], 'custom-css', '#fourchanx-css'); + return this.style = $.addStyle(CSS.sub(Conf['usercss']), 'custom-css', '#fourchanx-css'); }, rmStyle: function() { if (this.style) { @@ -14516,7 +18358,7 @@ CustomCSS = (function() { if (!this.style) { return this.addStyle(); } - return this.style.textContent = Conf['usercss']; + return this.style.textContent = CSS.sub(Conf['usercss']); } }; @@ -14532,12 +18374,6 @@ ExpandComment = (function() { if (g.VIEW !== 'index' || !Conf['Comment Expansion'] || Conf['JSON Index']) { return; } - if (g.BOARD.ID === 'g') { - this.callbacks.push(Fourchan.code); - } - if (g.BOARD.ID === 'sci') { - this.callbacks.push(Fourchan.math); - } return Callbacks.Post.push({ name: 'Comment Expansion', cb: this.node @@ -14565,7 +18401,10 @@ ExpandComment = (function() { return; } a.textContent = "Post No." + post + " Loading..."; - return $.cache("//a.4cdn.org" + (a.pathname.split(/\/+/).splice(0, 4).join('/')) + ".json", function() { + return $.cache(g.SITE.urls.threadJSON({ + boardID: post.boardID, + threadID: post.threadID + }), function() { return ExpandComment.parse(this, a, post); }); }, @@ -14583,12 +18422,12 @@ ExpandComment = (function() { var callback, clone, comment, href, i, j, k, len, len1, len2, postObj, posts, quote, ref, ref1, spoilerRange, status; status = req.status; if (status !== 200 && status !== 304) { - a.textContent = "Error " + req.statusText + " (" + status + ")"; + a.textContent = status ? "Error " + req.statusText + " (" + status + ")" : 'Connection Error'; return; } posts = req.response.posts; if (spoilerRange = posts[0].custom_spoiler) { - Build.spoilerRange[g.BOARD] = spoilerRange; + g.SITE.Build.spoilerRange[g.BOARD] = spoilerRange; } for (i = 0, len = posts.length; i < len; i++) { postObj = posts[i]; @@ -14638,13 +18477,13 @@ ExpandThread = (function() { slice = [].slice; ExpandThread = { - statuses: {}, + statuses: $.dict(), init: function() { - if (g.VIEW === 'thread' || !Conf['Thread Expansion']) { + if (!(g.VIEW === 'index' && Conf['Thread Expansion'])) { return; } if (Conf['JSON Index']) { - return $.on(d, 'IndexRefresh', this.onIndexRefresh); + return $.on(d, 'IndexRefreshInternal', this.onIndexRefresh); } else { return Callbacks.Thread.push({ name: 'Expand Thread', @@ -14655,29 +18494,30 @@ ExpandThread = (function() { } }, setButton: function(thread) { - var a; - if (!(a = $.x('following-sibling::*[contains(@class,"summary")][1]', thread.OP.nodes.root))) { + var a, ref; + if (!(thread.nodes.root && (a = $('.summary', thread.nodes.root)))) { return; } - a.textContent = Build.summaryText.apply(Build, ['+'].concat(slice.call(a.textContent.match(/\d+/g)))); + a.textContent = (ref = g.SITE.Build).summaryText.apply(ref, ['+'].concat(slice.call(a.textContent.match(/\d+/g)))); a.style.cursor = 'pointer'; return $.on(a, 'click', ExpandThread.cbToggle); }, disconnect: function(refresh) { - var ref, ref1, status, threadID; + var oldReq, ref, status, threadID; if (g.VIEW === 'thread' || !Conf['Thread Expansion']) { return; } ref = ExpandThread.statuses; for (threadID in ref) { status = ref[threadID]; - if ((ref1 = status.req) != null) { - ref1.abort(); + if ((oldReq = status.req)) { + delete status.req; + oldReq.abort(); } delete ExpandThread.statuses[threadID]; } if (!refresh) { - return $.off(d, 'IndexRefresh', this.onIndexRefresh); + return $.off(d, 'IndexRefreshInternal', this.onIndexRefresh); } }, onIndexRefresh: function() { @@ -14687,62 +18527,66 @@ ExpandThread = (function() { }); }, cbToggle: function(e) { - if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { + if ($.modifiedClick(e)) { return; } e.preventDefault(); return ExpandThread.toggle(Get.threadFromNode(this)); }, + cbToggleBottom: function(e) { + var bottom, thread; + if ($.modifiedClick(e)) { + return; + } + e.preventDefault(); + thread = Get.threadFromNode(this); + $.rm(this); + bottom = thread.nodes.root.getBoundingClientRect().bottom; + ExpandThread.toggle(thread); + return window.scrollBy(0, thread.nodes.root.getBoundingClientRect().bottom - bottom); + }, toggle: function(thread) { - var a, threadRoot; - threadRoot = thread.OP.nodes.root.parentNode; - if (!(a = $('.summary', threadRoot))) { + var a; + if (!(thread.nodes.root && (a = $('.summary', thread.nodes.root)))) { return; } if (thread.ID in ExpandThread.statuses) { - return ExpandThread.contract(thread, a, threadRoot); + return ExpandThread.contract(thread, a, thread.nodes.root); } else { return ExpandThread.expand(thread, a); } }, expand: function(thread, a) { - var status; + var ref, status; ExpandThread.statuses[thread] = status = {}; - a.textContent = Build.summaryText.apply(Build, ['...'].concat(slice.call(a.textContent.match(/\d+/g)))); - return status.req = $.cache("//a.4cdn.org/" + thread.board + "/thread/" + thread + ".json", function() { + a.textContent = (ref = g.SITE.Build).summaryText.apply(ref, ['...'].concat(slice.call(a.textContent.match(/\d+/g)))); + status.req = $.cache(g.SITE.urls.threadJSON({ + boardID: thread.board.ID, + threadID: thread.ID + }), function() { + if (this !== status.req) { + return; + } delete status.req; return ExpandThread.parse(this, thread, a); }); + return status.numReplies = $$(g.SITE.selectors.replyOriginal, thread.nodes.root).length; }, contract: function(thread, a, threadRoot) { - var filesCount, i, inlined, len, num, postsCount, replies, reply, status; + var filesCount, i, inlined, len, oldReq, postsCount, ref, replies, reply, status; status = ExpandThread.statuses[thread]; delete ExpandThread.statuses[thread]; - if (status.req) { - status.req.abort(); + if ((oldReq = status.req)) { + delete status.req; + oldReq.abort(); if (a) { - a.textContent = Build.summaryText.apply(Build, ['+'].concat(slice.call(a.textContent.match(/\d+/g)))); + a.textContent = (ref = g.SITE.Build).summaryText.apply(ref, ['+'].concat(slice.call(a.textContent.match(/\d+/g)))); } return; } replies = $$('.thread > .replyContainer', threadRoot); - if (!Conf['JSON Index'] || Conf['Show Replies']) { - num = (function() { - if (thread.isSticky) { - return 1; - } else { - switch (g.BOARD.ID) { - case 'b': - case 'vg': - return 3; - case 't': - return 1; - default: - return 5; - } - } - })(); - replies = replies.slice(0, -num); + if (status.numReplies) { + replies = replies.slice(0, -status.numReplies); } postsCount = 0; filesCount = 0; @@ -14759,15 +18603,19 @@ ExpandThread = (function() { } $.rm(reply); } - return a.textContent = Build.summaryText('+', postsCount, filesCount); + if (Index.enabled) { + $.event('PostsRemoved', null, a.parentNode); + } + a.textContent = g.SITE.Build.summaryText('+', postsCount, filesCount); + return $.rm($('.summary-bottom', threadRoot)); }, parse: function(req, thread, a) { - var filesCount, i, len, post, postData, posts, postsCount, postsRoot, ref, ref1, root; + var a2, filesCount, i, len, post, postData, posts, postsCount, postsRoot, ref, ref1, root; if ((ref = req.status) !== 200 && ref !== 304) { - a.textContent = "Error " + req.statusText + " (" + req.status + ")"; + a.textContent = req.status ? "Error " + req.statusText + " (" + req.status + ")" : 'Connection Error'; return; } - Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler; + g.SITE.Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler; posts = []; postsRoot = []; filesCount = 0; @@ -14777,14 +18625,15 @@ ExpandThread = (function() { if (postData.no === thread.ID) { continue; } - if ((post = thread.posts[postData.no]) && !post.isFetchedQuote) { + if ((post = thread.posts.get(postData.no)) && !post.isFetchedQuote) { if ('file' in post) { filesCount++; } - postsRoot.push(post.nodes.root); + root = post.nodes.root; + postsRoot.push(root); continue; } - root = Build.postFromObject(postData, thread.board.ID); + root = g.SITE.Build.postFromObject(postData, thread.board.ID); post = new Post(root, thread, thread.board); if ('file' in post) { filesCount++; @@ -14794,9 +18643,15 @@ ExpandThread = (function() { } Main.callbackNodes('Post', posts); $.after(a, postsRoot); - $.event('PostsInserted'); + $.event('PostsInserted', null, a.parentNode); postsCount = postsRoot.length; - return a.textContent = Build.summaryText('-', postsCount, filesCount); + a.textContent = g.SITE.Build.summaryText('-', postsCount, filesCount); + if (root) { + a2 = a.cloneNode(true); + a2.classList.add('summary-bottom'); + $.on(a2, 'click', ExpandThread.cbToggleBottom); + return $.after(root, a2); + } } }; @@ -14810,7 +18665,7 @@ FileInfo = (function() { FileInfo = { init: function() { var ref; - if (((ref = g.VIEW) !== 'index' && ref !== 'thread') || !Conf['File Info Formatting']) { + if (((ref = g.VIEW) !== 'index' && ref !== 'thread' && ref !== 'archive') || !Conf['File Info Formatting']) { return; } return Callbacks.Post.push({ @@ -14819,7 +18674,7 @@ FileInfo = (function() { }); }, node: function() { - var a, i, info, len, oldInfo, ref; + var a, i, info, j, len, len1, oldInfo, ref, ref1; if (!this.file) { return; } @@ -14829,6 +18684,11 @@ FileInfo = (function() { a = ref[i]; $.on(a, 'click', ImageCommon.download); } + ref1 = $$('.file-info .quick-filter-md5', this.file.text); + for (j = 0, len1 = ref1.length; j < len1; j++) { + a = ref1[j]; + $.on(a, 'click', Filter.quickFilterMD5); + } return; } oldInfo = $.el('span', { @@ -14843,107 +18703,79 @@ FileInfo = (function() { return $.prepend(this.file.text, info); }, format: function(formatString, post, outputNode) { - var a, i, len, output, ref; + var a, i, j, len, len1, output, ref, ref1; output = []; formatString.replace(/%(.)|[^%]+/g, function(s, c) { - output.push(c in FileInfo.formatters ? FileInfo.formatters[c].call(post) : { - innerHTML: E(s) - }); + output.push($.hasOwn(FileInfo.formatters, c) ? FileInfo.formatters[c].call(post) : {innerHTML: E(s)}); return ''; }); - $.extend(outputNode, { - innerHTML: E.cat(output) - }); + $.extend(outputNode, {innerHTML: E.cat(output)}); ref = $$('.download-button', outputNode); for (i = 0, len = ref.length; i < len; i++) { a = ref[i]; $.on(a, 'click', ImageCommon.download); } + ref1 = $$('.quick-filter-md5', outputNode); + for (j = 0, len1 = ref1.length; j < len1; j++) { + a = ref1[j]; + $.on(a, 'click', Filter.quickFilterMD5); + } }, formatters: { t: function() { - return { - innerHTML: E(this.file.url.match(/[^/]*$/)[0]) - }; + return {innerHTML: E(this.file.url.match(/[^/]*$/)[0])}; }, T: function() { - return { - innerHTML: "" + (FileInfo.formatters.t.call(this)).innerHTML + "" - }; + return {innerHTML: "" + (FileInfo.formatters.t.call(this)).innerHTML + ""}; }, l: function() { - return { - innerHTML: "" + (FileInfo.formatters.n.call(this)).innerHTML + "" - }; + return {innerHTML: "" + (FileInfo.formatters.n.call(this)).innerHTML + ""}; }, L: function() { - return { - innerHTML: "" + (FileInfo.formatters.N.call(this)).innerHTML + "" - }; + return {innerHTML: "" + (FileInfo.formatters.N.call(this)).innerHTML + ""}; }, n: function() { var fullname, shortname; fullname = this.file.name; - shortname = Build.shortFilename(this.file.name, this.isReply); + shortname = SW.yotsuba.Build.shortFilename(this.file.name, this.isReply); if (fullname === shortname) { - return { - innerHTML: E(fullname) - }; + return {innerHTML: E(fullname)}; } else { - return { - innerHTML: "" + E(shortname) + "" + E(fullname) + "" - }; + return {innerHTML: "" + E(shortname) + "" + E(fullname) + ""}; } }, N: function() { - return { - innerHTML: E(this.file.name) - }; + return {innerHTML: E(this.file.name)}; }, d: function() { - return { - innerHTML: "" - }; + return {innerHTML: ""}; }, - p: function() { - return { - innerHTML: ((this.file.isSpoiler) ? "Spoiler, " : "") - }; + f: function() { + return {innerHTML: ""}; + }, + p: function() { + return {innerHTML: ((this.file.isSpoiler) ? "Spoiler, " : "")}; }, s: function() { - return { - innerHTML: E(this.file.size) - }; + return {innerHTML: E(this.file.size)}; }, B: function() { - return { - innerHTML: E(Math.round(this.file.sizeInBytes)) + " Bytes" - }; + return {innerHTML: E(Math.round(this.file.sizeInBytes)) + " Bytes"}; }, K: function() { - return { - innerHTML: E(Math.round(this.file.sizeInBytes/1024)) + " KB" - }; + return {innerHTML: E(Math.round(this.file.sizeInBytes/1024)) + " KB"}; }, M: function() { - return { - innerHTML: E(Math.round(this.file.sizeInBytes/1048576*100)/100) + " MB" - }; + return {innerHTML: E(Math.round(this.file.sizeInBytes/1048576*100)/100) + " MB"}; }, r: function() { - return { - innerHTML: E(this.file.dimensions || "PDF") - }; + return {innerHTML: E(this.file.dimensions || "PDF")}; }, g: function() { - return { - innerHTML: ((this.file.tag) ? ", " + E(this.file.tag) : "") - }; + return {innerHTML: ((this.file.tag) ? ", " + E(this.file.tag) : "")}; }, '%': function() { - return { - innerHTML: "%" - }; + return {innerHTML: "%"}; } } }; @@ -14991,16 +18823,20 @@ Fourchan = (function() { Fourchan = { init: function() { var ref; - if ((ref = g.VIEW) !== 'index' && ref !== 'thread') { + if (!(g.SITE.software === 'yotsuba' && ((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive'))) { return; } - if (g.BOARD.ID === 'g') { + BoardConfig.ready(this.initBoard); + return Main.ready(this.initReady); + }, + initBoard: function() { + if (g.BOARD.config.code_tags) { $.on(window, 'prettyprint:cb', function(e) { var post, pre; - if (!(post = g.posts[e.detail.ID])) { + if (!(post = g.posts.get(e.detail.ID))) { return; } - if (!(pre = $$('.prettyprint', post.nodes.comment)[e.detail.i])) { + if (!(pre = $$('.prettyprint', post.nodes.comment)[+e.detail.i])) { return; } if (!$.hasClass(pre, 'prettyprinted')) { @@ -15008,13 +18844,29 @@ Fourchan = (function() { return $.addClass(pre, 'prettyprinted'); } }); - $.globalEval('window.addEventListener(\'prettyprint\', function(e) {\n window.dispatchEvent(new CustomEvent(\'prettyprint:cb\', {\n detail: {\n ID: e.detail.ID,\n i: e.detail.i,\n html: prettyPrintOne(e.detail.html)\n }\n }));\n}, false);'); + $.global(function() { + return window.addEventListener('prettyprint', function(e) { + return window.dispatchEvent(new CustomEvent('prettyprint:cb', { + detail: { + ID: e.detail.ID, + i: e.detail.i, + html: window.prettyPrintOne(e.detail.html) + } + })); + }, false); + }); Callbacks.Post.push({ - name: 'Parse /g/ code', - cb: this.code + name: 'Parse [code] tags', + cb: Fourchan.code + }); + g.posts.forEach(function(post) { + if (post.callbacksExecuted) { + return Callbacks.Post.execute(post, ['Parse [code] tags'], true); + } }); + ExpandComment.callbacks.push(Fourchan.code); } - if (g.BOARD.ID === 'sci') { + if (g.BOARD.config.math_tags) { $.global(function() { return window.addEventListener('mathjax', function(e) { if (window.MathJax) { @@ -15033,24 +18885,26 @@ Fourchan = (function() { }, false); }); Callbacks.Post.push({ - name: 'Parse /sci/ math', - cb: this.math - }); - Callbacks.CatalogThread.push({ - name: 'Parse /sci/ math', - cb: this.math + name: 'Parse [math] tags', + cb: Fourchan.math }); - } - return Main.ready(function() { - return $.global(function() { - var j, len, node, ref1; - window.clickable_ids = false; - ref1 = document.querySelectorAll('.posteruid, .capcode'); - for (j = 0, len = ref1.length; j < len; j++) { - node = ref1[j]; - node.removeEventListener('click', window.idClick, false); + g.posts.forEach(function(post) { + if (post.callbacksExecuted) { + return Callbacks.Post.execute(post, ['Parse [math] tags'], true); } }); + return ExpandComment.callbacks.push(Fourchan.math); + } + }, + initReady: function() { + return $.global(function() { + var j, len, node, ref; + window.clickable_ids = false; + ref = document.querySelectorAll('.posteruid, .capcode'); + for (j = 0, len = ref.length; j < len; j++) { + node = ref[j]; + node.removeEventListener('click', window.idClick, false); + } }); }, code: function() { @@ -15113,9 +18967,8 @@ IDColor = (function() { if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Color User IDs'])) { return; } - this.ids = { - Heaven: [0, 0, 0, '#fff'] - }; + this.ids = $.dict(); + this.ids['Heaven'] = [0, 0, 0, '#fff']; return Callbacks.Post.push({ name: 'Color User IDs', cb: this.node @@ -15123,7 +18976,7 @@ IDColor = (function() { }, node: function() { var rgb, span, style, uid; - if (this.isClone || !((uid = this.info.uniqueID) && (span = $('span.hand', this.nodes.uniqueID)))) { + if (this.isClone || !((uid = this.info.uniqueID) && (span = this.nodes.uniqueID))) { return; } rgb = IDColor.ids[uid] || IDColor.compute(uid); @@ -15134,19 +18987,10 @@ IDColor = (function() { }, compute: function(uid) { var hash, rgb; - hash = IDColor.hash(uid); - rgb = [(hash >> 24) & 0xFF, (hash >> 16) & 0xFF, (hash >> 8) & 0xFF]; - rgb.push((rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114) > 125 ? '#000' : '#fff'); + hash = g.SITE.uidColor ? g.SITE.uidColor(uid) : parseInt(uid, 16); + rgb = [(hash >> 16) & 0xFF, (hash >> 8) & 0xFF, hash & 0xFF]; + rgb.push($.luma(rgb) > 125 ? '#000' : '#fff'); return this.ids[uid] = rgb; - }, - hash: function(uid) { - var i, msg; - msg = 0; - i = 0; - while (i < 8) { - msg = (msg << 5) - msg + uid.charCodeAt(i++); - } - return msg; } }; @@ -15170,8 +19014,8 @@ IDHighlight = (function() { }, uniqueID: null, node: function() { - if (this.nodes.uniqueID) { - $.on(this.nodes.uniqueID, 'click', IDHighlight.click(this)); + if (this.nodes.uniqueIDRoot) { + $.on(this.nodes.uniqueIDRoot, 'click', IDHighlight.click(this)); } if (this.nodes.capcode) { $.on(this.nodes.capcode, 'click', IDHighlight.click(this)); @@ -15199,6 +19043,47 @@ IDHighlight = (function() { }).call(this); +IDPostCount = (function() { + var IDPostCount; + + IDPostCount = { + init: function() { + if (!(g.VIEW === 'thread' && Conf['Count Posts by ID'])) { + return; + } + Callbacks.Thread.push({ + name: 'Count Posts by ID', + cb: function() { + return IDPostCount.thread = this; + } + }); + return Callbacks.Post.push({ + name: 'Count Posts by ID', + cb: this.node + }); + }, + node: function() { + if (this.nodes.uniqueID && this.thread === IDPostCount.thread) { + return $.on(this.nodes.uniqueID, 'mouseover', IDPostCount.count); + } + }, + count: function() { + var n, uniqueID; + uniqueID = Get.postFromNode(this).info.uniqueID; + n = 0; + IDPostCount.thread.posts.forEach(function(post) { + if (post.info.uniqueID === uniqueID) { + return n++; + } + }); + return this.title = n + " post" + (n === 1 ? '' : 's') + " by this ID"; + } + }; + + return IDPostCount; + +}).call(this); + Keybinds = (function() { var Keybinds; @@ -15227,7 +19112,7 @@ Keybinds = (function() { return Conf[hotkey] = key; }, keydown: function(e) { - var form, i, key, len, notification, notifications, op, ref, ref1, ref2, ref3, ref4, ref5, searchInput, target, thread, threadRoot; + var base, base1, catalog, i, key, len, notification, notifications, post, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, searchInput, target, thread, threadRoot; if (!(key = Keybinds.keyCode(e))) { return; } @@ -15237,11 +19122,9 @@ Keybinds = (function() { return; } } - if (!(((ref1 = g.VIEW) !== 'index' && ref1 !== 'thread') || g.VIEW === 'index' && Conf['JSON Index'] && Conf['Index Mode'] === 'catalog' || g.VIEW === 'index' && g.BOARD.ID === 'f')) { + if ((ref1 = g.VIEW) === 'index' || ref1 === 'thread') { threadRoot = Nav.getThread(); - if (op = $('.op', threadRoot)) { - thread = Get.postFromNode(op).thread; - } + thread = Get.threadFromRoot(threadRoot); } switch (key) { case Conf['Toggle board list']: @@ -15324,6 +19207,24 @@ Keybinds = (function() { } Keybinds.sage(); break; + case Conf['Toggle Cooldown']: + if (!(QR.nodes && !QR.nodes.el.hidden && $.hasClass(QR.nodes.fileSubmit, 'custom-cooldown'))) { + return; + } + QR.toggleCustomCooldown(); + break; + case Conf['Post from URL']: + if (!QR.postingIsEnabled) { + return; + } + QR.handleUrl(''); + break; + case Conf['Add new post']: + if (!QR.postingIsEnabled) { + return; + } + QR.addPost(); + break; case Conf['Submit QR']: if (!(QR.nodes && !QR.nodes.el.hidden)) { return; @@ -15335,13 +19236,13 @@ Keybinds = (function() { case Conf['Update']: switch (g.VIEW) { case 'thread': - if (!Conf['Thread Updater']) { + if (!ThreadUpdater.enabled) { return; } ThreadUpdater.update(); break; case 'index': - if (!(Conf['JSON Index'] && g.BOARD.ID !== 'f')) { + if (!Index.enabled) { return; } Index.update(); @@ -15362,17 +19263,38 @@ Keybinds = (function() { } ThreadWatcher.buttonFetchAll(); break; + case Conf['Toggle thread watcher']: + if (!ThreadWatcher.enabled) { + return; + } + ThreadWatcher.toggleWatcher(); + break; + case Conf['Toggle threading']: + if (!QuoteThreading.ready) { + return; + } + QuoteThreading.toggleThreading(); + break; + case Conf['Mark thread read']: + if (!(g.VIEW === 'index' && thread && UnreadIndex.enabled)) { + return; + } + UnreadIndex.markRead.call(threadRoot); + break; case Conf['Expand image']: if (!(ImageExpand.enabled && threadRoot)) { return; } - Keybinds.img(threadRoot); + post = Get.postFromNode(Keybinds.post(threadRoot)); + if (post.file) { + ImageExpand.toggle(post); + } break; case Conf['Expand images']: - if (!(ImageExpand.enabled && threadRoot)) { + if (!ImageExpand.enabled) { return; } - Keybinds.img(threadRoot, true); + ImageExpand.cb.toggleAll(); break; case Conf['Open Gallery']: if (!Gallery.enabled) { @@ -15381,91 +19303,94 @@ Keybinds = (function() { Gallery.cb.toggle(); break; case Conf['fappeTyme']: - if (!(Conf['Fappe Tyme'] && ((ref2 = g.VIEW) === 'index' || ref2 === 'thread'))) { + if (!((ref2 = FappeTyme.nodes) != null ? ref2.fappe : void 0)) { return; } FappeTyme.toggle('fappe'); break; case Conf['werkTyme']: - if (!(Conf['Werk Tyme'] && ((ref3 = g.VIEW) === 'index' || ref3 === 'thread'))) { + if (!((ref3 = FappeTyme.nodes) != null ? ref3.werk : void 0)) { return; } FappeTyme.toggle('werk'); break; case Conf['Front page']: - if (Conf['JSON Index'] && g.VIEW === 'index' && g.BOARD.ID !== 'f') { + if (Index.enabled) { Index.userPageNav(1); } else { - window.location = "/" + g.BOARD + "/"; + location.href = "/" + g.BOARD + "/"; } break; case Conf['Open front page']: - $.open("/" + g.BOARD + "/"); + $.open(location.origin + "/" + g.BOARD + "/"); break; case Conf['Next page']: - if (!(g.VIEW === 'index' && g.BOARD.ID !== 'f')) { + if (!(g.VIEW === 'index' && !(typeof (base = g.SITE).isOnePage === "function" ? base.isOnePage(g.BOARD) : void 0))) { return; } - if (Conf['JSON Index']) { + if (Index.enabled) { if ((ref4 = Conf['Index Mode']) !== 'paged' && ref4 !== 'infinite') { return; } $('.next button', Index.pagelist).click(); } else { - if (form = $('.next form')) { - window.location = form.action; + if ((ref5 = $(g.SITE.selectors.nav.next)) != null) { + ref5.click(); } } break; case Conf['Previous page']: - if (!(g.VIEW === 'index' && g.BOARD.ID !== 'f')) { + if (!(g.VIEW === 'index' && !(typeof (base1 = g.SITE).isOnePage === "function" ? base1.isOnePage(g.BOARD) : void 0))) { return; } - if (Conf['JSON Index']) { - if ((ref5 = Conf['Index Mode']) !== 'paged' && ref5 !== 'infinite') { + if (Index.enabled) { + if ((ref6 = Conf['Index Mode']) !== 'paged' && ref6 !== 'infinite') { return; } $('.prev button', Index.pagelist).click(); } else { - if (form = $('.prev form')) { - window.location = form.action; + if ((ref7 = $(g.SITE.selectors.nav.prev)) != null) { + ref7.click(); } } break; case Conf['Search form']: - if (!(g.VIEW === 'index' && g.BOARD.ID !== 'f')) { + if (g.VIEW !== 'index') { + return; + } + searchInput = Index.enabled ? Index.searchInput : g.SITE.selectors.searchBox ? $(g.SITE.selectors.searchBox) : void 0; + if (!searchInput) { return; } - searchInput = Conf['JSON Index'] ? Index.searchInput : $.id('search-box'); Header.scrollToIfNeeded(searchInput); searchInput.focus(); break; case Conf['Paged mode']: - if (!(Conf['JSON Index'] && g.BOARD.ID !== 'f')) { + if (!Index.enabledOn(g.BOARD)) { return; } - window.location = g.VIEW === 'index' ? '#paged' : "/" + g.BOARD + "/#paged"; + location.href = g.VIEW === 'index' ? '#paged' : "/" + g.BOARD + "/#paged"; break; case Conf['Infinite scrolling mode']: - if (!(Conf['JSON Index'] && g.BOARD.ID !== 'f')) { + if (!Index.enabledOn(g.BOARD)) { return; } - window.location = g.VIEW === 'index' ? '#infinite' : "/" + g.BOARD + "/#infinite"; + location.href = g.VIEW === 'index' ? '#infinite' : "/" + g.BOARD + "/#infinite"; break; case Conf['All pages mode']: - if (!(Conf['JSON Index'] && g.BOARD.ID !== 'f')) { + if (!Index.enabledOn(g.BOARD)) { return; } - window.location = g.VIEW === 'index' ? '#all-pages' : "/" + g.BOARD + "/#all-pages"; + location.href = g.VIEW === 'index' ? '#all-pages' : "/" + g.BOARD + "/#all-pages"; break; case Conf['Open catalog']: - if (g.BOARD.ID === 'f') { + if (!(catalog = CatalogLinks.catalog())) { return; } - window.location = CatalogLinks.catalog(); + location.href = catalog; break; case Conf['Cycle sort type']: - if (!(Conf['JSON Index'] && g.VIEW === 'index' && g.BOARD.ID !== 'f')) { + if (!Index.enabled) { return; } Index.cycleSortType(); @@ -15487,6 +19412,7 @@ Keybinds = (function() { return; } ExpandThread.toggle(thread); + Header.scrollTo(threadRoot); break; case Conf['Open thread']: if (!(g.VIEW === 'index' && threadRoot)) { @@ -15525,6 +19451,14 @@ Keybinds = (function() { Header.scrollTo(threadRoot); ThreadHiding.toggle(thread); break; + case Conf['Quick Filter MD5']: + if (!threadRoot) { + return; + } + post = Keybinds.post(threadRoot); + Keybinds.hl(+1, threadRoot); + Filter.quickFilterMD5.call(post, e); + break; case Conf['Previous Post Quoting You']: if (!(threadRoot && QuoteYou.db)) { return; @@ -15598,31 +19532,40 @@ Keybinds = (function() { } return key; }, + post: function(thread) { + var s; + s = g.SITE.selectors; + return $("" + s.postContainer + s.highlightable.reply + "." + g.SITE.classes.highlight, thread) || $("" + (g.SITE.isOPContainerThread ? s.thread : s.postContainer) + s.highlightable.op, thread); + }, qr: function(thread) { QR.open(); if (thread != null) { - QR.quote.call($('input', $('.post.highlight', thread) || thread)); + QR.quote.call(Keybinds.post(thread)); } return QR.nodes.com.focus(); }, tags: function(tag, ta) { - var range, selEnd, selStart, supported, value; - supported = (function() { - switch (tag) { - case 'spoiler': - return !!$('.postForm input[name=spoiler]'); - case 'code': - return g.BOARD.ID === 'g'; - case 'math': - case 'eqn': - return g.BOARD.ID === 'sci'; - case 'sjis': - return g.BOARD.ID === 'jp'; + var range, selEnd, selStart, value; + BoardConfig.ready(function() { + var config, supported; + config = g.BOARD.config; + supported = (function() { + switch (tag) { + case 'spoiler': + return !!config.spoilers; + case 'code': + return !!config.code_tags; + case 'math': + case 'eqn': + return !!config.math_tags; + case 'sjis': + return !!config.sjis_tags; + } + })(); + if (!supported) { + return new Notice('warning', "[" + tag + "] tags are not supported on /" + g.BOARD + "/.", 20); } - })(); - if (!supported) { - new Notice('warning', "[" + tag + "] tags are not supported on /" + g.BOARD + "/.", 20); - } + }); value = ta.value; selStart = ta.selectionStart; selEnd = ta.selectionEnd; @@ -15636,21 +19579,12 @@ Keybinds = (function() { isSage = /sage/i.test(QR.nodes.email.value); return QR.nodes.email.value = isSage ? "" : "sage"; }, - img: function(thread, all) { - var post; - if (all) { - return ImageExpand.cb.toggleAll(); - } else { - post = Get.postFromNode($('.post.highlight', thread) || $('.op', thread)); - return ImageExpand.toggle(post); - } - }, open: function(thread, tab) { var url; if (g.VIEW !== 'index') { return; } - url = "/" + thread.board + "/thread/" + thread; + url = Get.url('thread', thread); if (tab) { return $.open(url); } else { @@ -15658,43 +19592,45 @@ Keybinds = (function() { } }, hl: function(delta, thread) { - var axis, height, i, len, next, postEl, replies, reply, root; - postEl = $('.reply.highlight', thread); + var axis, height, highlight, i, len, next, postEl, replies, reply, replySelector, root; + replySelector = "" + g.SITE.selectors.postContainer + g.SITE.selectors.highlightable.reply; + highlight = g.SITE.classes.highlight; + postEl = $(replySelector + "." + highlight, thread); if (!delta) { if (postEl) { - $.rmClass(postEl, 'highlight'); + $.rmClass(postEl, highlight); } return; } if (postEl) { height = postEl.getBoundingClientRect().height; if (Header.getTopOf(postEl) >= -height && Header.getBottomOf(postEl) >= -height) { - root = postEl.parentNode; + root = Get.postFromNode(postEl).nodes.root; axis = delta === +1 ? 'following' : 'preceding'; - if (!(next = $.x(axis + "-sibling::div[contains(@class,'replyContainer') and not(@hidden) and not(child::div[@class='stub'])][1]/child::div[contains(@class,'reply')]", root))) { + if (!(next = $.x(axis + "-sibling::" + g.SITE.xpath.replyContainer + "[not(@hidden) and not(child::div[@class='stub'])][1]", root))) { return; } + if (!next.matches(replySelector)) { + next = $(replySelector, next); + } Header.scrollToIfNeeded(next, delta === +1); - this.focus(next); - $.rmClass(postEl, 'highlight'); + $.addClass(next, highlight); + $.rmClass(postEl, highlight); return; } - $.rmClass(postEl, 'highlight'); + $.rmClass(postEl, highlight); } - replies = $$('.reply', thread); + replies = $$(replySelector, thread); if (delta === -1) { replies.reverse(); } for (i = 0, len = replies.length; i < len; i++) { reply = replies[i]; if (delta === +1 && Header.getTopOf(reply) > 0 || delta === -1 && Header.getBottomOf(reply) > 0) { - this.focus(reply); + $.addClass(reply, highlight); return; } } - }, - focus: function(post) { - return $.addClass(post, 'highlight'); } }; @@ -15702,6 +19638,64 @@ Keybinds = (function() { }).call(this); +ModContact = (function() { + var ModContact; + + ModContact = { + init: function() { + var ref; + if (!(g.SITE.software === 'yotsuba' && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { + return; + } + return Callbacks.Post.push({ + name: 'Mod Contact Links', + cb: this.node + }); + }, + node: function() { + var links, moveNote, moved; + if (this.isClone || !$.hasOwn(ModContact.specific, this.info.capcode)) { + return; + } + links = $.el('span', { + className: 'contact-links brackets-wrap' + }); + $.extend(links, ModContact.template(this.info.capcode)); + $.after(this.nodes.capcode, links); + if ((moved = this.info.comment.match(/This thread was moved to >>>\/(\w+)\//)) && $.hasOwn(ModContact.moveNote, moved[1])) { + moveNote = $.el('div', { + className: 'move-note' + }); + $.extend(moveNote, ModContact.moveNote[moved[1]]); + return $.add(this.nodes.post, moveNote); + } + }, + template: function(capcode) { + return {innerHTML: "feedback" + (ModContact.specific[capcode]()).innerHTML}; + }, + specific: { + Mod: function() { + return {innerHTML: " IRC"}; + }, + Manager: function() { + return ModContact.specific.Mod(); + }, + Developer: function() { + return {innerHTML: " github"}; + }, + Admin: function() { + return {innerHTML: " twitter"}; + } + }, + moveNote: { + qa: {innerHTML: "Moving a thread to /qa/ does not imply mods will read it. If you wish to contact mods, use feedback (https://www.4chan.org/feedback) or IRC (https://www.4chan-x.net/4chan-irc.html)."} + } + }; + + return ModContact; + +}).call(this); + Nav = (function() { var Nav; @@ -15758,7 +19752,13 @@ Nav = (function() { }, getThread: function() { var i, len, ref, thread, threadRoot; - ref = $$('.thread'); + if (g.VIEW === 'thread') { + return g.threads.get(g.BOARD + "." + g.THREADID).nodes.root; + } + if ($.hasClass(doc, 'catalog-mode')) { + return; + } + ref = $$(g.SITE.selectors.thread); for (i = 0, len = ref.length; i < len; i++) { threadRoot = ref[i]; thread = Get.threadFromRoot(threadRoot); @@ -15769,7 +19769,6 @@ Nav = (function() { return threadRoot; } } - return $('.board'); }, scroll: function(delta) { var axis, extra, next, ref, thread, top; @@ -15777,8 +19776,11 @@ Nav = (function() { ref.blur(); } thread = Nav.getThread(); + if (!thread) { + return; + } axis = delta === +1 ? 'following' : 'preceding'; - if (next = $.x(axis + "-sibling::div[contains(@class,'thread') and not(@hidden)][1]", thread)) { + if (next = $.x(axis + "-sibling::" + g.SITE.xpath.thread + "[not(@hidden)][1]", thread)) { top = Header.getTopOf(thread); if (delta === +1 && top < 5 || delta === -1 && top > -5) { thread = next; @@ -15800,7 +19802,7 @@ Nav = (function() { if (extra > 0) { return d.body.style.marginBottom = extra + "px"; } else { - d.body.style.marginBottom = null; + d.body.style.marginBottom = ''; delete Nav.haveExtra; return $.off(d, 'scroll', Nav.removeExtra); } @@ -15821,13 +19823,15 @@ NormalizeURL = (function() { return; } pathname = location.pathname.split(/\/+/); - switch (g.VIEW) { - case 'thread': - pathname[2] = 'thread'; - pathname = pathname.slice(0, 4); - break; - case 'index': - pathname = pathname.slice(0, 3); + if (g.SITE.software === 'yotsuba') { + switch (g.VIEW) { + case 'thread': + pathname[2] = 'thread'; + pathname = pathname.slice(0, 4); + break; + case 'index': + pathname = pathname.slice(0, 3); + } } pathname = pathname.join('/'); if (location.pathname !== pathname) { @@ -15840,26 +19844,66 @@ NormalizeURL = (function() { }).call(this); +PSA = (function() { + var PSA, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + PSA = { + init: function() { + var announcement, el; + if (g.SITE.software === 'yotsuba' && g.BOARD.ID === 'qa') { + announcement = { + innerHTML: "Stay in touch with your /qa/ friends!" + }; + el = $.el('div', { + className: 'fcx-announcement' + }, announcement); + $.onExists(doc, '.boardBanner', function(banner) { + return $.after(banner, el); + }); + } + if ('samachan.org' in Conf['siteProperties'] && indexOf.call(Conf['PSAseen'], 'samachan') < 0) { + el = $.el('span', { + innerHTML: "Looking for a new home?
              Some former Samachan users are regrouping on SushiChan.

              (a message from 4chan X)" + }); + return Main.ready(function() { + new Notice('info', el); + Conf['PSAseen'].push('samachan'); + return $.set('PSAseen', Conf['PSAseen']); + }); + } + } + }; + + return PSA; + +}).call(this); + PSAHiding = (function() { - var PSAHiding; + var PSAHiding, + slice = [].slice; PSAHiding = { init: function() { - if (!Conf['Announcement Hiding']) { + if (!(Conf['Announcement Hiding'] && g.SITE.selectors.psa)) { return; } $.addClass(doc, 'hide-announcement'); - return $.one(d, '4chanXInitFinished', this.setup); + $.onExists(doc, g.SITE.selectors.psa, this.setup); + return $.ready(function() { + if (!$(g.SITE.selectors.psa)) { + return $.rmClass(doc, 'hide-announcement'); + } + }); }, - setup: function() { - var btn, entry, hr, psa, ref; - if (!(psa = PSAHiding.psa = $.id('globalMessage'))) { - $.rmClass(doc, 'hide-announcement'); - return; - } - if ((hr = (ref = $.id('globalToggle')) != null ? ref.previousElementSibling : void 0) && hr.nodeName === 'HR') { + setup: function(psa) { + var btn, entry, hr, ref, ref1, ref2; + PSAHiding.psa = psa; + PSAHiding.text = (ref = psa.dataset.utc) != null ? ref : psa.innerHTML; + if (g.SITE.selectors.psaTop && (hr = (ref1 = $(g.SITE.selectors.psaTop)) != null ? ref1.previousElementSibling : void 0) && hr.nodeName === 'HR') { PSAHiding.hr = hr; } + PSAHiding.content = $.el('div'); entry = { el: $.el('a', { textContent: 'Show announcement', @@ -15868,55 +19912,193 @@ PSAHiding = (function() { }), order: 50, open: function() { - return PSAHiding.hidden; + return psa.hidden; } }; Header.menu.addEntry(entry); $.on(entry.el, 'click', PSAHiding.toggle); - PSAHiding.btn = btn = $.el('span', { + PSAHiding.btn = btn = $.el('a', { title: 'Mark announcement as read and hide.', - className: 'hide-announcement' - }); - $.extend(btn, { - innerHTML: "[Dismiss]" + className: 'hide-announcement-button fa fa-minus-square', + href: 'javascript:;' }); $.on(btn, 'click', PSAHiding.toggle); - $.get('hiddenPSA', 0, function(arg) { - var hiddenPSA; - hiddenPSA = arg.hiddenPSA; - PSAHiding.sync(hiddenPSA); - $.add(psa, btn); - return $.rmClass(doc, 'hide-announcement'); - }); - return $.sync('hiddenPSA', PSAHiding.sync); + if (((ref2 = psa.firstChild) != null ? ref2.tagName : void 0) === 'HR') { + $.after(psa.firstChild, btn); + } else { + $.prepend(psa, btn); + } + PSAHiding.sync(Conf['hiddenPSAList']); + $.rmClass(doc, 'hide-announcement'); + return $.sync('hiddenPSAList', PSAHiding.sync); }, toggle: function() { - var UTC; - if ($.hasClass(this, 'hide-announcement')) { - UTC = +$.id('globalMessage').dataset.utc; - $.set('hiddenPSA', UTC); + var hide, set; + hide = $.hasClass(this, 'hide-announcement-button'); + set = function(hiddenPSAList) { + if (hide) { + return hiddenPSAList[g.SITE.ID] = PSAHiding.text; + } else { + return delete hiddenPSAList[g.SITE.ID]; + } + }; + set(Conf['hiddenPSAList']); + PSAHiding.sync(Conf['hiddenPSAList']); + return $.get('hiddenPSAList', Conf['hiddenPSAList'], function(arg) { + var hiddenPSAList; + hiddenPSAList = arg.hiddenPSAList; + set(hiddenPSAList); + return $.set('hiddenPSAList', hiddenPSAList); + }); + }, + sync: function(hiddenPSAList) { + var content, psa, ref; + psa = PSAHiding.psa, content = PSAHiding.content; + psa.hidden = hiddenPSAList[g.SITE.ID] === PSAHiding.text; + if (psa.hidden) { + $.add(content, slice.call(psa.childNodes)); } else { - $.event('CloseMenu'); - $["delete"]('hiddenPSA'); + $.add(psa, slice.call(content.childNodes)); + } + return (ref = PSAHiding.hr) != null ? ref.hidden = psa.hidden : void 0; + } + }; + + return PSAHiding; + +}).call(this); + +PassMessage = (function() { + var PassMessage; + + PassMessage = { + init: function() { + var close, msg; + if (Conf['passMessageClosed']) { + return; + } + msg = $.el('div', { + className: 'box-outer top-box' + }, {innerHTML: "

              Trouble buying a 4chan Pass? (a message from 4chan X) ×

              Check the 4chan X wiki for alternative solutions.
              "}); + msg.style.cssText = 'padding-bottom: 0;'; + close = $('a', msg); + $.on(close, 'click', function() { + $.rm(msg); + return $.set('passMessageClosed', true); + }); + return $.ready(function() { + var hd; + if ((hd = $.id('hd'))) { + return $.after(hd, msg); + } else { + return $.prepend(d.body, msg); + } + }); + } + }; + + return PassMessage; + +}).call(this); + +PostJumper = (function() { + var PostJumper; + + PostJumper = { + init: function() { + var ref; + if (!(Conf['Unique ID and Capcode Navigation'] && ((ref = g.VIEW) === 'index' || ref === 'thread'))) { + return; } - return PSAHiding.sync(UTC); + this.buttons = this.makeButtons(); + return Callbacks.Post.push({ + name: 'Post Jumper', + cb: this.node + }); }, - sync: function(UTC) { - var psa, ref; - psa = PSAHiding.psa; - PSAHiding.hidden = PSAHiding.btn.hidden = (UTC != null) && UTC >= +psa.dataset.utc; - if (PSAHiding.hidden) { - $.rm(psa); - } else { - $.after($.id('globalToggle'), psa); + node: function() { + var buttons, i, len, ref; + if (this.isClone) { + ref = $$('.postJumper', this.nodes.info); + for (i = 0, len = ref.length; i < len; i++) { + buttons = ref[i]; + PostJumper.addListeners(buttons); + } + return; + } + if (this.nodes.uniqueIDRoot) { + PostJumper.addButtons(this, 'uniqueID'); + } + if (this.nodes.capcode) { + return PostJumper.addButtons(this, 'capcode'); + } + }, + addButtons: function(post, type) { + var buttons, value; + value = post.info[type]; + buttons = PostJumper.buttons.cloneNode(true); + $.extend(buttons.dataset, { + type: type, + value: value + }); + $.after(post.nodes[type + (type === 'capcode' ? '' : 'Root')], buttons); + return PostJumper.addListeners(buttons); + }, + addListeners: function(buttons) { + $.on(buttons.firstChild, 'click', PostJumper.buttonClick); + return $.on(buttons.lastChild, 'click', PostJumper.buttonClick); + }, + buttonClick: function() { + var dir, toJumper; + dir = $.hasClass(this, 'prev') ? -1 : 1; + if ((toJumper = PostJumper.find(this.parentNode, dir))) { + return PostJumper.scroll(this.parentNode, toJumper); + } + }, + find: function(jumper, dir) { + var axis, jumper2, ref, type, value, xpath; + ref = jumper.dataset, type = ref.type, value = ref.value; + xpath = "span[contains(@class,\"postJumper\") and @data-value=\"" + value + "\" and @data-type=\"" + type + "\"]"; + axis = dir < 0 ? 'preceding' : 'following'; + jumper2 = jumper; + while ((jumper2 = $.x(axis + "::" + xpath, jumper2))) { + if (jumper2.getBoundingClientRect().height) { + return jumper2; + } } - if ((ref = PSAHiding.hr) != null) { - ref.hidden = PSAHiding.hidden; + if ((jumper2 = $.x("(//" + xpath + ")[" + (dir < 0 ? 'last()' : '1') + "]"))) { + if (jumper2.getBoundingClientRect().height) { + return jumper2; + } + } + while ((jumper2 = $.x(axis + "::" + xpath, jumper2)) && jumper2 !== jumper) { + if (jumper2.getBoundingClientRect().height) { + return jumper2; + } } + return null; + }, + makeButtons: function() { + var charNext, charPrev, classNext, classPrev, span; + charPrev = '\u23EB'; + charNext = '\u23EC'; + classPrev = 'prev'; + classNext = 'next'; + span = $.el('span', { + className: 'postJumper' + }); + $.extend(span, {innerHTML: "" + E(charPrev) + "" + E(charNext) + ""}); + return span; + }, + scroll: function(fromJumper, toJumper) { + var destPos, prevPos; + prevPos = fromJumper.getBoundingClientRect().top; + destPos = toJumper.getBoundingClientRect().top; + return window.scrollBy(0, destPos - prevPos); } }; - return PSAHiding; + return PostJumper; }).call(this); @@ -15928,9 +20110,9 @@ RelativeDates = (function() { INTERVAL: $.MINUTE / 2, init: function() { var ref; - if (((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Relative Post Dates'] && !Conf['Relative Date Title'] || g.VIEW === 'index' && Conf['JSON Index'] && g.BOARD.ID !== 'f') { + if (((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Relative Post Dates'] && !Conf['Relative Date Title'] || Index.enabled) { this.flush(); - $.on(d, 'visibilitychange ThreadUpdate', this.flush); + $.on(d, 'visibilitychange PostsInserted', this.flush); } if (Conf['Relative Post Dates']) { return Callbacks.Post.push({ @@ -15941,6 +20123,9 @@ RelativeDates = (function() { }, node: function() { var dateEl; + if (!this.info.date) { + return; + } dateEl = this.nodes.date; if (Conf['Relative Date Title']) { $.on(dateEl, 'mouseover', (function(_this) { @@ -15956,14 +20141,22 @@ RelativeDates = (function() { dateEl.title = dateEl.textContent; return RelativeDates.update(this); }, - relative: function(diff, now, date) { + relative: function(diff, now, date, abbrev) { var days, months, number, rounded, unit, years; - unit = (number = diff / $.DAY) >= 1 ? (years = now.getYear() - date.getYear(), months = now.getMonth() - date.getMonth(), days = now.getDate() - date.getDate(), years > 1 ? (number = years - (months < 0 || months === 0 && days < 0), 'year') : years === 1 && (months > 0 || months === 0 && days >= 0) ? (number = years, 'year') : (months = months + 12 * years) > 1 ? (number = months - (days < 0), 'month') : months === 1 && days >= 0 ? (number = months, 'month') : 'day') : (number = diff / $.HOUR) >= 1 ? 'hour' : (number = diff / $.MINUTE) >= 1 ? 'minute' : (number = Math.max(0, diff) / $.SECOND, 'second'); + unit = (number = diff / $.DAY) >= 1 ? (years = now.getFullYear() - date.getFullYear(), months = now.getMonth() - date.getMonth(), days = now.getDate() - date.getDate(), years > 1 ? (number = years - (months < 0 || months === 0 && days < 0), 'year') : years === 1 && (months > 0 || months === 0 && days >= 0) ? (number = years, 'year') : (months = months + 12 * years) > 1 ? (number = months - (days < 0), 'month') : months === 1 && days >= 0 ? (number = months, 'month') : 'day') : (number = diff / $.HOUR) >= 1 ? 'hour' : (number = diff / $.MINUTE) >= 1 ? 'minute' : (number = Math.max(0, diff) / $.SECOND, 'second'); rounded = Math.round(number); - if (rounded !== 1) { - unit += 's'; + if (abbrev) { + unit = unit === 'month' ? 'mo' : unit[0]; + } else { + if (rounded !== 1) { + unit += 's'; + } + } + if (abbrev) { + return "" + rounded + unit; + } else { + return rounded + " " + unit + " ago"; } - return rounded + " " + unit + " ago"; }, stale: [], flush: function() { @@ -15989,12 +20182,18 @@ RelativeDates = (function() { return post.nodes.date.title = RelativeDates.relative(diff, now, date); }, update: function(data, now) { - var date, diff, i, isPost, len, ref, relative, singlePost; + var abbrev, date, diff, i, isPost, len, ref, relative, singlePost; isPost = data instanceof Post; - date = isPost ? data.info.date : new Date(+data.dataset.utc); + if (isPost) { + date = data.info.date; + abbrev = false; + } else { + date = new Date(+data.dataset.utc); + abbrev = !!data.dataset.abbrev; + } now || (now = new Date()); diff = now - date; - relative = RelativeDates.relative(diff, now, date); + relative = RelativeDates.relative(diff, now, date, abbrev); if (isPost) { ref = [data].concat(data.clones); for (i = 0, len = ref.length; i < len; i++) { @@ -16015,7 +20214,10 @@ RelativeDates = (function() { if (indexOf.call(RelativeDates.stale, data) >= 0) { return; } - if (data instanceof Post && !g.posts[data.fullID]) { + if (data instanceof Post && !g.posts.get(data.fullID)) { + return; + } + if (data instanceof Element && !doc.contains(data)) { return; } return RelativeDates.stale.push(data); @@ -16042,10 +20244,6 @@ RemoveSpoilers = (function() { name: 'Reveal Spoilers', cb: this.node }); - Callbacks.CatalogThread.push({ - name: 'Reveal Spoilers', - cb: this.node - }); if (g.VIEW === 'archive') { return $.ready(function() { return RemoveSpoilers.unspoiler($.id('arc-list')); @@ -16057,7 +20255,7 @@ RemoveSpoilers = (function() { }, unspoiler: function(el) { var i, len, span, spoiler, spoilers; - spoilers = $$('s', el); + spoilers = $$(g.SITE.selectors.spoiler, el); for (i = 0, len = spoilers.length; i < len; i++) { spoiler = spoilers[i]; span = $.el('span', { @@ -16088,18 +20286,18 @@ Report = (function() { }, ready: function() { $.addStyle(CSS.report); - if (!Conf['Use Recaptcha v1 in Reports'] && !Conf['Force Noscript Captcha'] && Main.jsEnabled) { - return new MutationObserver(function() { - Report.fit('iframe[src^="https://www.google.com/recaptcha/api2/frame"]'); - return Report.fit('body'); - }).observe(d.body, { - childList: true, - attributes: true, - subtree: true - }); - } else { - return Report.fit('body'); + if (Conf['Archive Report']) { + Report.archive(); } + new MutationObserver(function() { + Report.fit('iframe[src^="https://www.google.com/recaptcha/api2/frame"]'); + return Report.fit('body'); + }).observe(d.body, { + childList: true, + attributes: true, + subtree: true + }); + return Report.fit('body'); }, fit: function(selector) { var dy, el; @@ -16110,6 +20308,109 @@ Report = (function() { if (dy > 0) { return window.resizeBy(0, dy); } + }, + archive: function() { + var enabled, fieldset, form, match, message, reason, submit, types, urls; + if (!(urls = Redirect.report(g.BOARD.ID)).length) { + return; + } + form = $('form'); + types = $.id('reportTypes'); + message = $('h3'); + fieldset = $.el('fieldset', { + id: 'archive-report', + hidden: true + }, {innerHTML: ""}); + enabled = $('#archive-report-enabled', fieldset); + reason = $('#archive-report-reason', fieldset); + submit = $('#archive-report-submit', fieldset); + $.on(enabled, 'change', function() { + return reason.disabled = !this.checked; + }); + if (form && types) { + fieldset.hidden = !$('[value="31"]', types).checked; + $.on(types, 'change', function(e) { + fieldset.hidden = e.target.value !== '31'; + return Report.fit('body'); + }); + $.after(types, fieldset); + Report.fit('body'); + $.one(form, 'submit', function(e) { + if (!fieldset.hidden && enabled.checked) { + e.preventDefault(); + return Report.archiveSubmit(urls, reason.value, (function(_this) { + return function(results) { + _this.action = '#archiveresults=' + encodeURIComponent(JSON.stringify(results)); + return _this.submit(); + }; + })(this)); + } + }); + } else if (message) { + fieldset.hidden = /Report submitted!/.test(message.textContent); + $.on(enabled, 'change', function() { + return submit.hidden = !this.checked; + }); + $.after(message, fieldset); + $.on(submit, 'click', function() { + return Report.archiveSubmit(urls, reason.value, Report.archiveResults); + }); + } + if ((match = location.hash.match(/^#archiveresults=(.*)$/))) { + try { + return Report.archiveResults(JSON.parse(decodeURIComponent(match[1]))); + } catch (error) {} + } + }, + archiveSubmit: function(urls, reason, cb) { + var fn, form, i, len, name, ref, results, url; + form = $.formData({ + board: g.BOARD.ID, + num: Report.postID, + reason: reason + }); + results = []; + fn = function(name, url) { + return $.ajax(url, { + onloadend: function() { + results.push([ + name, this.response || { + error: '' + } + ]); + if (results.length === urls.length) { + return cb(results); + } + }, + form: form + }); + }; + for (i = 0, len = urls.length; i < len; i++) { + ref = urls[i], name = ref[0], url = ref[1]; + fn(name, url); + } + }, + archiveResults: function(results) { + var fieldset, i, len, line, name, ref, response; + fieldset = $.id('archive-report'); + for (i = 0, len = results.length; i < len; i++) { + ref = results[i], name = ref[0], response = ref[1]; + line = $.el('h3', { + className: 'archive-report-response' + }); + if ('success' in response) { + $.addClass(line, 'archive-report-success'); + line.textContent = name + ": " + response.success; + } else { + $.addClass(line, 'archive-report-error'); + line.textContent = name + ": " + (response.error || 'Error reporting post.'); + } + if (fieldset) { + $.before(fieldset, line); + } else { + $.add(d.body, line); + } + } } }; @@ -16158,7 +20459,7 @@ Time = (function() { Time = { init: function() { var ref; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Time Formatting'])) { + if (!(((ref = g.VIEW) === 'index' || ref === 'thread' || ref === 'archive') && Conf['Time Formatting'])) { return; } return Callbacks.Post.push({ @@ -16167,14 +20468,16 @@ Time = (function() { }); }, node: function() { - if (this.isClone) { + var textContent; + if (!this.info.date || this.isClone) { return; } - return this.nodes.date.textContent = Time.format(Conf['time'], this.info.date); + textContent = this.nodes.date.textContent; + return this.nodes.date.textContent = textContent.match(/^\s*/)[0] + Time.format(Conf['time'], this.info.date) + textContent.match(/\s*$/)[0]; }, format: function(formatString, date) { return formatString.replace(/%(.)/g, function(s, c) { - if (c in Time.formatters) { + if ($.hasOwn(Time.formatters, c)) { return Time.formatters[c].call(date); } else { return s; @@ -16183,6 +20486,30 @@ Time = (function() { }, day: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], month: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + localeFormat: function(date, options, defaultValue) { + if (Conf['timeLocale']) { + try { + return Intl.DateTimeFormat(Conf['timeLocale'], options).format(date); + } catch (error) {} + } + return defaultValue; + }, + localeFormatPart: function(date, options, part, defaultValue) { + var parts; + if (Conf['timeLocale']) { + try { + parts = Intl.DateTimeFormat(Conf['timeLocale'], options).formatToParts(date); + return parts.map(function(x) { + if (x.type === part) { + return x.value; + } else { + return ''; + } + }).join(''); + } catch (error) {} + } + return defaultValue; + }, zeroPad: function(n) { if (n < 10) { return "0" + n; @@ -16192,16 +20519,24 @@ Time = (function() { }, formatters: { a: function() { - return Time.day[this.getDay()].slice(0, 3); + return Time.localeFormat(this, { + weekday: 'short' + }, Time.day[this.getDay()].slice(0, 3)); }, A: function() { - return Time.day[this.getDay()]; + return Time.localeFormat(this, { + weekday: 'long' + }, Time.day[this.getDay()]); }, b: function() { - return Time.month[this.getMonth()].slice(0, 3); + return Time.localeFormat(this, { + month: 'short' + }, Time.month[this.getMonth()].slice(0, 3)); }, B: function() { - return Time.month[this.getMonth()]; + return Time.localeFormat(this, { + month: 'long' + }, Time.month[this.getMonth()]); }, d: function() { return Time.zeroPad(this.getDate()); @@ -16228,18 +20563,13 @@ Time = (function() { return Time.zeroPad(this.getMinutes()); }, p: function() { - if (this.getHours() < 12) { - return 'AM'; - } else { - return 'PM'; - } + return Time.localeFormatPart(this, { + hour: 'numeric', + hour12: true + }, 'dayperiod', (this.getHours() < 12 ? 'AM' : 'PM')); }, P: function() { - if (this.getHours() < 12) { - return 'am'; - } else { - return 'pm'; - } + return Time.formatters.p.call(this).toLowerCase(); }, S: function() { return Time.zeroPad(this.getSeconds()); @@ -16260,6 +20590,61 @@ Time = (function() { }).call(this); +Tinyboard = (function() { + var Tinyboard; + + Tinyboard = { + init: function() { + if (g.SITE.software !== 'tinyboard') { + return; + } + if (g.VIEW === 'thread') { + return Main.ready(function() { + return $.global(function() { + var base, boardID, form, originalNoko, ref, ref1, ref2, threadID; + ref = document.currentScript.dataset, boardID = ref.boardID, threadID = ref.threadID; + threadID = +threadID; + form = document.querySelector('form[name="post"]'); + window.$(document).ajaxComplete(function(event, request, settings) { + var detail, noko, postID, redirect, ref1, ref2; + if (settings.url !== form.action) { + return; + } + if (!(postID = +((ref1 = request.responseJSON) != null ? ref1.id : void 0))) { + return; + } + detail = { + boardID: boardID, + threadID: threadID, + postID: postID + }; + try { + ref2 = request.responseJSON, redirect = ref2.redirect, noko = ref2.noko; + if (redirect && (typeof originalNoko !== "undefined" && originalNoko !== null) && !originalNoko && !noko) { + detail.redirect = redirect; + } + } catch (error) {} + event = new CustomEvent('QRPostSuccessful', { + bubbles: true, + detail: detail + }); + return document.dispatchEvent(event); + }); + originalNoko = (ref1 = window.tb_settings) != null ? (ref2 = ref1.ajax) != null ? ref2.always_noko_replies : void 0 : void 0; + return ((base = (window.tb_settings || (window.tb_settings = {}))).ajax || (base.ajax = {})).always_noko_replies = true; + }, { + boardID: g.BOARD.ID, + threadID: g.THREADID + }); + }); + } + } + }; + + return Tinyboard; + +}).call(this); + Favicon = (function() { var Favicon; @@ -16269,24 +20654,35 @@ Favicon = (function() { return d.head && (Favicon.el = $('link[rel="shortcut icon"]', d.head)); }), Favicon.initAsap); }, + set: function(status) { + Favicon.status = status; + if (Favicon.el) { + Favicon.el.href = Favicon[status]; + return $.add(d.head, Favicon.el); + } + }, initAsap: function() { var href; Favicon.el.type = 'image/x-icon'; href = Favicon.el.href; - Favicon.SFW = /ws\.ico$/.test(href); + Favicon.isSFW = /ws\.ico$/.test(href); Favicon["default"] = href; - return Favicon["switch"](); + Favicon["switch"](); + if (Favicon.status) { + return Favicon.set(Favicon.status); + } }, "switch": function() { var f, i, items, t; items = { ferongr: ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///9zBQC/AADpDAP/gID/q6voCwJJTwpOAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxUlEQVR42q1TOwrCQBB9s0FRtJI0WoqFtSLYegoP4gVSeJsUHsHSI3iFeIqRXXgwrhlXwYHHhLwPTB7B36abBCV+0pA4DUBQUNZYQptGtW3jtoKyxgoe0yrBCoyZfL/5ioQ3URZOXW9I341l3oo+NXEZiW4CEuIzvPECopED4OaZ3RNmeAm4u+a8Jr5f17VyVoL8fr8qcltzwlyyj2iqcgPOQ9ExkHAITgD75bYBe0A5S4H/P9htuWMF3QXoQpwaKeT+lnsC6JE5I6aq6fEAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///8AcH4AtswA2PJ55fKi6fIA1/FtpPADAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxElEQVQ4y2NgoBq4/vE/HJOsBiRQUIfA2AzBqQYqUfn00/9FLz+BaQxDCKqBmX7jExijKEDSDJPHrnnbGQhGV4RmOFwdVkNwhQMheYwQxhaIi7b9Z9A3gWAQm2BUoQOgRhgA8o7j1ozLC4LCyAZcx6kZI5qg4kLKqggDFFWxJySsUQVzlb4pwgAJaTRvokcVNgOqOv8zcHBCsL07DgNg8YsczzA5MxtUL+DMD8g0slxI/H8GQ/P/DJKyeKIRpglXZsIiBwBhP5O+VbI/JgAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAFVBMVEX///8oeQBJ3ABV/wHM/7Lu/+ZU/gAqUP3dAAAAAXRSTlMAQObYZgAAAGJJREFUeF5Fi7ENg0AQBCfa/AFdDh2gdwPIogMK2E2+/xLslwOvdqRJhv+GQQPUCtJM7svankLrq/I+TY5e6Ueh1jyBMX7AFJi9vwfyVO4CbbO6jNYpp9GyVPbdkFhVgAQ2H0NOE5jk9DT8AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAx0lEQVQ4y2NgoBYI+cfwH4ZJVgMS0KhEYGyG4FQDkzjzf9P/d/+fgWl0QwiqgSkI/c8IxsgKkDXD5LFq9rwDweiK0A2HqcNqCK5wICSPEcLYAtH+AMN/IXMIBrEJRie6OEgjDAC5x3FqxuUFNiEUA67j1IweTTBxBQ1puAG86jgSEraogskJWSBcwCGF5k30qMJmgMFEhv/MXBAs5oLDAFj8IsczTE7UEeECbhU8+QGZRpaTi2b4L2zF8J9TGk80wjThykzY5AAW/2O1C2mIbgAAAABJRU5ErkJggg=='], - 'xat-': ['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'], + 'xat-': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAG1BMVEX+AACLkZFub2yfaF3zZGIAAAD/AAD/iYr/zs8IPcF6AAAABXRSTlMAeprJ7xzg6IEAAABZSURBVAjXY2DABKGBSkqioQwMrGmpxsZhaQEMDGFpIa5pqSCRtPDSNJBIaGh5eShQDYOye0V7iREKAyQFYoiCFAcyILQDGcGmEEZYkGoqiMHKysAQEICwGwAAjBmBqhYlagAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAPFBMVEUAAACEgoBva2ilamDxcG7IaWYgFBNOSEf//f0PDQwBAAA7LCwAAAD/AAD+hIX+m5z+zc5HAADPAAAGAADl032uAAAADHRSTlMAzNv0/vz+6v3+7ALrmfyXAAAAaUlEQVQY042PyxKAIAhFAc1eV7T6/3/N8VXOtAgWwBm4ANEPA8AswpySXHvvYZLlpBNrh9pDtcSqAQ1BUTVIjNUQY5icmwfglmXNgE0d6QBF9GigrU0A9LoM53U1kFzk6SBQuWfD/vHqDUCpBmVKTTM4AAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAIVBMVEUAAACRjop4dXVpZ2tdcI9dfKdisfMAAAAumMN9xv+s2/+PADT2AAAAB3RSTlMAepGdv83v3HIc4QAAAFxJREFUCNdjYMAE5YXKRuLlDAzsHe2uIRUdBQwMFR1l6R3tIJGOyukdIJHy8lkry4FqGEwzV62aFozMUAFJOQEZ4iDFhQwI7UBGaTiEUVFs3g5isLMzMBQUIOwGAJRlIu9hk08QAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEUAAACAgYVlc4ljsu4AAAAAAAAAAAAumMODyP6b1P6e1f/g8v89msgSIiwNFxwbPU3tQYj5AAAABnRSTlMAxej+9VTmD9ciAAAAZElEQVQI12NgwARpiUKKYmkMDGzlZUpK6eUJDAzp5clm5WUgkfKMtnKQSFpa54o0oBoGJYvZO88+gjJu7wMyhIBS2SCGGFDxaxADpP32NjAjSe0bSFd6epIaWISNjYEhJRVhNwAGlyJpYtcvcAAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAHlBMVEUfJSCRi5Frbm9dn19082KR/30AAABmzDOq/5vZ/9Gt/vt2AAAABnRSTlMAe5rJ7/4vxEp4AAAAWUlEQVQI12NgwARpiUpKYmkMDGzlZcbG6eUJDAzp5Slu5WUgkfLUsHKQSFpaRGsaUA2DsmvnjBAjFAZICsQQAylOZEBoBzKSzSCM9CS1MhCDjY2BISEBYTcAtgAcKSK2vuIAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAM1BMVEUAAACBj39tfm1qj2RepFlu2VQAAQAAAAAAAABmyzOX/oSr/pus/pzk/98PGgtatC4CBAI1ENblAAAACHRSTlMA09/p9v77ig0SBcQAAABnSURBVBjTjY9LDsAgCEQRsR2xWu9/2hK/adJFYQG8wABEPwyAYzNnSatjjPAiviWLhPCqI1R7HBrQdCmGBrEETTmnUAq/QMm5dODHyAQOXXR1zLUGsIEI7lonMGfeHQTq9xw4P159AIxSBSC53km7AAAAAElFTkSuQmCC'], Mayhem: ['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'], - '4chanJS': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAD/AABnZ2f///8nFk05AAAAAXRSTlMAQObYZgAAAEFJREFUeNqNjgEKACAMAjvX/98cAkkxgmSgO8Bt/Ai4ApJ6KKhzF3OiEMDASrGB/QWgPEHsUpN+Ng9xAETMYhDrWmeHAMcmvycWAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAD/AAD///9nZ2f77Y6hAAAAAXRSTlMAQObYZgAAAEBJREFUeF6NjQEKACAMAnfW/98cAxFiBIngOsTqR8B1IGkeG9p5i7XabgAGZNigXgA8aoCUxvzWAIcBItGiSEwdccYA3BuRAWkAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAAul8NnZ2f////82iC9AAAAAXRSTlMAQObYZgAAAEFJREFUeNqNjgEKACAMAjvX/98cAkkxgmSgO8Bt/Ai4ApJ6KKhzF3OiEMDASrGB/QWgPEHsUpN+Ng9xAETMYhDrWmeHAMcmvycWAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAAul8P///9nZ2cgIeMlAAAAAXRSTlMAQObYZgAAAEBJREFUeF6NjQEKACAMAnfW/98cAxFiBIngOsTqR8B1IGkeG9p5i7XabgAGZNigXgA8aoCUxvzWAIcBItGiSEwdccYA3BuRAWkAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAElBMVEUBAAAAAABmzDNlyjJnZ2f///+6o7dfAAAAAXRSTlMAQObYZgAAAERJREFUeF6NjkEKADEIA51o///lJZfQxUsHITogWi8AvwZJuxmYa25xDooBLEwOWFTYAsYVhdorLZt9Ng9xCUTCUCQ2H3F4ANrZ2WNiAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAElBMVEUBAAAAAABmzDP///9lyjJnZ2cIHys9AAAAAXRSTlMAQObYZgAAAENJREFUeF6NjUEKwEAMAjNm9/9fLkEslFwqgjoEUn8EfAqSdrkwzj6ieyyTkQEVGWRvANfO1iEX620AjgBEwqR4Y+sBeGAA6d+vQ4IAAAAASUVORK5CYII='], + '4chanJS': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAD/AABnZ2f///8nFk05AAAAAXRSTlMAQObYZgAAAEFJREFUeNqNjgEKACAMAjvX/98cAkkxgmSgO8Bt/Ai4ApJ6KKhzF3OiEMDASrGB/QWgPEHsUpN+Ng9xAETMYhDrWmeHAMcmvycWAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAD1BMVEUAAAAAAAD/AABmZmYA/wBD99DBAAAAAXRSTlMAQObYZgAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAE9JREFUCNdljcsRACEIQ5MOiNKAdGAJ9N/Uiu7nsMzABHgB4B8ygFoZA2hhVWavhhGeURPJU9q45+17hGbfGxa82Ndex3hEM44SJGD2/b4AzDgGlHbl388AAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAD1BMVEUBAAAAAAAul8NnZ2f////82iC9AAAAAXRSTlMAQObYZgAAAEFJREFUeNqNjgEKACAMAjvX/98cAkkxgmSgO8Bt/Ai4ApJ6KKhzF3OiEMDASrGB/QWgPEHsUpN+Ng9xAETMYhDrWmeHAMcmvycWAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAD1BMVEUAAAAAAAAul8NnZ2f/AAD7B+mqAAAAAXRSTlMAQObYZgAAAAlwSFlzAAALEgAACxIB0t1+/AAAAE9JREFUCNdljcsRACEIQ5MOiNKAdGAJ9N/Uiu7nsMzABHgB4B8ygFoZA2hhVWavhhGeURPJU9q45+17hGbfGxa82Ndex3hEM44SJGD2/b4AzDgGlHbl388AAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAElBMVEUBAAAAAABmzDNlyjJnZ2f///+6o7dfAAAAAXRSTlMAQObYZgAAAERJREFUeF6NjkEKADEIA51o///lJZfQxUsHITogWi8AvwZJuxmYa25xDooBLEwOWFTYAsYVhdorLZt9Ng9xCUTCUCQ2H3F4ANrZ2WNiAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAD1BMVEUAAAAAAABmzDNmZmb/AAC8/wCMAAAAAXRSTlMAQObYZgAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAE9JREFUCNdljcsRACEIQ5MOiNKAdGAJ9N/Uiu7nsMzABHgB4B8ygFoZA2hhVWavhhGeURPJU9q45+17hGbfGxa82Ndex3hEM44SJGD2/b4AzDgGlHbl388AAAAASUVORK5CYII='], Original: ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEX/////AAD///8AAABBZmS3AAAAAXRSTlMAQObYZgAAAExJREFUeF4tyrENgDAMAMFXKuQswQLBG3mOlBnFS1gwDfIYLpEivvjq2MlqjmYvYg5jWEzCwtDSQlwcXKCVLrpFbvLvvSf9uZJ2HusDtJAY7Tkn1oYAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAhElEQVR42q1RwQnAMAjMu5M4guAKXa4j5dUROo5tipSDcrFChUONd0di2m/hEGVOHDyIPufgwAFASDkpoSzmBrkJ2UMyR9LsJ3rvrqo3Rt1YMIMhhNnOxLMnoMFBxHyJAr2IOBFzA8U+6pLBdmEJTA0aMVjpDd6Loks0s5HZNwYx8tfZCZ0kll7ORffZAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEX///8ul8P///8AAACaqgkzAAAAAXRSTlMAQObYZgAAAExJREFUeF4tyrENgDAMAMFXKuQswQLBG3mOlBnFS1gwDfIYLpEivvjq2MlqjmYvYg5jWEzCwtDSQlwcXKCVLrpFbvLvvSf9uZJ2HusDtJAY7Tkn1oYAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAALVBMVEUAAAAAAAAAAAAAAAABBQcHFx4KISoNLToaVW4oKCgul8M4ODg7OzvBwcH///8uS/CdAAAAA3RSTlMAx9dmesIgAAAAV0lEQVR42m2NWw6AIBAD1eILZO5/XI0UAgm7H9tOsu0yGWAQSOoFijHOxOANGqm/LczpOaXs4gISrPZ+gc2+hO5w2xdwgOjBFUIF+sEJrhUl9JFr+badFwR+BfqlmGUJAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEX///9mzDP///8AAACT0n1lAAAAAXRSTlMAQObYZgAAAExJREFUeF4tyrENgDAMAMFXKuQswQLBG3mOlBnFS1gwDfIYLpEivvjq2MlqjmYvYg5jWEzCwtDSQlwcXKCVLrpFbvLvvSf9uZJ2HusDtJAY7Tkn1oYAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAALVBMVEUAAAAAAAAAAAAAAAAECAIQIAgWLAsePA8oKCg4ODg6dB07OztmzDPBwcH///+rsf3XAAAAA3RSTlMAx9dmesIgAAAAV0lEQVR42m2NWw6AIBAD1eIDhbn/cTVSCCTsfmw7ybbLZIBBIKkXKKU0E4M3aKT+tjCn5xiziwuIsNr7BTb7ErrDZV/AAaIHdwgV6AcnuFaU0Eeu5dt2XiUyBjCQ2bIrAAAAAElFTkSuQmCC'], - 'Metro': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAC/AABrZQDiAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAAHAAAdAAApAAAsAAA4AABsAACQAAC/AAD///9SVhtjAAAAA3RSTlMAPse+s4iwAAAAM0lEQVQIW2NggAGuVasWgDBpDDAQUoSaob0Jao73lgVojOitUEazBZRRvR3KmJa5AO4KAGBtLuMAuhIIAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAAA1/GhpCidAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAAACAkAISUALzQAMTcAQEcAeokAorYA1/H///8BrzTFAAAAA3RSTlMAPse+s4iwAAAAM0lEQVQIW2NggAGuVasWgDBpDDAQUoSaob0Jao73lgVojOitUEazBZRRvR3KmJa5AO4KAGBtLuMAuhIIAAAAAElFTkSuQmCC', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAABV/wErM5hwAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAADCgANKAASOAATOwAZTAAwkQBAwQBV/wH////+Fmy4AAAAA3RSTlMAPse+s4iwAAAAM0lEQVQIW2NggAGuVasWgDBpDDAQUoSaob0Jao73lgVojOitUEazBZRRvR3KmJa5AO4KAGBtLuMAuhIIAAAAAElFTkSuQmCC'] - }[Conf['favicon']]; + 'Metro': ['iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAC/AABrZQDiAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAC/AAD///8dAAApAABsAAAHAAA4AACQAAAsAABMCpCvAAAAA3RSTlMAPse+s4iwAAAAMklEQVQI12NggAFmY2MDECaNAQZCilAzVJyg5oS4GqAxUtygjIp2KGOKJ5SxepcB3BUAcdYRqxAtgFoAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAAA1/GhpCidAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAAAA1/H///8AISUALzQAeokACAkAQEcAorYAMTcE9WFNAAAAA3RSTlMAPse+s4iwAAAAMklEQVQI12NggAFmY2MDECaNAQZCilAzVJyg5oS4GqAxUtygjIp2KGOKJ5SxepcB3BUAcdYRqxAtgFoAAAAASUVORK5CYII=', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAABV/wErM5hwAAAAAXRSTlMAQObYZgAAABJJREFUCB1jZGBgrMNAQEEc4gCSfAX5bRw/NQAAAABJRU5ErkJggg==', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAAAAAAAAABV/wH///8NKAASOAAwkQADCgAZTABAwQATOwC5e3VGAAAAA3RSTlMAPse+s4iwAAAAMklEQVQI12NggAFmY2MDECaNAQZCilAzVJyg5oS4GqAxUtygjIp2KGOKJ5SxepcB3BUAcdYRqxAtgFoAAAAASUVORK5CYII='] + }; + items = $.getOwn(items, Conf['favicon']); f = Favicon; t = 'data:image/png;base64,'; i = 0; @@ -16297,7 +20693,7 @@ Favicon = (function() { return f.update(); }, update: function() { - if (this.SFW) { + if (this.isSFW) { this.unread = this.unreadSFW; return this.unreadY = this.unreadSFWY; } else { @@ -16305,6 +20701,8 @@ Favicon = (function() { return this.unreadY = this.unreadNSFWY; } }, + SFW: '//s.4cdn.org/image/favicon-ws.ico', + NSFW: '//s.4cdn.org/image/favicon.ico', dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==', logo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAgMAAAC+UIlYAAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAGlJREFUWMPtlkEKADEIA/tJP9lXLttQto2yHxgDHozTi0ToGK2WKZZ+HAQQMZc+xBwI4EZ+wAC2IfPuSIDOZJrSZQEAX9eVJhhwIuUYAnQe8rhAEMAZlTI2MID9f5Clyh0JeE1V1ZEAvB4qDfwuJTSGRAAAAABJRU5ErkJggg==' }; @@ -16318,7 +20716,7 @@ MarkNewIPs = (function() { MarkNewIPs = { init: function() { - if (g.VIEW !== 'thread' || !Conf['Mark New IPs']) { + if (!(g.SITE.software === 'yotsuba' && g.VIEW === 'thread' && Conf['Mark New IPs'])) { return; } return Callbacks.Thread.push({ @@ -16342,13 +20740,13 @@ MarkNewIPs = (function() { i = MarkNewIPs.ipCount; for (j = 0, len = newPosts.length; j < len; j++) { fullID = newPosts[j]; - MarkNewIPs.markNew(g.posts[fullID], ++i); + MarkNewIPs.markNew(g.posts.get(fullID), ++i); } break; case -deletedPosts.length: for (k = 0, len1 = newPosts.length; k < len1; k++) { fullID = newPosts[k]; - MarkNewIPs.markOld(g.posts[fullID]); + MarkNewIPs.markOld(g.posts.get(fullID)); } } MarkNewIPs.ipCount = ipCount; @@ -16384,7 +20782,6 @@ ReplyPruning = (function() { if (!(g.VIEW === 'thread' && Conf['Reply Pruning'])) { return; } - this.active = !(Conf['Quote Threading'] && Conf['Thread Quotes']); this.container = $.frag(); this.summary = $.el('span', { hidden: true, @@ -16397,17 +20794,16 @@ ReplyPruning = (function() { return $.event('change', null, _this.inputs.enabled); }; })(this)); - label = UI.checkbox('Prune Replies', 'Show Last', this.active); + label = UI.checkbox('Prune Replies', 'Show Last', Conf['Prune All Threads']); el = $.el('span', { title: 'Maximum number of replies to show.' - }, { - innerHTML: " " - }); + }, {innerHTML: " "}); $.prepend(el, label); this.inputs = { enabled: label.firstElementChild, replies: el.lastElementChild }; + this.setEnabled.call(this.inputs.enabled); $.on(this.inputs.enabled, 'change', this.setEnabled); $.on(this.inputs.replies, 'change', $.cb.value); Header.menu.addEntry({ @@ -16442,6 +20838,12 @@ ReplyPruning = (function() { node: function() { var ref; ReplyPruning.thread = this; + if (this.isSticky) { + ReplyPruning.active = ReplyPruning.inputs.enabled.checked = true; + if (QuoteThreading.input) { + Conf['Thread Quotes'] = QuoteThreading.input.checked = false; + } + } this.posts.forEach(function(post) { if (post.isReply) { ReplyPruning.total++; @@ -16450,7 +20852,7 @@ ReplyPruning = (function() { } } }); - if (ReplyPruning.active && /^#p\d+$/.test(location.hash) && (0 <= (ref = this.posts.keys.indexOf(location.hash.slice(2))) && ref < 1 + Math.max(ReplyPruning.total - +Conf["Max Replies"], 0))) { + if (ReplyPruning.active && /^#p\d+$/.test(location.hash) && (1 <= (ref = this.posts.keys.indexOf(location.hash.slice(2))) && ref < 1 + Math.max(ReplyPruning.total - +Conf["Max Replies"], 0))) { ReplyPruning.active = ReplyPruning.inputs.enabled.checked = false; } $.after(this.OP.nodes.root, ReplyPruning.summary); @@ -16469,21 +20871,24 @@ ReplyPruning = (function() { for (i = 0, len = ref.length; i < len; i++) { fullID = ref[i]; ReplyPruning.total++; - if (g.posts[fullID].file) { + if (g.posts.get(fullID).file) { ReplyPruning.totalFiles++; } } }, update: function() { - var boardTop, frag, hidden1, hidden2, oldPos, post, posts; + var boardTop, frag, hidden1, hidden2, node, oldPos, post, posts; hidden1 = ReplyPruning.hidden; hidden2 = ReplyPruning.active ? Math.max(ReplyPruning.total - +Conf["Max Replies"], 0) : 0; oldPos = d.body.clientHeight - window.scrollY; posts = ReplyPruning.thread.posts; if (ReplyPruning.hidden < hidden2) { while (ReplyPruning.hidden < hidden2 && ReplyPruning.position < posts.keys.length) { - post = posts[posts.keys[ReplyPruning.position++]]; + post = posts.get(posts.keys[ReplyPruning.position++]); if (post.isReply && !post.isFetchedQuote) { + while ((node = ReplyPruning.summary.nextSibling) && node !== post.nodes.root) { + $.add(ReplyPruning.container, node); + } $.add(ReplyPruning.container, post.nodes.root); ReplyPruning.hidden++; if (post.file) { @@ -16494,8 +20899,11 @@ ReplyPruning = (function() { } else if (ReplyPruning.hidden > hidden2) { frag = $.frag(); while (ReplyPruning.hidden > hidden2 && ReplyPruning.position > 0) { - post = posts[posts.keys[--ReplyPruning.position]]; + post = posts.get(posts.keys[--ReplyPruning.position]); if (post.isReply && !post.isFetchedQuote) { + while ((node = ReplyPruning.container.lastChild) && node !== post.nodes.root) { + $.prepend(frag, node); + } $.prepend(frag, post.nodes.root); ReplyPruning.hidden--; if (post.file) { @@ -16504,9 +20912,9 @@ ReplyPruning = (function() { } } $.after(ReplyPruning.summary, frag); - $.event('PostsInserted'); + $.event('PostsInserted', null, ReplyPruning.summary.parentNode); } - ReplyPruning.summary.textContent = ReplyPruning.active ? Build.summaryText('+', ReplyPruning.hidden, ReplyPruning.hiddenFiles) : Build.summaryText('-', ReplyPruning.total, ReplyPruning.totalFiles); + ReplyPruning.summary.textContent = ReplyPruning.active ? g.SITE.Build.summaryText('+', ReplyPruning.hidden, ReplyPruning.hiddenFiles) : g.SITE.Build.summaryText('-', ReplyPruning.total, ReplyPruning.totalFiles); ReplyPruning.summary.hidden = ReplyPruning.total <= +Conf["Max Replies"]; if (hidden1 !== hidden2 && (boardTop = Header.getTopOf($('.board'))) < 0) { return window.scrollBy(0, Math.max(d.body.clientHeight - oldPos, window.scrollY + boardTop) - window.scrollY); @@ -16522,20 +20930,24 @@ ThreadStats = (function() { var ThreadStats; ThreadStats = { + postCount: 0, + fileCount: 0, + postIndex: 0, init: function() { - var sc, statsHTML, statsTitle; + var base, sc, statsHTML, statsTitle; if (g.VIEW !== 'thread' || !Conf['Thread Stats']) { return; } - statsHTML = { - innerHTML: "? / ?" + ((Conf["IP Count in Stats"]) ? " / ?" : "") + ((Conf["Page Count in Stats"]) ? " / ?" : "") - }; + if (Conf['Page Count in Stats']) { + this[(typeof (base = g.SITE).isPrunedByAge === "function" ? base.isPrunedByAge(g.BOARD) : void 0) ? 'showPurgePos' : 'showPage'] = true; + } + statsHTML = {innerHTML: "? / ?" + ((Conf["IP Count in Stats"] && g.SITE.hasIPCount) ? " / ?" : "") + ((Conf["Page Count in Stats"]) ? " / ?" : "")}; statsTitle = 'Posts / Files'; - if (Conf['IP Count in Stats']) { + if (Conf['IP Count in Stats'] && g.SITE.hasIPCount) { statsTitle += ' / IPs'; } if (Conf['Page Count in Stats']) { - statsTitle += (g.BOARD.ID === 'f' ? ' / Purge Position' : ' / Page'); + statsTitle += (this.showPurgePos ? ' / Purge Position' : ' / Page'); } if (Conf['Updater and Stats in Header']) { this.dialog = sc = $.el('span', { @@ -16545,9 +20957,7 @@ ThreadStats = (function() { $.extend(sc, statsHTML); Header.addShortcut('stats', sc, 200); } else { - this.dialog = sc = UI.dialog('thread-stats', 'bottom: 0px; right: 0px;', { - innerHTML: "
              " + (statsHTML).innerHTML + "
              " - }); + this.dialog = sc = UI.dialog('thread-stats', {innerHTML: "
              " + (statsHTML).innerHTML + "
              "}); $.addClass(doc, 'float'); $.ready(function() { return $.add(d.body, sc); @@ -16566,50 +20976,64 @@ ThreadStats = (function() { }); }, node: function() { - var fileCount, postCount; - postCount = 0; - fileCount = 0; - this.posts.forEach(function(post) { - postCount++; - if (post.file) { - fileCount++; - } - if (ThreadStats.pageCountEl) { - return ThreadStats.lastPost = post.info.date; - } - }); ThreadStats.thread = this; + ThreadStats.count(); + ThreadStats.update(); ThreadStats.fetchPage(); - ThreadStats.update(postCount, fileCount, this.ipCount); + $.on(d, 'PostsInserted', function() { + return $.queueTask(ThreadStats.onPostsInserted); + }); return $.on(d, 'ThreadUpdate', ThreadStats.onUpdate); }, + count: function() { + var i, j, n, post, posts, ref, ref1; + posts = ThreadStats.thread.posts; + n = posts.keys.length; + for (i = j = ref = ThreadStats.postIndex, ref1 = n; j < ref1; i = j += 1) { + post = posts.get(posts.keys[i]); + if (!post.isFetchedQuote) { + ThreadStats.postCount++; + ThreadStats.fileCount += post.files.length; + } + } + return ThreadStats.postIndex = n; + }, onUpdate: function(e) { - var fileCount, ipCount, newPosts, postCount, ref, ref1; + var fileCount, postCount, ref; if (e.detail[404]) { return; } - ref = e.detail, postCount = ref.postCount, fileCount = ref.fileCount, ipCount = ref.ipCount, newPosts = ref.newPosts; - ThreadStats.update(postCount, fileCount, ipCount); - if (!ThreadStats.pageCountEl) { - return; + ref = e.detail, postCount = ref.postCount, fileCount = ref.fileCount; + $.extend(ThreadStats, { + postCount: postCount, + fileCount: fileCount + }); + ThreadStats.postIndex = ThreadStats.thread.posts.keys.length; + ThreadStats.update(); + if (ThreadStats.showPage && ThreadStats.pageCountEl.textContent !== '1') { + return ThreadStats.fetchPage(); } - if (newPosts.length) { - ThreadStats.lastPost = g.posts[newPosts[newPosts.length - 1]].info.date; + }, + onPostsInserted: function() { + if (!(ThreadStats.thread.posts.keys.length > ThreadStats.postIndex)) { + return; } - if (g.BOARD.ID !== 'f' && ((ref1 = ThreadStats.pageCountEl) != null ? ref1.textContent : void 0) !== '1') { + ThreadStats.count(); + ThreadStats.update(); + if (ThreadStats.showPage && ThreadStats.pageCountEl.textContent !== '1') { return ThreadStats.fetchPage(); } }, - update: function(postCount, fileCount, ipCount) { - var fileCountEl, ipCountEl, postCountEl, thread; + update: function() { + var fileCountEl, ipCountEl, postCountEl, ref, thread; thread = ThreadStats.thread, postCountEl = ThreadStats.postCountEl, fileCountEl = ThreadStats.fileCountEl, ipCountEl = ThreadStats.ipCountEl; - postCountEl.textContent = postCount; - fileCountEl.textContent = fileCount; - if ((ipCount != null) && ipCountEl) { - ipCountEl.textContent = ipCount; + postCountEl.textContent = ThreadStats.postCount; + fileCountEl.textContent = ThreadStats.fileCount; + if (ipCountEl != null) { + ipCountEl.textContent = (ref = thread.ipCount) != null ? ref : '?'; } - (thread.postLimit && !thread.isSticky ? $.addClass : $.rmClass)(postCountEl, 'warning'); - return (thread.fileLimit && !thread.isSticky ? $.addClass : $.rmClass)(fileCountEl, 'warning'); + postCountEl.classList.toggle('warning', thread.postLimit && !thread.isSticky); + return fileCountEl.classList.toggle('warning', thread.fileLimit && !thread.isSticky); }, fetchPage: function() { if (!ThreadStats.pageCountEl) { @@ -16622,40 +21046,47 @@ ThreadStats = (function() { return; } ThreadStats.timeout = setTimeout(ThreadStats.fetchPage, 2 * $.MINUTE); - return $.ajax("//a.4cdn.org/" + ThreadStats.thread.board + "/threads.json", { - onload: ThreadStats.onThreadsLoad - }, { - whenModified: 'ThreadStats' - }); + return $.whenModified(g.SITE.urls.threadsListJSON(ThreadStats.thread), 'ThreadStats', ThreadStats.onThreadsLoad); }, onThreadsLoad: function() { - var i, j, k, len, len1, len2, page, purgePos, ref, ref1, ref2, thread; + var i, j, k, l, len, len1, len2, len3, len4, m, nThreads, o, page, pageNum, purgePos, ref, ref1, ref2, ref3, ref4, thread; if (this.status === 200) { - ref = this.response; - for (i = 0, len = ref.length; i < len; i++) { - page = ref[i]; - if (g.BOARD.ID === 'f') { - purgePos = 1; + if (ThreadStats.showPurgePos) { + purgePos = 1; + ref = this.response; + for (j = 0, len = ref.length; j < len; j++) { + page = ref[j]; ref1 = page.threads; - for (j = 0, len1 = ref1.length; j < len1; j++) { - thread = ref1[j]; + for (k = 0, len1 = ref1.length; k < len1; k++) { + thread = ref1[k]; if (thread.no < ThreadStats.thread.ID) { purgePos++; } } - ThreadStats.pageCountEl.textContent = purgePos; - } else { - ref2 = page.threads; - for (k = 0, len2 = ref2.length; k < len2; k++) { - thread = ref2[k]; - if (!(thread.no === ThreadStats.thread.ID)) { - continue; + } + ThreadStats.pageCountEl.textContent = purgePos; + return ThreadStats.pageCountEl.classList.toggle('warning', purgePos === 1); + } else { + i = nThreads = 0; + ref2 = this.response; + for (l = 0, len2 = ref2.length; l < len2; l++) { + page = ref2[l]; + nThreads += page.threads.length; + } + ref3 = this.response; + for (pageNum = m = 0, len3 = ref3.length; m < len3; pageNum = ++m) { + page = ref3[pageNum]; + ref4 = page.threads; + for (o = 0, len4 = ref4.length; o < len4; o++) { + thread = ref4[o]; + if (thread.no === ThreadStats.thread.ID) { + ThreadStats.pageCountEl.textContent = pageNum + 1; + ThreadStats.pageCountEl.classList.toggle('warning', i >= nThreads - this.response[0].threads.length); + ThreadStats.lastPageUpdate = new Date(thread.last_modified * $.SECOND); + ThreadStats.retry(); + return; } - ThreadStats.pageCountEl.textContent = page.page; - (page.page === this.response.length ? $.addClass : $.rmClass)(ThreadStats.pageCountEl, 'warning'); - ThreadStats.lastPageUpdate = new Date(thread.last_modified * $.SECOND); - ThreadStats.retry(); - return; + i++; } } } @@ -16664,11 +21095,11 @@ ThreadStats = (function() { } }, retry: function() { - var ref; - if (g.BOARD.ID !== 'f' && ThreadStats.lastPost > ThreadStats.lastPageUpdate && ((ref = ThreadStats.pageCountEl) != null ? ref.textContent : void 0) !== '1') { - clearTimeout(ThreadStats.timeout); - return ThreadStats.timeout = setTimeout(ThreadStats.fetchPage, 5 * $.SECOND); + if (!(ThreadStats.showPage && ThreadStats.pageCountEl.textContent !== '1' && !g.SITE.threadModTimeIgnoresSage && ThreadStats.thread.posts.get(ThreadStats.thread.lastPost).info.date > ThreadStats.lastPageUpdate)) { + return; } + clearTimeout(ThreadStats.timeout); + return ThreadStats.timeout = setTimeout(ThreadStats.fetchPage, 5 * $.SECOND); } }; @@ -16686,6 +21117,7 @@ ThreadUpdater = (function() { if (g.VIEW !== 'thread' || !Conf['Thread Updater']) { return; } + this.enabled = true; this.audio = $.el('audio'); if ($.engine !== 'gecko') { this.audio.src = this.beep; @@ -16694,14 +21126,10 @@ ThreadUpdater = (function() { this.dialog = sc = $.el('span', { id: 'updater' }); - $.extend(sc, { - innerHTML: "" - }); + $.extend(sc, {innerHTML: ""}); Header.addShortcut('updater', sc, 100); } else { - this.dialog = sc = UI.dialog('updater', 'bottom: 0px; left: 0px;', { - innerHTML: "
              " - }); + this.dialog = sc = UI.dialog('updater', {innerHTML: "
              "}); $.addClass(doc, 'float'); $.ready(function() { return $.add(d.body, sc); @@ -16715,9 +21143,7 @@ ThreadUpdater = (function() { updateLink = $.el('span', { className: 'brackets-wrap updatelink' }); - $.extend(updateLink, { - innerHTML: "Update" - }); + $.extend(updateLink, {innerHTML: "Update"}); Main.ready(function() { var navLinksBot; if ((navLinksBot = $('.navLinksBot'))) { @@ -16743,9 +21169,7 @@ ThreadUpdater = (function() { el: el }); } - this.settings = $.el('span', { - innerHTML: "Interval" - }); + this.settings = $.el('span', {innerHTML: "Interval"}); $.on(this.settings, 'click', this.intervalShortcut); subEntries.push({ el: this.settings @@ -16764,7 +21188,7 @@ ThreadUpdater = (function() { }, node: function() { ThreadUpdater.thread = this; - ThreadUpdater.root = this.OP.nodes.root.parentNode; + ThreadUpdater.root = this.nodes.root; ThreadUpdater.outdateCount = 0; ThreadUpdater.postIDs = []; ThreadUpdater.fileIDs = []; @@ -16835,11 +21259,12 @@ ThreadUpdater = (function() { } }, load: function() { - var req; - req = ThreadUpdater.req; - switch (req.status) { + if (this !== ThreadUpdater.req) { + return; + } + switch (this.status) { case 200: - ThreadUpdater.parse(req); + ThreadUpdater.parse(this); if (ThreadUpdater.thread.isArchived) { return ThreadUpdater.kill(); } else { @@ -16847,7 +21272,9 @@ ThreadUpdater = (function() { } break; case 404: - return $.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/catalog.json", { + return $.ajax(g.SITE.urls.catalogJSON({ + boardID: ThreadUpdater.thread.board.ID + }), { onloadend: function() { var confirmed, i, k, len, len1, page, ref, ref1, thread; if (this.status === 200) { @@ -16870,12 +21297,12 @@ ThreadUpdater = (function() { if (confirmed) { return ThreadUpdater.kill(); } else { - return ThreadUpdater.error(req); + return ThreadUpdater.error(this); } } }); default: - return ThreadUpdater.error(req); + return ThreadUpdater.error(this); } } }, @@ -16953,17 +21380,18 @@ ThreadUpdater = (function() { return ThreadUpdater.seconds--; }, update: function() { - var ref; + var oldReq; clearTimeout(ThreadUpdater.timeoutID); ThreadUpdater.set('timer', '...', 'loading'); - if ((ref = ThreadUpdater.req) != null) { - ref.abort(); - } - return ThreadUpdater.req = $.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/thread/" + ThreadUpdater.thread + ".json", { - onloadend: ThreadUpdater.cb.load, + if ((oldReq = ThreadUpdater.req)) { + delete ThreadUpdater.req; + oldReq.abort(); + } + return ThreadUpdater.req = $.whenModified(g.SITE.urls.threadJSON({ + boardID: ThreadUpdater.thread.board.ID, + threadID: ThreadUpdater.thread.ID + }), 'ThreadUpdater', ThreadUpdater.cb.load, { timeout: $.MINUTE - }, { - whenModified: 'ThreadUpdater' }); }, updateThreadStatus: function(type, status) { @@ -16985,10 +21413,10 @@ ThreadUpdater = (function() { thread = ThreadUpdater.thread; board = thread.board; ref = ThreadUpdater.postIDs, lastPost = ref[ref.length - 1]; - if (postObjects[postObjects.length - 1].no < lastPost && new Date(req.getResponseHeader('Last-Modified')) - thread.posts[lastPost].info.date < 30 * $.SECOND) { + if (postObjects[postObjects.length - 1].no < lastPost && new Date(req.getResponseHeader('Last-Modified')) - thread.posts.get(lastPost).info.date < 30 * $.SECOND) { return; } - Build.spoilerRange[board] = OP.custom_spoiler; + g.SITE.Build.spoilerRange[board] = OP.custom_spoiler; thread.setStatus('Archived', !!OP.archived); ThreadUpdater.updateThreadStatus('Sticky', !!OP.sticky); ThreadUpdater.updateThreadStatus('Closed', !!OP.closed); @@ -17011,12 +21439,12 @@ ThreadUpdater = (function() { if (ID <= lastPost) { continue; } - if ((post = thread.posts[ID]) && !post.isFetchedQuote) { + if ((post = thread.posts.get(ID)) && !post.isFetchedQuote) { post.resurrect(); continue; } newPosts.push(board + "." + ID); - node = Build.postFromObject(postObject, board.ID); + node = g.SITE.Build.postFromObject(postObject, board.ID); posts.push(new Post(node, thread, board)); if (ThreadUpdater.postID === ID) { delete ThreadUpdater.postID; @@ -17029,7 +21457,7 @@ ThreadUpdater = (function() { if (!(indexOf.call(index, ID) < 0)) { continue; } - thread.posts[ID].kill(); + thread.posts.get(ID).kill(); deletedPosts.push(board + "." + ID); } ThreadUpdater.postIDs = index; @@ -17040,7 +21468,7 @@ ThreadUpdater = (function() { if (!(!(indexOf.call(files, ID) >= 0 || (ref3 = board + "." + ID, indexOf.call(deletedPosts, ref3) >= 0)))) { continue; } - thread.posts[ID].kill(true); + thread.posts.get(ID).kill(true); deletedFiles.push(board + "." + ID); } ThreadUpdater.fileIDs = files; @@ -17071,7 +21499,7 @@ ThreadUpdater = (function() { $.add(ThreadUpdater.root, post.nodes.root); } } - $.event('PostsInserted'); + $.event('PostsInserted', null, ThreadUpdater.root); if (scroll) { if (Conf['Bottom Scroll']) { window.scrollTo(0, d.body.clientHeight); @@ -17106,11 +21534,12 @@ ThreadUpdater = (function() { ThreadWatcher = (function() { var ThreadWatcher, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, slice = [].slice; ThreadWatcher = { init: function() { - var sc; + var ref, sc; if (!(this.enabled = Conf['Thread Watcher'])) { return; } @@ -17119,26 +21548,26 @@ ThreadWatcher = (function() { textContent: 'Watcher', title: 'Thread Watcher', href: 'javascript:;', - className: 'disabled fa fa-eye' + className: 'fa fa-eye' }); this.db = new DataBoard('watchedThreads', this.refresh, true); - this.dialog = UI.dialog('thread-watcher', 'top: 50px; left: 0px;', { - innerHTML: "
              Thread Watcher ×
              " - }); + this.dbLM = new DataBoard('watcherLastModified', null, true); + this.dialog = UI.dialog('thread-watcher', {innerHTML: "
              Thread Watcher ×
              "}); this.status = $('#watcher-status', this.dialog); this.list = this.dialog.lastElementChild; this.refreshButton = $('.refresh', this.dialog); this.closeButton = $('.move > .close', this.dialog); - this.unreaddb = Unread.db || new DataBoard('lastReadPosts'); + this.unreaddb = Unread.db || UnreadIndex.db || new DataBoard('lastReadPosts'); this.unreadEnabled = Conf['Remember Last Read Post']; $.on(d, 'QRPostSuccessful', this.cb.post); $.on(sc, 'click', this.toggleWatcher); $.on(this.refreshButton, 'click', this.buttonFetchAll); $.on(this.closeButton, 'click', this.toggleWatcher); - $.on(d, '4chanXInitFinished', this.ready); + this.menu.addHeaderMenuEntry(); + $.onExists(doc, 'body', this.addDialog); switch (g.VIEW) { case 'index': - $.on(d, 'IndexRefresh', this.cb.onIndexRefresh); + $.on(d, 'IndexUpdate', this.cb.onIndexUpdate); break; case 'thread': $.on(d, 'ThreadUpdate', this.cb.onThreadRefresh); @@ -17146,20 +21575,22 @@ ThreadWatcher = (function() { if (Conf['Fixed Thread Watcher']) { $.addClass(doc, 'fixed-watcher'); } - if (Conf['Toggleable Thread Watcher']) { + if (!Conf['Persistent Thread Watcher']) { + $.addClass(ThreadWatcher.shortcut, 'disabled'); this.dialog.hidden = true; - Header.addShortcut('watcher', sc, 510); - $.addClass(doc, 'toggleable-watcher'); } + Header.addShortcut('watcher', sc, 510); + ThreadWatcher.initLastModified(); ThreadWatcher.fetchAuto(); - if (g.VIEW === 'index' && Conf['JSON Index'] && Conf['Menu'] && g.BOARD.ID !== 'f') { + $.on(window, 'visibilitychange focus', function() { + return $.queueTask(ThreadWatcher.fetchAuto); + }); + if (Conf['Menu'] && Index.enabled) { Menu.menu.addEntry({ el: $.el('a', { href: 'javascript:;', className: 'has-shortcut-text' - }, { - innerHTML: "Alt+click" - }), + }, {innerHTML: "Alt+click"}), order: 6, open: function(arg) { var thread; @@ -17173,13 +21604,16 @@ ThreadWatcher = (function() { } this.cb = function() { $.event('CloseMenu'); - return ThreadWatcher.toggle(thread); + return ThreadWatcher.toggle(thread, true); }; $.on(this.el, 'click', this.cb); return true; } }); } + if ((ref = g.VIEW) !== 'index' && ref !== 'thread') { + return; + } Callbacks.Post.push({ name: 'Thread Watcher', cb: this.node @@ -17191,65 +21625,78 @@ ThreadWatcher = (function() { }, isWatched: function(thread) { var ref; - return (ref = ThreadWatcher.db) != null ? ref.get({ + return !!((ref = ThreadWatcher.db) != null ? ref.get({ boardID: thread.board.ID, threadID: thread.ID - }) : void 0; + }) : void 0); + }, + isWatchedRaw: function(boardID, threadID) { + var ref; + return !!((ref = ThreadWatcher.db) != null ? ref.get({ + boardID: boardID, + threadID: threadID + }) : void 0); + }, + setToggler: function(toggler, isWatched) { + toggler.classList.toggle('watched', isWatched); + return toggler.title = (isWatched ? 'Unwatch' : 'Watch') + " Thread"; }, node: function() { - var toggler; + var boardID, data, siteID, threadID, toggler; if (this.isReply) { return; } if (this.isClone) { - toggler = $('.watch-thread-link', this.nodes.post); + toggler = $('.watch-thread-link', this.nodes.info); } else { toggler = $.el('a', { href: 'javascript:;', className: 'watch-thread-link' }); - $.before($('input', this.nodes.post), toggler); + $.before($('input', this.nodes.info), toggler); + } + siteID = g.SITE.ID; + boardID = this.board.ID; + threadID = this.thread.ID; + data = ThreadWatcher.db.get({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }); + ThreadWatcher.setToggler(toggler, !!data); + $.on(toggler, 'click', ThreadWatcher.cb.toggle); + if (data && (data.excerpt == null)) { + return $.queueTask((function(_this) { + return function() { + return ThreadWatcher.update(siteID, boardID, threadID, { + excerpt: Get.threadExcerpt(_this.thread) + }); + }; + })(this)); } - return $.on(toggler, 'click', ThreadWatcher.cb.toggle); }, catalogNode: function() { if (ThreadWatcher.isWatched(this.thread)) { $.addClass(this.nodes.root, 'watched'); } - $.on(this.nodes.thumb.parentNode, 'click', (function(_this) { + return $.on(this.nodes.root, 'mousedown click', (function(_this) { return function(e) { if (!(e.button === 0 && e.altKey)) { return; } - ThreadWatcher.toggle(_this.thread); + if (e.type === 'click') { + ThreadWatcher.toggle(_this.thread, true); + } return e.preventDefault(); }; })(this)); - return $.on(this.nodes.thumb.parentNode, 'mousedown', function(e) { - if (e.button === 0 && e.altKey) { - return e.preventDefault(); - } - }); }, - ready: function() { - $.off(d, '4chanXInitFinished', ThreadWatcher.ready); + addDialog: function() { if (!Main.isThisPageLegit()) { return; } - ThreadWatcher.refresh(); - $.add(d.body, ThreadWatcher.dialog); - if (!Conf['Auto Watch']) { - return; - } - return $.get('AutoWatch', 0, function(arg) { - var AutoWatch, thread; - AutoWatch = arg.AutoWatch; - if (!(thread = g.BOARD.threads[AutoWatch])) { - return; - } - ThreadWatcher.add(thread); - return $["delete"]('AutoWatch'); - }); + ThreadWatcher.build(); + return $.prepend(d.body, ThreadWatcher.dialog); }, toggleWatcher: function() { $.toggleClass(ThreadWatcher.shortcut, 'disabled'); @@ -17257,101 +21704,153 @@ ThreadWatcher = (function() { }, cb: { openAll: function() { - var a, i, len, ref; + var a, j, len1, ref; if ($.hasClass(this, 'disabled')) { return; } - ref = $$('a[title]', ThreadWatcher.list); - for (i = 0, len = ref.length; i < len; i++) { - a = ref[i]; + ref = $$('a.watcher-link', ThreadWatcher.list); + for (j = 0, len1 = ref.length; j < len1; j++) { + a = ref[j]; + $.open(a.href); + } + return $.event('CloseMenu'); + }, + openUnread: function() { + var a, j, len1, ref; + if ($.hasClass(this, 'disabled')) { + return; + } + ref = $$('.replies-unread > a.watcher-link', ThreadWatcher.list); + for (j = 0, len1 = ref.length; j < len1; j++) { + a = ref[j]; + $.open(a.href); + } + return $.event('CloseMenu'); + }, + openDeads: function() { + var a, j, len1, ref; + if ($.hasClass(this, 'disabled')) { + return; + } + ref = $$('.dead-thread > a.watcher-link', ThreadWatcher.list); + for (j = 0, len1 = ref.length; j < len1; j++) { + a = ref[j]; $.open(a.href); } return $.event('CloseMenu'); }, + clear: function() { + var boardID, j, len1, ref, ref1, siteID, threadID; + if (!confirm("Delete ALL threads from watcher?")) { + return; + } + ref = ThreadWatcher.getAll(); + for (j = 0, len1 = ref.length; j < len1; j++) { + ref1 = ref[j], siteID = ref1.siteID, boardID = ref1.boardID, threadID = ref1.threadID; + ThreadWatcher.db["delete"]({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }); + } + ThreadWatcher.refresh(true); + return $.event('CloseMenu'); + }, pruneDeads: function() { - var boardID, data, i, len, ref, ref1, threadID; + var boardID, data, j, len1, ref, ref1, siteID, threadID; if ($.hasClass(this, 'disabled')) { return; } - ThreadWatcher.db.forceSync(); ref = ThreadWatcher.getAll(); - for (i = 0, len = ref.length; i < len; i++) { - ref1 = ref[i], boardID = ref1.boardID, threadID = ref1.threadID, data = ref1.data; - if (!data.isDead) { - continue; + for (j = 0, len1 = ref.length; j < len1; j++) { + ref1 = ref[j], siteID = ref1.siteID, boardID = ref1.boardID, threadID = ref1.threadID, data = ref1.data; + if (data.isDead) { + ThreadWatcher.db["delete"]({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }); + } + } + ThreadWatcher.refresh(true); + return $.event('CloseMenu'); + }, + dismiss: function() { + var boardID, data, j, len1, ref, ref1, siteID, threadID; + ref = ThreadWatcher.getAll(); + for (j = 0, len1 = ref.length; j < len1; j++) { + ref1 = ref[j], siteID = ref1.siteID, boardID = ref1.boardID, threadID = ref1.threadID, data = ref1.data; + if (data.quotingYou) { + ThreadWatcher.update(siteID, boardID, threadID, { + dismiss: data.quotingYou || 0 + }); } - delete ThreadWatcher.db.data.boards[boardID][threadID]; - ThreadWatcher.db.deleteIfEmpty({ - boardID: boardID - }); } - ThreadWatcher.db.save(); - ThreadWatcher.refresh(); return $.event('CloseMenu'); }, toggle: function() { var thread; thread = Get.postFromNode(this).thread; - Index.followedThreadID = thread.ID; - ThreadWatcher.toggle(thread); - return delete Index.followedThreadID; + return ThreadWatcher.toggle(thread, true); }, rm: function() { - var boardID, ref, threadID; + var boardID, ref, siteID, threadID; + siteID = this.parentNode.dataset.siteID; ref = this.parentNode.dataset.fullID.split('.'), boardID = ref[0], threadID = ref[1]; - return ThreadWatcher.rm(boardID, +threadID); + return ThreadWatcher.rm(siteID, boardID, +threadID, void 0, true); }, post: function(e) { - var boardID, postID, ref, threadID; + var boardID, cb, postID, ref, threadID; ref = e.detail, boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID; + cb = PostRedirect.delay(); if (postID === threadID) { if (Conf['Auto Watch']) { - return $.set('AutoWatch', threadID); + return ThreadWatcher.addRaw(boardID, threadID, {}, cb, true); } } else if (Conf['Auto Watch Reply']) { - return ThreadWatcher.add(g.threads[boardID + '.' + threadID]); + return ThreadWatcher.add(g.threads.get(boardID + '.' + threadID) || new Thread(threadID, g.boards[boardID] || new Board(boardID)), cb, true); } }, - onIndexRefresh: function() { - var boardID, data, db, ref, threadID; + onIndexUpdate: function(e) { + var boardID, data, db, nKilled, ref, ref1, siteID, threadID; db = ThreadWatcher.db; + siteID = g.SITE.ID; boardID = g.BOARD.ID; - db.forceSync(); - ref = db.data.boards[boardID]; + nKilled = 0; + ref = db.data[siteID].boards[boardID]; for (threadID in ref) { data = ref[threadID]; - if (!(data != null ? data.isDead : void 0) && !(threadID in g.BOARD.threads)) { - if (Conf['Auto Prune'] || !(data && typeof data === 'object')) { - db["delete"]({ - boardID: boardID, - threadID: threadID - }); - } else { - if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) { - ThreadWatcher.fetchStatus({ - boardID: boardID, - threadID: threadID, - data: data - }); - } - data.isDead = true; - db.set({ - boardID: boardID, - threadID: threadID, - val: data - }); - } + if (!(!(data != null ? data.isDead : void 0) && (ref1 = boardID + "." + threadID, indexOf.call(e.detail.threads, ref1) < 0))) { + continue; + } + if (!e.detail.threads.some(function(fullID) { + return +fullID.split('.')[1] > threadID; + })) { + continue; + } + if (Conf['Auto Prune'] || !(data && typeof data === 'object')) { + db["delete"]({ + boardID: boardID, + threadID: threadID + }); + nKilled++; + } else { + ThreadWatcher.fetchStatus({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + data: data + }); } } - return ThreadWatcher.refresh(); + if (nKilled) { + return ThreadWatcher.refresh(); + } }, onThreadRefresh: function(e) { var thread; - thread = g.threads[e.detail.threadID]; - if (!(e.detail[404] && ThreadWatcher.db.get({ - boardID: thread.board.ID, - threadID: thread.ID - }))) { + thread = g.threads.get(e.detail.threadID); + if (!(e.detail[404] && ThreadWatcher.isWatched(thread))) { return; } return ThreadWatcher.add(thread); @@ -17359,6 +21858,38 @@ ThreadWatcher = (function() { }, requests: [], fetched: 0, + fetch: function(url, arg, args, cb) { + var ajax, force, onloadend, ref, req, siteID; + siteID = arg.siteID, force = arg.force; + if (ThreadWatcher.requests.length === 0) { + ThreadWatcher.status.textContent = '...'; + $.addClass(ThreadWatcher.refreshButton, 'fa-spin'); + } + onloadend = function() { + if (this.finished) { + return; + } + this.finished = true; + ThreadWatcher.fetched++; + if (ThreadWatcher.fetched === ThreadWatcher.requests.length) { + ThreadWatcher.clearRequests(); + } else { + ThreadWatcher.status.textContent = (Math.round(ThreadWatcher.fetched / ThreadWatcher.requests.length * 100)) + "%"; + } + return cb.apply(this, args); + }; + ajax = siteID === g.SITE.ID ? $.ajax : CrossOrigin.ajax; + if (force) { + if ((ref = $.lastModified.ThreadWatcher) != null) { + delete ref[url]; + } + } + req = $.whenModified(url, 'ThreadWatcher', onloadend, { + timeout: $.MINUTE, + ajax: ajax + }); + return ThreadWatcher.requests.push(req); + }, clearRequests: function() { ThreadWatcher.requests = []; ThreadWatcher.fetched = 0; @@ -17366,196 +21897,402 @@ ThreadWatcher = (function() { return $.rmClass(ThreadWatcher.refreshButton, 'fa-spin'); }, abort: function() { - var i, len, ref, req; + var j, len1, ref, req; + delete ThreadWatcher.syncing; ref = ThreadWatcher.requests; - for (i = 0, len = ref.length; i < len; i++) { - req = ref[i]; - if (req.readyState !== 4) { - req.abort(); + for (j = 0, len1 = ref.length; j < len1; j++) { + req = ref[j]; + if (!(!req.finished)) { + continue; } + req.finished = true; + req.abort(); } return ThreadWatcher.clearRequests(); }, + initLastModified: function() { + var base, boardID, boards, data, date, lm, ref, ref1, siteID, url; + lm = ((base = $.lastModified)['ThreadWatcher'] || (base['ThreadWatcher'] = $.dict())); + ref = ThreadWatcher.dbLM.data; + for (siteID in ref) { + boards = ref[siteID]; + ref1 = boards.boards; + for (boardID in ref1) { + data = ref1[boardID]; + if (ThreadWatcher.db.get({ + siteID: siteID, + boardID: boardID + })) { + for (url in data) { + date = data[url]; + lm[url] = date; + } + } else { + ThreadWatcher.dbLM["delete"]({ + siteID: siteID, + boardID: boardID + }); + } + } + } + }, fetchAuto: function() { - var db, interval, now; + var db, interval, now, ref; clearTimeout(ThreadWatcher.timeout); if (!Conf['Auto Update Thread Watcher']) { return; } db = ThreadWatcher.db; - interval = ThreadWatcher.unreadEnabled && Conf['Show Unread Count'] ? 5 * $.MINUTE : 2 * $.HOUR; + interval = Conf['Show Page'] || (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) ? 5 * $.MINUTE : 2 * $.HOUR; now = Date.now(); - if (now >= (db.data.lastChecked || 0) + interval) { - db.data.lastChecked = now; - ThreadWatcher.fetchAllStatus(); - db.save(); + if (!((now - interval < (ref = db.data.lastChecked || 0) && ref <= now) || d.hidden || !d.hasFocus())) { + ThreadWatcher.fetchAllStatus(interval); } return ThreadWatcher.timeout = setTimeout(ThreadWatcher.fetchAuto, interval); }, buttonFetchAll: function() { - if (ThreadWatcher.requests.length) { + if (ThreadWatcher.syncing || ThreadWatcher.requests.length) { return ThreadWatcher.abort(); } else { return ThreadWatcher.fetchAllStatus(); } }, - fetchAllStatus: function() { - var i, len, ref, thread, threads; - ThreadWatcher.db.forceSync(); - ThreadWatcher.unreaddb.forceSync(); - if ((ref = QuoteYou.db) != null) { - ref.forceSync(); + fetchAllStatus: function(interval) { + var dbi, dbs, j, len1, n, results; + if (interval == null) { + interval = 0; + } + ThreadWatcher.status.textContent = '...'; + $.addClass(ThreadWatcher.refreshButton, 'fa-spin'); + ThreadWatcher.syncing = true; + dbs = [ThreadWatcher.db, ThreadWatcher.unreaddb, QuoteYou.db].filter(function(x) { + return x; + }); + n = 0; + results = []; + for (j = 0, len1 = dbs.length; j < len1; j++) { + dbi = dbs[j]; + results.push(dbi.forceSync(function() { + var board, boards, db, deep, k, len2, now, ref, ref1; + if ((++n) === dbs.length) { + if (!ThreadWatcher.syncing) { + return; + } + delete ThreadWatcher.syncing; + if (!((0 <= (ref = Date.now() - (ThreadWatcher.db.data.lastChecked || 0)) && ref < interval))) { + db = ThreadWatcher.db; + now = Date.now(); + deep = !((now - 2 * $.HOUR < (ref1 = db.data.lastChecked2 || 0) && ref1 <= now)); + boards = ThreadWatcher.getAll(true); + for (k = 0, len2 = boards.length; k < len2; k++) { + board = boards[k]; + ThreadWatcher.fetchBoard(board, deep); + } + db.setLastChecked(); + if (deep) { + db.setLastChecked('lastChecked2'); + } + } + if (ThreadWatcher.fetched === ThreadWatcher.requests.length) { + return ThreadWatcher.clearRequests(); + } + } + })); } - if (!(threads = ThreadWatcher.getAll()).length) { + return results; + }, + fetchBoard: function(board, deep) { + var base, boardID, data, force, j, len1, ref, site, siteID, thread, url, urlF; + if (!board.some(function(thread) { + return !thread.data.isDead; + })) { return; } - for (i = 0, len = threads.length; i < len; i++) { - thread = threads[i]; - ThreadWatcher.fetchStatus(thread); + force = false; + for (j = 0, len1 = board.length; j < len1; j++) { + thread = board[j]; + data = thread.data; + if (!data.isDead && data.last !== -1) { + if (Conf['Show Page'] && (data.page == null)) { + force = true; + } + if (data.modified == null) { + force = thread.force = true; + } + } } - }, - fetchStatus: function(thread, force) { - var boardID, data, req, threadID; - boardID = thread.boardID, threadID = thread.threadID, data = thread.data; - if (data.isDead && !force) { + ref = board[0], siteID = ref.siteID, boardID = ref.boardID; + site = g.sites[siteID]; + if (!site) { return; } - if (ThreadWatcher.requests.length === 0) { - ThreadWatcher.status.textContent = '...'; - $.addClass(ThreadWatcher.refreshButton, 'fa-spin'); + urlF = deep && site.threadModTimeIgnoresSage ? 'catalogJSON' : 'threadsListJSON'; + url = typeof (base = site.urls)[urlF] === "function" ? base[urlF]({ + siteID: siteID, + boardID: boardID + }) : void 0; + if (!url) { + return; } - req = $.ajax("//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", { - onloadend: function() { - return ThreadWatcher.parseStatus.call(this, thread); - }, - timeout: $.MINUTE - }, { - whenModified: force ? false : 'ThreadWatcher' + return ThreadWatcher.fetch(url, { + siteID: siteID, + force: force + }, [board, url], ThreadWatcher.parseBoard); + }, + parseBoard: function(board, url) { + var base, boardID, data, i, index, item, j, k, l, lastPage, len1, len2, len3, len4, lmDate, m, modified, nThreads, oldest, page, pageLength, ref, ref1, ref2, ref3, ref4, replies, siteID, thread, threadID, threads; + if (this.status !== 200) { + return; + } + ref = board[0], siteID = ref.siteID, boardID = ref.boardID; + lmDate = this.getResponseHeader('Last-Modified'); + ThreadWatcher.dbLM.extend({ + siteID: siteID, + boardID: boardID, + val: $.item(url, lmDate) }); - return ThreadWatcher.requests.push(req); + threads = $.dict(); + pageLength = 0; + nThreads = 0; + oldest = null; + try { + pageLength = ((ref1 = this.response[0]) != null ? ref1.threads.length : void 0) || 0; + ref2 = this.response; + for (i = j = 0, len1 = ref2.length; j < len1; i = ++j) { + page = ref2[i]; + ref3 = page.threads; + for (k = 0, len2 = ref3.length; k < len2; k++) { + item = ref3[k]; + threads[item.no] = { + page: i + 1, + index: nThreads, + modified: item.last_modified, + replies: item.replies + }; + nThreads++; + if ((oldest == null) || item.no < oldest) { + oldest = item.no; + } + } + } + } catch (error) { + for (l = 0, len3 = board.length; l < len3; l++) { + thread = board[l]; + ThreadWatcher.fetchStatus(thread); + } + } + for (m = 0, len4 = board.length; m < len4; m++) { + thread = board[m]; + threadID = thread.threadID, data = thread.data; + if (threads[threadID]) { + ref4 = threads[threadID], page = ref4.page, index = ref4.index, modified = ref4.modified, replies = ref4.replies; + if (Conf['Show Page']) { + lastPage = (typeof (base = g.sites[siteID]).isPrunedByAge === "function" ? base.isPrunedByAge({ + siteID: siteID, + boardID: boardID + }) : void 0) ? threadID === oldest : index >= nThreads - pageLength; + ThreadWatcher.update(siteID, boardID, threadID, { + page: page, + lastPage: lastPage + }); + } + if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) { + if (modified !== data.modified || ((replies != null) && replies !== data.replies)) { + (thread.newData || (thread.newData = {})).modified = modified; + ThreadWatcher.fetchStatus(thread); + } + } + } else { + ThreadWatcher.fetchStatus(thread); + } + } }, - parseStatus: function(arg) { - var boardID, data, i, isDead, lastReadPost, len, match, postObj, quotesYou, quotingYou, ref, ref1, regexp, threadID, unread; - boardID = arg.boardID, threadID = arg.threadID, data = arg.data; - ThreadWatcher.fetched++; - if (ThreadWatcher.fetched === ThreadWatcher.requests.length) { - ThreadWatcher.clearRequests(); - } else { - ThreadWatcher.status.textContent = (Math.round(ThreadWatcher.fetched / ThreadWatcher.requests.length * 100)) + "%"; + fetchStatus: function(thread) { + var base, boardID, data, force, ref, siteID, threadID, url; + siteID = thread.siteID, boardID = thread.boardID, threadID = thread.threadID, data = thread.data, force = thread.force; + url = (ref = g.sites[siteID]) != null ? typeof (base = ref.urls).threadJSON === "function" ? base.threadJSON({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }) : void 0 : void 0; + if (!url) { + return; + } + if (data.isDead && !force) { + return; + } + if (data.last === -1) { + return; } + return ThreadWatcher.fetch(url, { + siteID: siteID, + force: force + }, [thread], ThreadWatcher.parseStatus); + }, + parseStatus: function(thread, isArchiveURL) { + var archiveURL, base, boardID, data, force, isArchived, isDead, j, last, lastReadPost, len1, match, newData, postObj, quotesYou, quotingYou, ref, ref1, ref2, ref3, regexp, replies, site, siteID, threadID, unread, youOP; + siteID = thread.siteID, boardID = thread.boardID, threadID = thread.threadID, data = thread.data, newData = thread.newData, force = thread.force; + site = g.sites[siteID]; if (this.status === 200 && this.response) { - isDead = !!this.response.posts[0].archived; + last = this.response.posts[this.response.posts.length - 1].no; + replies = this.response.posts.length - 1; + isDead = isArchived = !!(this.response.posts[0].archived || isArchiveURL); if (isDead && Conf['Auto Prune']) { - ThreadWatcher.db["delete"]({ - boardID: boardID, - threadID: threadID - }); - ThreadWatcher.refresh(); + ThreadWatcher.rm(siteID, boardID, threadID); + return; + } + if (last === data.last && isDead === data.isDead && isArchived === data.isArchived) { return; } lastReadPost = ThreadWatcher.unreaddb.get({ + siteID: siteID, boardID: boardID, threadID: threadID, defaultValue: 0 }); - unread = quotingYou = 0; - ref = this.response.posts; - for (i = 0, len = ref.length; i < len; i++) { - postObj = ref[i]; - if (!(postObj.no > lastReadPost)) { + unread = data.unread || 0; + quotingYou = data.quotingYou || 0; + youOP = !!((ref = QuoteYou.db) != null ? ref.get({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + postID: threadID + }) : void 0); + ref1 = this.response.posts; + for (j = 0, len1 = ref1.length; j < len1; j++) { + postObj = ref1[j]; + if (!(postObj.no > (data.last || 0) && postObj.no > lastReadPost)) { continue; } - if ((ref1 = QuoteYou.db) != null ? ref1.get({ + if ((ref2 = QuoteYou.db) != null ? ref2.get({ + siteID: siteID, boardID: boardID, threadID: threadID, postID: postObj.no }) : void 0) { continue; } - unread++; - if (!(QuoteYou.db && postObj.com)) { - continue; - } quotesYou = false; - regexp = /]*\bhref="(?:\/([^\/]+)\/thread\/)?(\d+)?(?:#p(\d+))?"/g; - while (match = regexp.exec(postObj.com)) { - if (QuoteYou.db.get({ - boardID: match[1] || boardID, - threadID: match[2] || threadID, - postID: match[3] || match[2] || threadID - })) { - quotesYou = true; - break; + if (!Conf['Require OP Quote Link'] && youOP) { + quotesYou = true; + } else if (QuoteYou.db && postObj.com) { + regexp = site.regexp.quotelinkHTML; + regexp.lastIndex = 0; + while ((match = regexp.exec(postObj.com))) { + if (QuoteYou.db.get({ + siteID: siteID, + boardID: match[1] ? encodeURIComponent(match[1]) : boardID, + threadID: match[2] || threadID, + postID: match[3] || match[2] || threadID + })) { + quotesYou = true; + break; + } } } - if (quotesYou && !Filter.isHidden(Build.parseJSON(postObj, boardID))) { - quotingYou++; + if (!unread || (!quotingYou && quotesYou)) { + if (Filter.isHidden(site.Build.parseJSON(postObj, { + siteID: siteID, + boardID: boardID + }))) { + continue; + } } - } - if (isDead !== data.isDead || unread !== data.unread || quotingYou !== data.quotingYou) { - data.isDead = isDead; - data.unread = unread; - data.quotingYou = quotingYou; - ThreadWatcher.db.set({ - boardID: boardID, - threadID: threadID, - val: data - }); - return ThreadWatcher.refresh(); - } + unread++; + if (quotesYou) { + quotingYou = postObj.no; + } + } + newData || (newData = {}); + $.extend(newData, { + last: last, + replies: replies, + isDead: isDead, + isArchived: isArchived, + unread: unread, + quotingYou: quotingYou + }); + return ThreadWatcher.update(siteID, boardID, threadID, newData); } else if (this.status === 404) { - if (Conf['Auto Prune']) { - ThreadWatcher.db["delete"]({ - boardID: boardID, - threadID: threadID + archiveURL = (ref3 = g.sites[siteID]) != null ? typeof (base = ref3.urls).archivedThreadJSON === "function" ? base.archivedThreadJSON({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }) : void 0 : void 0; + if (!isArchiveURL && archiveURL) { + return ThreadWatcher.fetch(archiveURL, { + siteID: siteID, + force: force + }, [thread, true], ThreadWatcher.parseStatus); + } else if (site.mayLackJSON && (data.last == null)) { + return ThreadWatcher.update(siteID, boardID, threadID, { + last: -1 }); } else { - data.isDead = true; - delete data.unread; - delete data.quotingYou; - ThreadWatcher.db.set({ - boardID: boardID, - threadID: threadID, - val: data + return ThreadWatcher.update(siteID, boardID, threadID, { + isDead: true }); } - return ThreadWatcher.refresh(); } }, - getAll: function() { - var all, boardID, data, ref, threadID, threads; + getAll: function(groupByBoard) { + var all, boardID, boards, cont, data, ref, ref1, siteID, threadID, threads; all = []; - ref = ThreadWatcher.db.data.boards; - for (boardID in ref) { - threads = ref[boardID]; - if (Conf['Current Board'] && boardID !== g.BOARD.ID) { - continue; - } - for (threadID in threads) { - data = threads[threadID]; - if (data && typeof data === 'object') { - all.push({ - boardID: boardID, - threadID: threadID, - data: data - }); + ref = ThreadWatcher.db.data; + for (siteID in ref) { + boards = ref[siteID]; + ref1 = boards.boards; + for (boardID in ref1) { + threads = ref1[boardID]; + if (Conf['Current Board'] && (siteID !== g.SITE.ID || boardID !== g.BOARD.ID)) { + continue; + } + if (groupByBoard) { + all.push((cont = [])); + } + for (threadID in threads) { + data = threads[threadID]; + if (data && typeof data === 'object') { + (groupByBoard ? cont : all).push({ + siteID: siteID, + boardID: boardID, + threadID: threadID, + data: data + }); + } } } } return all; }, - makeLine: function(boardID, threadID, data) { - var count, div, fullID, link, title, x; + makeLine: function(siteID, boardID, threadID, data) { + var count, div, excerpt, fullID, isArchived, link, page, ref, title, x; x = $.el('a', { className: 'fa fa-times', href: 'javascript:;' }); $.on(x, 'click', ThreadWatcher.cb.rm); + excerpt = data.excerpt, isArchived = data.isArchived; + excerpt || (excerpt = "/" + boardID + "/ - No." + threadID); + if (Conf['Show Site Prefix']) { + excerpt = ThreadWatcher.prefixes[siteID] + excerpt; + } link = $.el('a', { - href: "/" + boardID + "/thread/" + threadID, - title: data.excerpt, + href: ((ref = g.sites[siteID]) != null ? ref.urls.thread({ + siteID: siteID, + boardID: boardID, + threadID: threadID + }, isArchived) : void 0) || '', + title: excerpt, className: 'watcher-link' }); + if (Conf['Show Page'] && (data.page != null)) { + page = $.el('span', { + textContent: "[" + data.page + "]", + className: 'watcher-page' + }); + $.add(link, page); + } if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count'] && (data.unread != null)) { count = $.el('span', { textContent: "(" + data.unread + ")", @@ -17564,19 +22301,28 @@ ThreadWatcher = (function() { $.add(link, count); } title = $.el('span', { - textContent: data.excerpt, + textContent: excerpt, className: 'watcher-title' }); $.add(link, title); div = $.el('div'); fullID = boardID + "." + threadID; div.dataset.fullID = fullID; + div.dataset.siteID = siteID; if (g.VIEW === 'thread' && fullID === (g.BOARD + "." + g.THREADID)) { $.addClass(div, 'current'); } if (data.isDead) { $.addClass(div, 'dead-thread'); } + if (Conf['Show Page']) { + if (data.lastPage) { + $.addClass(div, 'last-page'); + } + if (data.page != null) { + div.dataset.page = data.page; + } + } if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) { if (data.unread === 0) { $.addClass(div, 'replies-read'); @@ -17584,75 +22330,122 @@ ThreadWatcher = (function() { if (data.unread) { $.addClass(div, 'replies-unread'); } - if (data.quotingYou) { + if ((data.quotingYou || 0) > (data.dismiss || 0)) { $.addClass(div, 'replies-quoting-you'); } } $.add(div, [x, $.tn(' '), link]); return div; }, - refresh: function() { - var boardID, data, i, j, len, len1, list, nodes, ref, ref1, ref2, refresher, threadID; + setPrefixes: function(threads) { + var conflicts, conflicts2, j, k, len, len1, len2, prefix, prefixes, siteID, siteID2; + prefixes = $.dict(); + for (j = 0, len1 = threads.length; j < len1; j++) { + siteID = threads[j].siteID; + if (siteID in prefixes) { + continue; + } + len = 0; + prefix = ''; + conflicts = Object.keys(prefixes); + while (conflicts.length > 0) { + len++; + prefix = siteID.slice(0, len); + conflicts2 = []; + for (k = 0, len2 = conflicts.length; k < len2; k++) { + siteID2 = conflicts[k]; + if (siteID2.slice(0, len) === prefix) { + conflicts2.push(siteID2); + } else if (prefixes[siteID2].length < len) { + prefixes[siteID2] = siteID2.slice(0, len); + } + } + conflicts = conflicts2; + } + prefixes[siteID] = prefix; + } + return ThreadWatcher.prefixes = prefixes; + }, + build: function() { + var boardID, data, j, len1, list, nodes, ref, siteID, thread, threadID, threads; nodes = []; - ref = ThreadWatcher.getAll(); - for (i = 0, len = ref.length; i < len; i++) { - ref1 = ref[i], boardID = ref1.boardID, threadID = ref1.threadID, data = ref1.data; - nodes.push(ThreadWatcher.makeLine(boardID, threadID, data)); + threads = ThreadWatcher.getAll(); + ThreadWatcher.setPrefixes(threads); + for (j = 0, len1 = threads.length; j < len1; j++) { + ref = threads[j], siteID = ref.siteID, boardID = ref.boardID, threadID = ref.threadID, data = ref.data; + if ((data.excerpt == null) && siteID === g.SITE.ID && (thread = g.threads.get(boardID + "." + threadID)) && thread.OP) { + ThreadWatcher.db.extend({ + boardID: boardID, + threadID: threadID, + val: { + excerpt: Get.threadExcerpt(thread) + } + }); + } + nodes.push(ThreadWatcher.makeLine(siteID, boardID, threadID, data)); } list = ThreadWatcher.list; $.rmAll(list); $.add(list, nodes); + return ThreadWatcher.refreshIcon(); + }, + refresh: function(manual) { + ThreadWatcher.build(); g.threads.forEach(function(thread) { - var helper, j, len1, post, ref2, toggler; - helper = ThreadWatcher.isWatched(thread) ? ['addClass', 'Unwatch'] : ['rmClass', 'Watch']; + var isWatched, j, len1, post, ref, toggler; + isWatched = ThreadWatcher.isWatched(thread); if (thread.OP) { - ref2 = [thread.OP].concat(slice.call(thread.OP.clones)); - for (j = 0, len1 = ref2.length; j < len1; j++) { - post = ref2[j]; - toggler = $('.watch-thread-link', post.nodes.post); - $[helper[0]](toggler, 'watched'); - toggler.title = helper[1] + " Thread"; + ref = [thread.OP].concat(slice.call(thread.OP.clones)); + for (j = 0, len1 = ref.length; j < len1; j++) { + post = ref[j]; + if ((toggler = $('.watch-thread-link', post.nodes.info))) { + ThreadWatcher.setToggler(toggler, isWatched); + } } } if (thread.catalogView) { - return $[helper[0]](thread.catalogView.nodes.root, 'watched'); + return thread.catalogView.nodes.root.classList.toggle('watched', isWatched); } }); - ThreadWatcher.refreshIcon(); - ref2 = ThreadWatcher.menu.refreshers; - for (j = 0, len1 = ref2.length; j < len1; j++) { - refresher = ref2[j]; - refresher(); - } - if (Index.nodes && Conf['Pin Watched Threads']) { - Index.sort(); - return Index.buildIndex(); + if (Conf['Pin Watched Threads']) { + return $.event('SortIndex', { + deferred: !(manual && Conf['Index Mode'] === 'catalog') + }); } }, refreshIcon: function() { - var className, i, len, ref; + var className, j, len1, ref; ref = ['replies-unread', 'replies-quoting-you']; - for (i = 0, len = ref.length; i < len; i++) { - className = ref[i]; + for (j = 0, len1 = ref.length; j < len1; j++) { + className = ref[j]; ThreadWatcher.shortcut.classList.toggle(className, !!$("." + className, ThreadWatcher.dialog)); } }, - update: function(boardID, threadID, newData) { - var data, key, line, n, newLine, ref, val; + update: function(siteID, boardID, threadID, newData) { + var data, j, key, len1, line, n, newLine, ref, ref1, val; if (!(data = (ref = ThreadWatcher.db) != null ? ref.get({ + siteID: siteID, boardID: boardID, threadID: threadID }) : void 0)) { return; } if (newData.isDead && Conf['Auto Prune']) { - ThreadWatcher.db["delete"]({ - boardID: boardID, - threadID: threadID - }); - ThreadWatcher.refresh(); + ThreadWatcher.rm(siteID, boardID, threadID); return; } + if (newData.isDead || newData.last === -1) { + ref1 = ['isArchived', 'page', 'lastPage', 'unread', 'quotingyou']; + for (j = 0, len1 = ref1.length; j < len1; j++) { + key = ref1[j]; + if (!(key in newData)) { + newData[key] = void 0; + } + } + } + if ((newData.last != null) && newData.last < data.last) { + newData.modified = void 0; + } n = 0; for (key in newData) { val = newData[key]; @@ -17663,21 +22456,14 @@ ThreadWatcher = (function() { if (!n) { return; } - ThreadWatcher.db.forceSync(); - if (!(data = ThreadWatcher.db.get({ - boardID: boardID, - threadID: threadID - }))) { - return; - } - $.extend(data, newData); - ThreadWatcher.db.set({ + ThreadWatcher.db.extend({ + siteID: siteID, boardID: boardID, threadID: threadID, - val: data + val: newData }); - if (line = $("#watched-threads > [data-full-i-d='" + boardID + "." + threadID + "']", ThreadWatcher.dialog)) { - newLine = ThreadWatcher.makeLine(boardID, threadID, data); + if ((line = $("#watched-threads > [data-site-i-d='" + siteID + "'][data-full-i-d='" + boardID + "." + threadID + "']", ThreadWatcher.dialog))) { + newLine = ThreadWatcher.makeLine(siteID, boardID, threadID, data); $.replace(line, newLine); return ThreadWatcher.refreshIcon(); } else { @@ -17699,34 +22485,40 @@ ThreadWatcher = (function() { }); return cb(); } - if (data.isDead && !((data.unread != null) || (data.quotingYou != null))) { + if (data.isDead && !((data.isArchived != null) || (data.page != null) || (data.lastPage != null) || (data.unread != null) || (data.quotingYou != null))) { return cb(); } - data.isDead = true; - delete data.unread; - delete data.quotingYou; - return ThreadWatcher.db.set({ + return ThreadWatcher.db.extend({ boardID: boardID, threadID: threadID, - val: data + val: { + isDead: true, + isArchived: void 0, + page: void 0, + lastPage: void 0, + unread: void 0, + quotingYou: void 0 + } }, cb); }, - toggle: function(thread) { - var boardID, threadID; + toggle: function(thread, manual) { + var boardID, siteID, threadID; + siteID = g.SITE.ID; boardID = thread.board.ID; threadID = thread.ID; if (ThreadWatcher.db.get({ boardID: boardID, threadID: threadID })) { - return ThreadWatcher.rm(boardID, threadID); + return ThreadWatcher.rm(siteID, boardID, threadID, void 0, manual); } else { - return ThreadWatcher.add(thread); + return ThreadWatcher.add(thread, void 0, manual); } }, - add: function(thread) { - var boardID, data, threadID; + add: function(thread, cb, manual) { + var boardID, data, siteID, threadID; data = {}; + siteID = g.SITE.ID; boardID = thread.board.ID; threadID = thread.ID; if (thread.isDead) { @@ -17734,35 +22526,54 @@ ThreadWatcher = (function() { boardID: boardID, threadID: threadID })) { - ThreadWatcher.rm(boardID, threadID); + ThreadWatcher.rm(siteID, boardID, threadID, cb); return; } data.isDead = true; } - data.excerpt = Get.threadExcerpt(thread); - ThreadWatcher.db.set({ + if (thread.OP) { + data.excerpt = Get.threadExcerpt(thread); + } + return ThreadWatcher.addRaw(boardID, threadID, data, cb, manual); + }, + addRaw: function(boardID, threadID, data, cb, manual) { + var oldData, thread; + oldData = ThreadWatcher.db.get({ boardID: boardID, threadID: threadID, - val: data + defaultValue: $.dict() }); - ThreadWatcher.refresh(); - if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) { - return ThreadWatcher.fetchStatus({ - boardID: boardID, - threadID: threadID, - data: data - }, true); + delete oldData.last; + delete oldData.modified; + $.extend(oldData, data); + ThreadWatcher.db.set({ + boardID: boardID, + threadID: threadID, + val: oldData + }, cb); + ThreadWatcher.refresh(manual); + thread = { + siteID: g.SITE.ID, + boardID: boardID, + threadID: threadID, + data: data, + force: true + }; + if (Conf['Show Page'] && !data.isDead) { + return ThreadWatcher.fetchBoard([thread]); + } else if (ThreadWatcher.unreadEnabled && Conf['Show Unread Count']) { + return ThreadWatcher.fetchStatus(thread); } }, - rm: function(boardID, threadID) { + rm: function(siteID, boardID, threadID, cb, manual) { ThreadWatcher.db["delete"]({ + siteID: siteID, boardID: boardID, threadID: threadID - }); - return ThreadWatcher.refresh(); + }, cb); + return ThreadWatcher.refresh(manual); }, menu: { - refreshers: [], init: function() { var menu; if (!Conf['Thread Watcher']) { @@ -17772,7 +22583,6 @@ ThreadWatcher = (function() { $.on($('.menu-button', ThreadWatcher.dialog), 'click', function(e) { return menu.toggle(e, this, ThreadWatcher); }); - this.addHeaderMenuEntry(); return this.addMenuEntries(); }, addHeaderMenuEntry: function() { @@ -17785,73 +22595,97 @@ ThreadWatcher = (function() { }); Header.menu.addEntry({ el: entryEl, - order: 60 - }); - $.on(entryEl, 'click', function() { - return ThreadWatcher.toggle(g.threads[g.BOARD + "." + g.THREADID]); + order: 60, + open: function() { + var addClass, ref, rmClass, text; + ref = !!ThreadWatcher.db.get({ + boardID: g.BOARD.ID, + threadID: g.THREADID + }) ? ['unwatch-thread', 'watch-thread', 'Unwatch thread'] : ['watch-thread', 'unwatch-thread', 'Watch thread'], addClass = ref[0], rmClass = ref[1], text = ref[2]; + $.addClass(entryEl, addClass); + $.rmClass(entryEl, rmClass); + entryEl.textContent = text; + return true; + } }); - return this.refreshers.push(function() { - var addClass, ref, rmClass, text; - ref = $('.current', ThreadWatcher.list) ? ['unwatch-thread', 'watch-thread', 'Unwatch thread'] : ['watch-thread', 'unwatch-thread', 'Watch thread'], addClass = ref[0], rmClass = ref[1], text = ref[2]; - $.addClass(entryEl, addClass); - $.rmClass(entryEl, rmClass); - return entryEl.textContent = text; + return $.on(entryEl, 'click', function() { + return ThreadWatcher.toggle(g.threads.get(g.BOARD + "." + g.THREADID), true); }); }, addMenuEntries: function() { - var cb, conf, entries, entry, i, len, name, ref, ref1, refresh, subEntries; + var cb, conf, entries, entry, j, len1, name, open, ref, ref1, text, title; entries = []; entries.push({ + text: 'Open all threads', cb: ThreadWatcher.cb.openAll, - entry: { - el: $.el('a', { - textContent: 'Open all threads' - }) - }, - refresh: function() { - return (ThreadWatcher.list.firstElementChild ? $.rmClass : $.addClass)(this.el, 'disabled'); + open: function() { + this.el.classList.toggle('disabled', !ThreadWatcher.list.firstElementChild); + return true; } }); entries.push({ - cb: ThreadWatcher.cb.pruneDeads, - entry: { - el: $.el('a', { - textContent: 'Prune dead threads' - }) - }, - refresh: function() { - return ($('.dead-thread', ThreadWatcher.list) ? $.rmClass : $.addClass)(this.el, 'disabled'); + text: 'Open unread threads', + cb: ThreadWatcher.cb.openUnread, + open: function() { + this.el.classList.toggle('disabled', !$('.replies-unread', ThreadWatcher.list)); + return true; + } + }); + entries.push({ + text: 'Open dead threads', + cb: ThreadWatcher.cb.openDeads, + open: function() { + this.el.classList.toggle('disabled', !$('.dead-thread', ThreadWatcher.list)); + return true; } }); - subEntries = []; - ref = Config.threadWatcher; - for (name in ref) { - conf = ref[name]; - subEntries.push(this.createSubEntry(name, conf[1])); - } entries.push({ - entry: { - el: $.el('span', { - textContent: 'Settings' - }), - subEntries: subEntries + text: 'Clear all threads', + cb: ThreadWatcher.cb.clear, + open: function() { + this.el.classList.toggle('disabled', !ThreadWatcher.list.firstElementChild); + return true; } }); - for (i = 0, len = entries.length; i < len; i++) { - ref1 = entries[i], entry = ref1.entry, cb = ref1.cb, refresh = ref1.refresh; - if (entry.el.nodeName === 'A') { - entry.el.href = 'javascript:;'; + entries.push({ + text: 'Prune dead threads', + cb: ThreadWatcher.cb.pruneDeads, + open: function() { + this.el.classList.toggle('disabled', !$('.dead-thread', ThreadWatcher.list)); + return true; } - if (cb) { - $.on(entry.el, 'click', cb); + }); + entries.push({ + text: 'Dismiss posts quoting you', + title: 'Unhighlight the thread watcher icon and threads until there are new replies quoting you.', + cb: ThreadWatcher.cb.dismiss, + open: function() { + this.el.classList.toggle('disabled', !$.hasClass(ThreadWatcher.shortcut, 'replies-quoting-you')); + return true; } - if (refresh) { - this.refreshers.push(refresh.bind(entry)); + }); + for (j = 0, len1 = entries.length; j < len1; j++) { + ref = entries[j], text = ref.text, title = ref.title, cb = ref.cb, open = ref.open; + entry = { + el: $.el('a', { + textContent: text, + href: 'javascript:;' + }) + }; + if (title) { + entry.el.title = title; } + $.on(entry.el, 'click', cb); + entry.open = open.bind(entry); this.menu.addEntry(entry); } + ref1 = Config.threadWatcher; + for (name in ref1) { + conf = ref1[name]; + this.addCheckbox(name, conf[1]); + } }, - createSubEntry: function(name, desc) { + addCheckbox: function(name, desc) { var entry, input; entry = { type: 'thread watcher', @@ -17865,13 +22699,15 @@ ThreadWatcher = (function() { entry.el.title += '\n[Remember Last Read Post is disabled.]'; } $.on(input, 'change', $.cb.checked); - if (name === 'Current Board' || name === 'Show Unread Count') { - $.on(input, 'change', ThreadWatcher.refresh); - } - if (name === 'Show Unread Count' || name === 'Auto Update Thread Watcher') { + $.on(input, 'change', function() { + if (name === 'Current Board' || name === 'Show Page' || name === 'Show Unread Count' || name === 'Show Site Prefix') { + return ThreadWatcher.refresh(); + } + }); + if (name === 'Show Page' || name === 'Show Unread Count' || name === 'Auto Update Thread Watcher') { $.on(input, 'change', ThreadWatcher.fetchAuto); } - return entry; + return this.menu.addEntry(entry); } } }; @@ -17895,7 +22731,8 @@ Unread = (function() { this.db = new DataBoard('lastReadPosts', this.sync); } this.hr = $.el('hr', { - id: 'unread-line' + id: 'unread-line', + className: 'unread-line' }); this.posts = new Set(); this.postsQuotingYou = new Set(); @@ -17911,7 +22748,7 @@ Unread = (function() { }); }, node: function() { - var ID, j, len, ref, ref1; + var ID, j, len, ref, ref1, resetLink; Unread.thread = this; Unread.title = d.title; Unread.lastReadPost = ((ref = Unread.db) != null ? ref.get({ @@ -17927,7 +22764,22 @@ Unread = (function() { } } $.one(d, '4chanXInitFinished', Unread.ready); - return $.on(d, 'ThreadUpdate', Unread.onUpdate); + $.on(d, 'PostsInserted', Unread.onUpdate); + $.on(d, 'ThreadUpdate', function(e) { + if (e.detail[404]) { + return Unread.update(); + } + }); + resetLink = $.el('a', { + href: 'javascript:;', + className: 'unread-reset', + textContent: 'Mark all unread' + }); + $.on(resetLink, 'click', Unread.reset); + return Header.menu.addEntry({ + el: resetLink, + order: 70 + }); }, ready: function() { if (Conf['Remember Last Read Post'] && Conf['Scroll to Last Read Post']) { @@ -17949,22 +22801,46 @@ Unread = (function() { } }, scroll: function() { - var hash, position, ref, root; + var bottom, hash, position; if ((hash = location.hash.match(/\d+/)) && hash[0] in Unread.thread.posts) { return; } - ReplyPruning.showIfHidden((ref = Unread.position) != null ? ref.data.nodes.root.id : void 0); position = Unread.positionPrev(); while (position) { - root = position.data.nodes.root; - if (!root.getBoundingClientRect().height) { + bottom = position.data.nodes.bottom; + if (!bottom.getBoundingClientRect().height) { position = position.prev; } else { - Header.scrollToIfNeeded(root, true); + Header.scrollToIfNeeded(bottom, true); break; } } }, + reset: function() { + if (Unread.lastReadPost == null) { + return; + } + Unread.posts = new Set(); + Unread.postsQuotingYou = new Set(); + Unread.order = new RandomAccessList(); + Unread.position = null; + Unread.lastReadPost = 0; + Unread.readCount = 0; + Unread.thread.posts.forEach(function(post) { + return Unread.addPost.call(post); + }); + $.forceSync('Remember Last Read Post'); + if (Conf['Remember Last Read Post'] && (!Unread.thread.isDead || Unread.thread.isArchived)) { + Unread.db.set({ + boardID: Unread.thread.board.ID, + threadID: Unread.thread.ID, + val: 0 + }); + } + Unread.updatePosition(); + Unread.setLine(); + return Unread.update(); + }, sync: function() { var ID, i, j, lastReadPost, postIDs, ref, ref1; if (Unread.lastReadPost == null) { @@ -17982,7 +22858,7 @@ Unread = (function() { postIDs = Unread.thread.posts.keys; for (i = j = ref = Unread.readCount, ref1 = postIDs.length; j < ref1; i = j += 1) { ID = +postIDs[i]; - if (!Unread.thread.posts[ID].isFetchedQuote) { + if (!Unread.thread.posts.get(ID).isFetchedQuote) { if (ID > Unread.lastReadPost) { break; } @@ -17996,19 +22872,14 @@ Unread = (function() { return Unread.update(); }, addPost: function() { - var ref; if (this.isFetchedQuote || this.isClone) { return; - } - Unread.order.push(this); - if (this.ID <= Unread.lastReadPost || this.isHidden || ((ref = QuoteYou.db) != null ? ref.get({ - boardID: this.board.ID, - threadID: this.thread.ID, - postID: this.ID - }) : void 0)) { + } + Unread.order.push(this); + if (this.ID <= Unread.lastReadPost || this.isHidden || QuoteYou.isYou(this)) { return; } - Unread.posts.add(this.ID); + Unread.posts.add((Unread.posts.last = this.ID)); Unread.addPostQuotingYou(this); return Unread.position != null ? Unread.position : Unread.position = Unread.order[this.ID]; }, @@ -18020,22 +22891,25 @@ Unread = (function() { if (!((ref1 = QuoteYou.db) != null ? ref1.get(Get.postDataFromLink(quotelink)) : void 0)) { continue; } - Unread.postsQuotingYou.add(post.ID); + Unread.postsQuotingYou.add((Unread.postsQuotingYou.last = post.ID)); Unread.openNotification(post); return; } }, - openNotification: function(post) { + openNotification: function(post, predicate) { var notif; + if (predicate == null) { + predicate = ' replied to you'; + } if (!Header.areNotificationsEnabled) { return; } - notif = new Notification(post.info.nameBlock + " replied to you", { - body: post.info.commentDisplay, + notif = new Notification("" + post.info.nameBlock + predicate, { + body: post.commentDisplay(), icon: Favicon.logo }); notif.onclick = function() { - Header.scrollToIfNeeded(post.nodes.root, true); + Header.scrollToIfNeeded(post.nodes.bottom, true); return window.focus(); }; return notif.onshow = function() { @@ -18044,12 +22918,12 @@ Unread = (function() { }, 7 * $.SECOND); }; }, - onUpdate: function(e) { - if (!e.detail[404]) { + onUpdate: function() { + return $.queueTask(function() { Unread.setLine(); Unread.read(); - } - return Unread.update(); + return Unread.update(); + }); }, readSinglePost: function(post) { var ID; @@ -18064,7 +22938,7 @@ Unread = (function() { return Unread.update(); }, read: $.debounce(100, function(e) { - var ID, count, data, ref, ref1, root; + var ID, bottom, count, data, ref; if (!Unread.posts.size && Unread.readCount !== Unread.thread.posts.keys.length) { Unread.saveLastReadPost(); } @@ -18074,20 +22948,13 @@ Unread = (function() { count = 0; while (Unread.position) { ref = Unread.position, ID = ref.ID, data = ref.data; - root = data.nodes.root; - if (!(!root.getBoundingClientRect().height || Header.getBottomOf(root) > -1)) { + bottom = data.nodes.bottom; + if (!(!bottom.getBoundingClientRect().height || Header.getBottomOf(bottom) > -1)) { break; } count++; Unread.posts["delete"](ID); Unread.postsQuotingYou["delete"](ID); - if ((ref1 = QuoteYou.db) != null ? ref1.get({ - boardID: data.board.ID, - threadID: data.thread.ID, - postID: ID - }) : void 0) { - QuoteYou.lastRead = root; - } Unread.position = Unread.position.next; } if (!count) { @@ -18113,7 +22980,7 @@ Unread = (function() { postIDs = Unread.thread.posts.keys; for (i = j = ref = Unread.readCount, ref1 = postIDs.length; j < ref1; i = j += 1) { ID = +postIDs[i]; - if (!Unread.thread.posts[ID].isFetchedQuote) { + if (!Unread.thread.posts.get(ID).isFetchedQuote) { if (Unread.posts.has(ID)) { break; } @@ -18124,7 +22991,6 @@ Unread = (function() { if (Unread.thread.isDead && !Unread.thread.isArchived) { return; } - Unread.db.forceSync(); return Unread.db.set({ boardID: Unread.thread.board.ID, threadID: Unread.thread.ID, @@ -18132,12 +22998,20 @@ Unread = (function() { }); }), setLine: function(force) { + var node, oldPosition, ref; if (!Conf['Unread Line']) { return; } if (Unread.hr.hidden || d.hidden || (force === true)) { + oldPosition = Unread.linePosition; if ((Unread.linePosition = Unread.positionPrev())) { - $.after(Unread.linePosition.data.nodes.root, Unread.hr); + if (Unread.linePosition !== oldPosition) { + node = Unread.linePosition.data.nodes.bottom; + if (((ref = node.nextSibling) != null ? ref.tagName : void 0) === 'BR') { + node = node.nextSibling; + } + $.after(node, Unread.hr); + } } else { $.rm(Unread.hr); } @@ -18154,211 +23028,331 @@ Unread = (function() { titleDead = Unread.thread.isDead ? Unread.title.replace('-', (Unread.thread.isArchived ? '- Archived -' : '- 404 -')) : Unread.title; d.title = "" + titleQuotingYou + titleCount + titleDead; } + Unread.saveThreadWatcherCount(); + if (Conf['Unread Favicon'] && g.SITE.software === 'yotsuba') { + isDead = Unread.thread.isDead; + return Favicon.set((countQuotingYou ? (isDead ? 'unreadDeadY' : 'unreadY') : count ? (isDead ? 'unreadDead' : 'unread') : (isDead ? 'dead' : 'default'))); + } + }, + saveThreadWatcherCount: $.debounce(2 * $.SECOND, function() { + var i, j, posts, quotingYou, ref; $.forceSync('Remember Last Read Post'); if (Conf['Remember Last Read Post'] && (!Unread.thread.isDead || Unread.thread.isArchived)) { - ThreadWatcher.update(Unread.thread.board.ID, Unread.thread.ID, { + quotingYou = !Conf['Require OP Quote Link'] && QuoteYou.isYou(Unread.thread.OP) ? Unread.posts : Unread.postsQuotingYou; + if (!quotingYou.size) { + quotingYou.last = 0; + } else if (!quotingYou.has(quotingYou.last)) { + quotingYou.last = 0; + posts = Unread.thread.posts.keys; + for (i = j = ref = posts.length - 1; j >= 0; i = j += -1) { + if (quotingYou.has(+posts[i])) { + quotingYou.last = posts[i]; + break; + } + } + } + return ThreadWatcher.update(g.SITE.ID, Unread.thread.board.ID, Unread.thread.ID, { + last: Unread.thread.lastPost, isDead: Unread.thread.isDead, - unread: count, - quotingYou: countQuotingYou + isArchived: Unread.thread.isArchived, + unread: Unread.posts.size, + quotingYou: quotingYou.last || 0 }); } - if (Conf['Unread Favicon']) { - isDead = Unread.thread.isDead; - Favicon.el.href = countQuotingYou ? Favicon[isDead ? 'unreadDeadY' : 'unreadY'] : count ? Favicon[isDead ? 'unreadDead' : 'unread'] : Favicon[isDead ? 'dead' : 'default']; - return $.add(d.head, Favicon.el); + }) + }; + + return Unread; + +}).call(this); + +UnreadIndex = (function() { + var UnreadIndex; + + UnreadIndex = { + lastReadPost: $.dict(), + hr: $.dict(), + markReadLink: $.dict(), + init: function() { + if (!(g.VIEW === 'index' && Conf['Remember Last Read Post'] && Conf['Unread Line in Index'])) { + return; + } + this.enabled = true; + this.db = new DataBoard('lastReadPosts', this.sync); + Callbacks.Thread.push({ + name: 'Unread Line in Index', + cb: this.node + }); + $.on(d, 'IndexRefreshInternal', this.onIndexRefresh); + return $.on(d, 'PostsInserted PostsRemoved', this.onPostsInserted); + }, + node: function() { + UnreadIndex.lastReadPost[this.fullID] = UnreadIndex.db.get({ + boardID: this.board.ID, + threadID: this.ID + }) || 0; + if (!Index.enabled) { + return UnreadIndex.update(this); + } + }, + onIndexRefresh: function(e) { + var i, len, ref, results, thread, threadID; + if (e.detail.isCatalog) { + return; + } + ref = e.detail.threadIDs; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + threadID = ref[i]; + thread = g.threads.get(threadID); + results.push(UnreadIndex.update(thread)); + } + return results; + }, + onPostsInserted: function(e) { + var ref, ref1, thread, wasVisible; + if (e.target === Index.root) { + return; + } + thread = Get.threadFromNode(e.target); + if (!thread || thread.nodes.root !== e.target) { + return; + } + wasVisible = !!((ref = UnreadIndex.hr[thread.fullID]) != null ? ref.parentNode : void 0); + UnreadIndex.update(thread); + if (Conf['Scroll to Last Read Post'] && e.type === 'PostsInserted' && !wasVisible && !!((ref1 = UnreadIndex.hr[thread.fullID]) != null ? ref1.parentNode : void 0)) { + return Header.scrollToIfNeeded(UnreadIndex.hr[thread.fullID], true); + } + }, + sync: function() { + return g.threads.forEach(function(thread) { + var lastReadPost, ref; + lastReadPost = UnreadIndex.db.get({ + boardID: thread.board.ID, + threadID: thread.ID + }) || 0; + if (lastReadPost !== UnreadIndex.lastReadPost[thread.fullID]) { + UnreadIndex.lastReadPost[thread.fullID] = lastReadPost; + if ((ref = thread.nodes.root) != null ? ref.parentNode : void 0) { + return UnreadIndex.update(thread); + } + } + }); + }, + update: function(thread) { + var divider, firstUnread, hasUnread, hr, lastReadPost, link, repliesRead, repliesShown; + lastReadPost = UnreadIndex.lastReadPost[thread.fullID]; + repliesShown = 0; + repliesRead = 0; + firstUnread = null; + thread.posts.forEach(function(post) { + if (post.isReply && thread.nodes.root.contains(post.nodes.root)) { + repliesShown++; + if (post.ID <= lastReadPost) { + return repliesRead++; + } else if ((!firstUnread || post.ID < firstUnread.ID) && !post.isHidden && !QuoteYou.isYou(post)) { + return firstUnread = post; + } + } + }); + hr = UnreadIndex.hr[thread.fullID]; + if (firstUnread && (repliesRead || (lastReadPost === thread.OP.ID && (!$(g.SITE.selectors.summary, thread.nodes.root) || thread.ID in ExpandThread.statuses)))) { + if (!hr) { + hr = UnreadIndex.hr[thread.fullID] = $.el('hr', { + className: 'unread-line' + }); + } + $.before(firstUnread.nodes.root, hr); + } else { + $.rm(hr); + } + hasUnread = repliesShown ? firstUnread || !repliesRead : Index.enabled ? thread.lastPost > lastReadPost : thread.OP.ID > lastReadPost; + thread.nodes.root.classList.toggle('unread-thread', hasUnread); + link = UnreadIndex.markReadLink[thread.fullID]; + if (!link) { + link = UnreadIndex.markReadLink[thread.fullID] = $.el('a', { + className: 'unread-mark-read brackets-wrap', + href: 'javascript:;', + textContent: 'Mark Read' + }); + $.on(link, 'click', UnreadIndex.markRead); + } + if ((divider = $(g.SITE.selectors.threadDivider, thread.nodes.root))) { + return $.before(divider, link); + } else { + return $.add(thread.nodes.root, link); } + }, + markRead: function() { + var thread; + thread = Get.threadFromNode(this); + UnreadIndex.lastReadPost[thread.fullID] = thread.lastPost; + UnreadIndex.db.set({ + boardID: thread.board.ID, + threadID: thread.ID, + val: thread.lastPost + }); + $.rm(UnreadIndex.hr[thread.fullID]); + thread.nodes.root.classList.remove('unread-thread'); + return ThreadWatcher.update(g.SITE.ID, thread.board.ID, thread.ID, { + last: thread.lastPost, + unread: 0, + quotingYou: 0 + }); } }; - return Unread; + return UnreadIndex; }).call(this); Captcha = {}; (function() { - Captcha.fixes = { - imageKeys: '789456123uiojklm'.split('').concat(['Comma', 'Period']), - imageKeys16: '7890uiopjkl'.split('').concat(['Semicolon', 'm', 'Comma', 'Period', 'Slash']), - css: '.rc-imageselect-target > div:focus, .rc-image-tile-target:focus {\n outline: 2px solid #4a90e2;\n}\n.rc-imageselect-target td:focus {\n box-shadow: inset 0 0 0 2px #4a90e2;\n outline: none;\n}\n.rc-button-default:focus {\n box-shadow: inset 0 0 0 2px #0063d6;\n}', - cssNoscript: '.fbc-payload-imageselect {\n position: relative;\n}\n.fbc-payload-imageselect > label {\n position: absolute;\n display: block;\n height: 93.3px;\n width: 93.3px;\n}\nlabel[data-row="0"] {top: 0px;}\nlabel[data-row="1"] {top: 93.3px;}\nlabel[data-row="2"] {top: 186.6px;}\nlabel[data-col="0"] {left: 0px;}\nlabel[data-col="1"] {left: 93.3px;}\nlabel[data-col="2"] {left: 186.6px;}\n.fbc-payload-imageselect > input:focus + label {\n outline: 2px solid #4a90e2;\n}\n.fbc-button-verify input:focus {\n box-shadow: inset 0 0 0 2px #0063d6;\n}\nbody.focus .fbc {\n box-shadow: inset 0 0 0 2px #4a90e2;\n}', + Captcha.cache = { init: function() { - switch (location.pathname.split('/')[3]) { - case 'anchor': - return this.initMain(); - case 'frame': - return this.initPopup(); - case 'fallback': - return this.initNoscript(); - } - }, - initMain: function() { - var a, j, len, ref; - $.onExists(d.body, '#recaptcha-anchor', function(checkbox) { - var focus; - focus = function() { - var ref; - if (d.hasFocus() && ((ref = d.activeElement) === d.documentElement || ref === d.body)) { - return checkbox.focus(); - } + $.on(d, 'SaveCaptcha', (function(_this) { + return function(e) { + return _this.saveAPI(e.detail); }; - focus(); - return $.on(window, 'focus', function() { - return $.queueTask(focus); - }); - }); - ref = $$('.rc-anchor-pt a'); - for (j = 0, len = ref.length; j < len; j++) { - a = ref[j]; - a.tabIndex = -1; - } + })(this)); + return $.on(d, 'NoCaptcha', (function(_this) { + return function(e) { + return _this.noCaptcha(e.detail); + }; + })(this)); }, - initPopup: function() { - $.addStyle(this.css); - this.fixImages(); - new MutationObserver((function(_this) { + captchas: [], + getCount: function() { + return this.captchas.length; + }, + neededRaw: function() { + return !(this.haveCookie() || this.captchas.length || QR.req || this.submitCB) && (QR.posts.length > 1 || Conf['Auto-load captcha'] || !QR.posts[0].isOnlyQuotes() || QR.posts[0].file); + }, + needed: function() { + return this.neededRaw() && $.event('LoadCaptcha'); + }, + prerequest: function() { + if (!Conf['Prerequest Captcha']) { + return; + } + return $.queueTask((function(_this) { return function() { - return _this.fixImages(); + var isReply; + if (!_this.prerequested && _this.neededRaw() && !$.event('LoadCaptcha') && !QR.captcha.occupied() && QR.cooldown.seconds <= 60 && QR.selected === QR.posts[QR.posts.length - 1] && !QR.selected.isOnlyQuotes()) { + isReply = QR.selected.thread !== 'new'; + if (!$.event('RequestCaptcha', { + isReply: isReply + })) { + _this.prerequested = true; + _this.submitCB = function(captcha) { + if (captcha) { + return _this.save(captcha); + } + }; + return _this.updateCount(); + } + } }; - })(this)).observe(d.body, { - childList: true, - subtree: true - }); - return $.on(d, 'keydown', this.keybinds.bind(this)); + })(this)); }, - initNoscript: function() { - var data, ref, token; - this.noscript = true; - data = (token = (ref = $('.fbc-verification-token > textarea')) != null ? ref.value : void 0) ? { - token: token - } : { - working: true - }; - new Connection(window.parent, '*').send(data); - d.body.classList.toggle('focus', d.hasFocus()); - $.on(window, 'focus blur', function() { - return d.body.classList.toggle('focus', d.hasFocus()); - }); - this.images = $$('.fbc-payload-imageselect > input'); - this.width = 3; - if (this.images.length !== 9) { - return; + haveCookie: function() { + return /\b_ct=/.test(d.cookie) && QR.posts[0].thread !== 'new'; + }, + getOne: function() { + var captcha; + delete this.prerequested; + this.clear(); + if ((captcha = this.captchas.shift())) { + this.count(); + return captcha; + } else { + return null; } - $.addStyle(this.cssNoscript); - this.addLabels(); - $.on(d, 'keydown', this.keybinds.bind(this)); - return $.on($('.fbc-imageselect-challenge > form'), 'submit', this.checkForm.bind(this)); }, - fixImages: function() { - var img, j, len, ref; - this.images = $$('.rc-image-tile-target'); - if (!this.images.length) { - this.images = $$('.rc-imageselect-target > div, .rc-imageselect-target td'); + request: function(isReply) { + if (!this.submitCB) { + if ($.event('RequestCaptcha', { + isReply: isReply + })) { + return; + } } - this.width = $$('.rc-imageselect-target tr:first-of-type td').length || Math.round(Math.sqrt(this.images.length)); - ref = this.images; - for (j = 0, len = ref.length; j < len; j++) { - img = ref[j]; - img.tabIndex = 0; + return (function(_this) { + return function(cb) { + _this.submitCB = cb; + return _this.updateCount(); + }; + })(this); + }, + abort: function() { + if (this.submitCB) { + delete this.submitCB; + $.event('AbortCaptcha'); + return this.updateCount(); } - if (this.images.length === 9) { - return this.addTooltips(this.images); + }, + saveAPI: function(captcha) { + var cb; + if ((cb = this.submitCB)) { + delete this.submitCB; + cb(captcha); + return this.updateCount(); } else { - return this.addTooltips16(this.images); + return this.save(captcha); } }, - addLabels: function() { - var checkbox, i, imageSelect, label, labels; - imageSelect = $('.fbc-payload-imageselect'); - labels = (function() { - var j, len, ref, results; - ref = this.images; - results = []; - for (i = j = 0, len = ref.length; j < len; i = ++j) { - checkbox = ref[i]; - checkbox.id = "checkbox-" + i; - label = $.el('label', { - htmlFor: checkbox.id - }); - label.dataset.row = Math.floor(i / 3); - label.dataset.col = i % 3; - $.after(checkbox, label); - results.push(label); + noCaptcha: function(detail) { + var cb; + if ((cb = this.submitCB)) { + if (!this.haveCookie() || (detail != null ? detail.error : void 0)) { + QR.error((detail != null ? detail.error : void 0) || 'Failed to retrieve captcha.'); + QR.captcha.setup(d.activeElement === QR.nodes.status); } - return results; - }).call(this); - return this.addTooltips(labels); - }, - addTooltips: function(nodes) { - var i, j, len, node; - for (i = j = 0, len = nodes.length; j < len; i = ++j) { - node = nodes[i]; - node.title = this.imageKeys[i] + " or " + (this.imageKeys[i + 9][0].toUpperCase()) + this.imageKeys[i + 9].slice(1); + delete this.submitCB; + cb(); + return this.updateCount(); } }, - addTooltips16: function(nodes) { - var i, j, key, len, node, ref; - ref = this.imageKeys16; - for (i = j = 0, len = ref.length; j < len; i = ++j) { - key = ref[i]; - if (i % 4 < this.width && (node = nodes[nodes.length - (4 - Math.floor(i / 4)) * this.width + (i % 4)])) { - node.title = "" + (key[0].toUpperCase()) + key.slice(1); - } + save: function(captcha) { + var cb; + if ((cb = this.submitCB)) { + this.abort(); + cb(captcha); + return; } + this.captchas.push(captcha); + this.captchas.sort(function(a, b) { + return a.timeout - b.timeout; + }); + return this.count(); }, - checkForm: function(e) { - var checkbox, j, len, n, ref; - n = 0; - ref = this.images; - for (j = 0, len = ref.length; j < len; j++) { - checkbox = ref[j]; - if (checkbox.checked) { - n++; + clear: function() { + var captcha, i, j, len, now, ref; + if (this.captchas.length) { + now = Date.now(); + ref = this.captchas; + for (i = j = 0, len = ref.length; j < len; i = ++j) { + captcha = ref[i]; + if (captcha.timeout > now) { + break; + } + } + if (i) { + this.captchas = this.captchas.slice(i); + return this.count(); } } - if (n === 0) { - return e.preventDefault(); - } - }, - keybinds: function(e) { - var dx, i, img, key, last, n, reload, verify, w, x; - if (!(this.images && doc.contains(this.images[0]))) { - return; - } - n = this.images.length; - w = this.width; - last = n + w - 1; - reload = $('#recaptcha-reload-button, .fbc-button-reload'); - verify = $('#recaptcha-verify-button, .fbc-button-verify > input'); - x = this.images.indexOf(d.activeElement); - if (x < 0) { - x = d.activeElement === verify ? last : n; - } - key = Keybinds.keyCode(e); - if (!this.noscript && key === 'Space' && x < n) { - this.images[x].click(); - } else if (n === 9 && (i = this.imageKeys.indexOf(key)) >= 0) { - this.images[i % 9].click(); - verify.focus(); - } else if (n !== 9 && (i = this.imageKeys16.indexOf(key)) >= 0 && i % 4 < w && (img = this.images[n - (4 - Math.floor(i / 4)) * w + (i % 4)])) { - img.click(); - verify.focus(); - } else if (dx = { - 'Up': n, - 'Down': w, - 'Left': last, - 'Right': 1 - }[key]) { - x = (x + dx) % (n + w); - if ((n < x && x < last)) { - x = dx === last ? n : last; - } - (this.images[x] || (x === n ? reload : void 0) || (x === last ? verify : void 0)).focus(); - } else { - return; + }, + count: function() { + clearTimeout(this.timer); + if (this.captchas.length) { + this.timer = setTimeout(this.clear.bind(this), this.captchas[0].timeout - Date.now()); } - e.preventDefault(); - return e.stopPropagation(); + return this.updateCount(); + }, + updateCount: function() { + return $.event('CaptchaCount', this.captchas.length); } }; @@ -18367,39 +23361,27 @@ Captcha = {}; (function() { Captcha.replace = { init: function() { - if (!(d.cookie.indexOf('pass_enabled=1') < 0)) { - return; - } - if (location.hostname === 'sys.4chan.org' && /[?&]altc\b/.test(location.search) && Main.jsEnabled) { - $.onExists(doc, 'script[src="//www.google.com/recaptcha/api/js/recaptcha_ajax.js"]', function() { - $.global(function() { - return window.el.onload = null; - }); - return Captcha.v1.create(); - }); - return; - } - if (((Conf['Use Recaptcha v1'] && location.hostname === 'boards.4chan.org') || (Conf['Use Recaptcha v1 in Reports'] && location.hostname === 'sys.4chan.org')) && Main.jsEnabled) { - $.ready(Captcha.replace.v1); + var ref; + if (!(g.SITE.software === 'yotsuba' && d.cookie.indexOf('pass_enabled=1') < 0)) { return; } if (Conf['Force Noscript Captcha'] && Main.jsEnabled) { $.ready(Captcha.replace.noscript); return; } - if (Conf['captchaLanguage'].trim() || Conf['Captcha Fixes']) { - if (location.hostname === 'boards.4chan.org') { + if (Conf['captchaLanguage'].trim()) { + if ((ref = location.hostname) === 'boards.4chan.org' || ref === 'boards.4channel.org') { return $.onExists(doc, '#captchaFormPart', function(node) { - return $.onExists(node, 'iframe', Captcha.replace.iframe); + return $.onExists(node, 'iframe[src^="https://www.google.com/recaptcha/"]', Captcha.replace.iframe); }); } else { - return $.onExists(doc, 'iframe', Captcha.replace.iframe); + return $.onExists(doc, 'iframe[src^="https://www.google.com/recaptcha/"]', Captcha.replace.iframe); } } }, noscript: function() { var insert, noscript, original, span, toggle; - if (!((original = $('#g-recaptcha, #captchaContainerAlt')) && (noscript = $('noscript')))) { + if (!((original = $('#g-recaptcha')) && (noscript = $('noscript', original.parentNode)))) { return; } span = $.el('span', { @@ -18409,7 +23391,7 @@ Captcha = {}; $.rm(original); insert = function() { span.innerHTML = noscript.textContent; - return Captcha.replace.iframe($('iframe', span)); + return Captcha.replace.iframe($('iframe[src^="https://www.google.com/recaptcha/"]', span)); }; if ((toggle = $('#togglePostFormLink a, #form-link'))) { return $.on(toggle, 'click', insert); @@ -18417,25 +23399,6 @@ Captcha = {}; return insert(); } }, - v1: function() { - var form, link; - if (!$.id('g-recaptcha')) { - return; - } - Captcha.v1.replace(); - if ((link = $.id('form-link'))) { - return $.on(link, 'click', function() { - return Captcha.v1.create(); - }); - } else if (location.hostname === 'boards.4chan.org') { - form = $.id('postForm'); - return form.addEventListener('focus', (function() { - return Captcha.v1.create(); - }), true); - } else { - return Captcha.v1.create(); - } - }, iframe: function(iframe) { var lang, src; if ((lang = Conf['captchaLanguage'].trim())) { @@ -18444,385 +23407,129 @@ Captcha = {}; iframe.src = src; } } - return Captcha.replace.autocopy(iframe); - }, - autocopy: function(iframe) { - if (!(Conf['Captcha Fixes'] && /^https:\/\/www\.google\.com\/recaptcha\/api\/fallback\?/.test(iframe.src))) { - return; - } - return new Connection(iframe, 'https://www.google.com', { - working: function() { - var ref, ref1; - if ((ref = $.id('qr')) != null ? ref.contains(iframe) : void 0) { - return (ref1 = $('#qr .captcha-container textarea')) != null ? ref1.parentNode.hidden = true : void 0; - } - }, - token: function(token) { - var node, textarea; - node = iframe; - while ((node = node.parentNode)) { - if ((textarea = $('textarea', node))) { - break; - } - } - textarea.value = token; - return $.event('input', null, textarea); - } - }); } }; }).call(this); (function() { - Captcha.v1 = { - blank: "data:image/svg+xml,", + Captcha.t = { init: function() { - var imgContainer, input; - if (d.cookie.indexOf('pass_enabled=1') >= 0) { - return; - } - if (!(this.isEnabled = !!$('#g-recaptcha, #captchaContainerAlt'))) { - return; - } - imgContainer = $.el('div', { - className: 'captcha-img', - title: 'Reload reCAPTCHA' - }); - $.extend(imgContainer, { - innerHTML: "" - }); - input = $.el('input', { - className: 'captcha-input field', - title: 'Verification', - autocomplete: 'off', - spellcheck: false - }); - this.nodes = { - img: imgContainer.firstChild, - input: input - }; - $.on(input, 'blur', QR.focusout); - $.on(input, 'focus', QR.focusin); - $.on(input, 'keydown', QR.captcha.keydown.bind(QR.captcha)); - $.on(this.nodes.img.parentNode, 'click', QR.captcha.reload.bind(QR.captcha)); - $.addClass(QR.nodes.el, 'has-captcha', 'captcha-v1'); - $.after(QR.nodes.com.parentNode, [imgContainer, input]); - this.captchas = []; - $.get('captchas', [], function(arg) { - var captchas; - captchas = arg.captchas; - QR.captcha.sync(captchas); - return QR.captcha.clear(); - }); - $.sync('captchas', this.sync); - this.replace(); - this.beforeSetup(); - if (Conf['Auto-load captcha']) { - this.setup(); - } - new MutationObserver(this.afterSetup).observe($.id('captchaContainerAlt'), { - childList: true - }); - return this.afterSetup(); - }, - replace: function() { - var container, old; - if (this.script) { - return; - } - if (!(this.script = $('script[src="//www.google.com/recaptcha/api/js/recaptcha_ajax.js"]', d.head))) { - this.script = $.el('script', { - src: '//www.google.com/recaptcha/api/js/recaptcha_ajax.js' - }); - $.add(d.head, this.script); - } - if (old = $.id('g-recaptcha')) { - container = $.el('div', { - id: 'captchaContainerAlt' - }); - return $.replace(old, container); - } - }, - create: function() { - var cont, lang; - cont = $.id('captchaContainerAlt'); - if (this.occupied) { - return; - } - this.occupied = true; - if ((lang = Conf['captchaLanguage'].trim())) { - cont.dataset.lang = lang; - } - $.onExists(cont, '#recaptcha_image', function(image) { - return $.on(image, 'click', function() { - if ($.id('recaptcha_challenge_image')) { - return $.global(function() { - return window.Recaptcha.reload(); - }); - } - }); - }); - $.onExists(cont, '#recaptcha_response_field', function(field) { - $.on(field, 'keydown', function(e) { - if (e.keyCode === 8 && !field.value) { - return $.global(function() { - return window.Recaptcha.reload(); - }); - } - }); - if (location.hostname === 'sys.4chan.org') { - return field.focus(); - } - }); - return $.global(function() { - var container, options, script; - container = document.getElementById('captchaContainerAlt'); - options = { - theme: 'clean', - tabindex: { - "boards.4chan.org": 5 - }[location.hostname], - lang: container.dataset.lang - }; - if (window.Recaptcha) { - return window.Recaptcha.create('6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc', container, options); - } else { - script = document.head.querySelector('script[src="//www.google.com/recaptcha/api/js/recaptcha_ajax.js"]'); - return script.addEventListener('load', function() { - return window.Recaptcha.create('6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc', container, options); - }, false); - } - }); - }, - cb: { - focus: function() { - return QR.captcha.setup(false, true); - } - }, - beforeSetup: function() { - var img, input, ref; - ref = this.nodes, img = ref.img, input = ref.input; - img.parentNode.hidden = true; - img.src = this.blank; - input.value = ''; - input.placeholder = 'Focus to load reCAPTCHA'; - this.count(); - return $.on(input, 'focus click', this.cb.focus); - }, - needed: function() { - var captchaCount, postsCount; - captchaCount = this.captchas.length; - if (QR.req) { - captchaCount++; - } - postsCount = QR.posts.length; - if (postsCount === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) { - postsCount = 0; - } - return captchaCount < postsCount; - }, - onNewPost: function() {}, - onPostChange: function() {}, - setup: function(focus, force) { - if (!(this.isEnabled && (force || this.needed()))) { - return; - } - this.create(); - if (focus) { - $.addClass(QR.nodes.el, 'focus'); - return this.nodes.input.focus(); - } - }, - afterSetup: function() { - var challenge, img, input, ref, setLifetime; - if (!(challenge = $.id('recaptcha_challenge_field_holder'))) { - return; - } - if (challenge === QR.captcha.nodes.challenge) { - return; - } - setLifetime = function(e) { - return QR.captcha.lifetime = e.detail; - }; - $.on(window, 'captcha:timeout', setLifetime); - $.global(function() { - return window.dispatchEvent(new CustomEvent('captcha:timeout', { - detail: window.RecaptchaState.timeout - })); - }); - $.off(window, 'captcha:timeout', setLifetime); - ref = QR.captcha.nodes, img = ref.img, input = ref.input; - img.parentNode.hidden = false; - input.placeholder = 'Verification'; - QR.captcha.count(); - $.off(input, 'focus click', QR.captcha.cb.focus); - QR.captcha.nodes.challenge = challenge; - new MutationObserver(QR.captcha.load.bind(QR.captcha)).observe(challenge, { - childList: true, - subtree: true, - attributes: true - }); - QR.captcha.load(); - if (QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight) { - QR.nodes.el.style.top = null; - return QR.nodes.el.style.bottom = '0px'; + var root; + if (d.cookie.indexOf('pass_enabled=1') >= 0) { + return; } - }, - destroy: function() { - if (!this.script) { + if (!(this.isEnabled = !!$('#t-root') || !$.id('postForm'))) { return; } - $.global(function() { - return window.Recaptcha.destroy(); + root = $.el('div', { + className: 'captcha-root' }); - delete this.occupied; - if (this.nodes) { - return this.beforeSetup(); - } - }, - sync: function(captchas) { - if (captchas == null) { - captchas = []; - } - QR.captcha.captchas = captchas; - return QR.captcha.count(); + this.nodes = { + root: root + }; + $.addClass(QR.nodes.el, 'has-captcha', 'captcha-t'); + return $.after(QR.nodes.com.parentNode, root); }, - getOne: function() { - var captcha, challenge, response, timeout; - this.clear(); - if (captcha = this.captchas.shift()) { - this.count(); - $.set('captchas', this.captchas); - return captcha; + moreNeeded: function() {}, + getThread: function() { + var boardID, threadID; + boardID = g.BOARD.ID; + if (QR.posts[0].thread === 'new') { + threadID = '0'; } else { - challenge = this.nodes.img.alt; - timeout = this.timeout; - if (/\S/.test(response = this.nodes.input.value)) { - this.destroy(); - return { - challenge: challenge, - response: response, - timeout: timeout - }; - } else { - return null; - } - } - }, - save: function() { - var response; - if (!/\S/.test(response = this.nodes.input.value)) { - return; + threadID = '' + QR.posts[0].thread; } - this.nodes.input.value = ''; - this.captchas.push({ - challenge: this.nodes.img.alt, - response: response, - timeout: this.timeout - }); - this.captchas.sort(function(a, b) { - return a.timeout - b.timeout; - }); - this.count(); - this.destroy(); - this.setup(false, true); - return $.set('captchas', this.captchas); + return { + boardID: boardID, + threadID: threadID + }; }, - clear: function() { - var captcha, i, j, len, now, ref; - if (!this.captchas.length) { + setup: function(focus) { + if (!this.isEnabled) { return; } - $.forceSync('captchas'); - now = Date.now(); - ref = this.captchas; - for (i = j = 0, len = ref.length; j < len; i = ++j) { - captcha = ref[i]; - if (captcha.timeout > now) { - break; - } + if (!this.nodes.container) { + this.nodes.container = $.el('div', { + className: 'captcha-container' + }); + $.prepend(this.nodes.root, this.nodes.container); + Captcha.t.currentThread = Captcha.t.getThread(); + $.global(function() { + var el; + el = document.querySelector('#qr .captcha-container'); + window.TCaptcha.init(el, this.boardID, +this.threadID); + return window.TCaptcha.setErrorCb(function(err) { + return window.dispatchEvent(new CustomEvent('CreateNotification', { + detail: { + type: 'warning', + content: '' + err + } + })); + }); + }, Captcha.t.currentThread); } - if (!i) { - return; + if (focus) { + return $('#t-resp').focus(); } - this.captchas = this.captchas.slice(i); - this.count(); - return $.set('captchas', this.captchas); }, - load: function() { - var challenge, challenge_image; - if ($('#captchaContainerAlt[class~="recaptcha_is_showing_audio"]')) { - this.nodes.img.src = this.blank; + destroy: function() { + if (!(this.isEnabled && this.nodes.container)) { return; } - if (!this.nodes.challenge.firstChild) { + $.global(function() { + return window.TCaptcha.destroy(); + }); + $.rm(this.nodes.container); + return delete this.nodes.container; + }, + updateThread: function() { + var boardID, newThread, ref, threadID; + if (!this.isEnabled) { return; } - if (!(challenge_image = $.id('recaptcha_challenge_image'))) { - return; + ref = Captcha.t.currentThread || {}, boardID = ref.boardID, threadID = ref.threadID; + newThread = Captcha.t.getThread(); + if (!(newThread.boardID === boardID && newThread.threadID === threadID)) { + Captcha.t.destroy(); + return Captcha.t.setup(); } - this.timeout = Date.now() + this.lifetime * $.SECOND - $.MINUTE; - challenge = this.nodes.challenge.firstChild.value; - this.nodes.img.alt = challenge; - this.nodes.img.src = challenge_image.src; - this.nodes.input.value = ''; - return this.clear(); }, - count: function() { - var count, placeholder; - count = this.captchas ? this.captchas.length : 0; - placeholder = this.nodes.input.placeholder.replace(/\ \(.*\)$/, ''); - placeholder += (function() { - switch (count) { - case 0: - if (placeholder === 'Verification') { - return ' (Shift + Enter to cache)'; - } else { - return ''; - } - break; - case 1: - return ' (1 cached captcha)'; - default: - return " (" + count + " cached captchas)"; + getOne: function() { + var el, i, key, len, ref, response; + response = {}; + if (this.nodes.container) { + ref = ['t-response', 't-challenge']; + for (i = 0, len = ref.length; i < len; i++) { + key = ref[i]; + response[key] = $("[name='" + key + "']", this.nodes.container).value; } - })(); - this.nodes.input.placeholder = placeholder; - this.nodes.input.alt = count; - clearTimeout(this.timer); - if (count) { - return this.timer = setTimeout(this.clear.bind(this), this.captchas[0].timeout - Date.now()); } - }, - reload: function(focus) { - $.global(function() { - if (window.Recaptcha.type === 'image') { - window.Recaptcha.reload(); - } else { - window.Recaptcha.switch_type('image'); - } - return window.Recaptcha.should_focus = false; - }); - if (focus) { - return this.nodes.input.focus(); + if (!response['t-response'] && !((el = $('#t-msg')) && /Verification not required/i.test(el.textContent))) { + response = null; } + return response; }, - keydown: function(e) { - if (e.keyCode === 8 && !this.nodes.input.value) { - this.reload(); - } else if (e.keyCode === 13 && e.shiftKey) { - this.save(); - } else { + setUsed: function() { + if (!this.isEnabled) { return; } - return e.preventDefault(); + if (this.nodes.container) { + return $.global(function() { + return window.TCaptcha.clearChallenge(); + }); + } + }, + occupied: function() { + return !!this.nodes.container; } }; }).call(this); (function() { + var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + Captcha.v2 = { lifetime: 2 * $.MINUTE, init: function() { @@ -18830,25 +23537,18 @@ Captcha = {}; if (d.cookie.indexOf('pass_enabled=1') >= 0) { return; } - if (!(this.isEnabled = !!$('#g-recaptcha, #captchaContainerAlt, #captcha-forced-noscript'))) { + if (!(this.isEnabled = !!$('#g-recaptcha, #captcha-forced-noscript') || !$.id('postForm'))) { return; } if ((this.noscript = Conf['Force Noscript Captcha'] || !Main.jsEnabled)) { $.addClass(QR.nodes.el, 'noscript-captcha'); } - this.captchas = []; - $.get('captchas', [], function(arg) { - var captchas; - captchas = arg.captchas; - return QR.captcha.sync(captchas); - }); - $.sync('captchas', this.sync.bind(this)); + Captcha.cache.init(); + $.on(d, 'CaptchaCount', this.count.bind(this)); root = $.el('div', { className: 'captcha-root' }); - $.extend(root, { - innerHTML: "
              " - }); + $.extend(root, {innerHTML: "
              "}); counter = $('.captcha-counter > a', root); this.nodes = { root: root, @@ -18877,7 +23577,7 @@ Captcha = {}; })(this)); }, timeouts: {}, - postsCount: 0, + prevNeeded: 0, noscriptURL: function() { var lang, url; url = 'https://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc'; @@ -18886,28 +23586,17 @@ Captcha = {}; } return url; }, - needed: function() { - var captchaCount; - captchaCount = this.captchas.length; - if (QR.req) { - captchaCount++; - } - this.postsCount = QR.posts.length; - if (this.postsCount === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) { - this.postsCount = 0; - } - return captchaCount < this.postsCount; - }, - onNewPost: function() { - return this.setup(); - }, - onPostChange: function() { - if (this.postsCount === 0) { - this.setup(); - } - if (QR.posts.length === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) { - return this.postsCount = 0; - } + moreNeeded: function() { + return $.queueTask((function(_this) { + return function() { + var needed; + needed = Captcha.cache.needed(); + if (needed && !_this.prevNeeded) { + _this.setup(QR.cooldown.auto && d.activeElement === QR.nodes.status); + } + return _this.prevNeeded = needed; + }; + })(this)); }, toggle: function() { if (this.nodes.container && !this.timeouts.destroy) { @@ -18917,7 +23606,7 @@ Captcha = {}; } }, setup: function(focus, force) { - if (!(this.isEnabled && (this.needed() || force))) { + if (!(this.isEnabled && (Captcha.cache.needed() || force))) { return; } if (focus) { @@ -18933,7 +23622,7 @@ Captcha = {}; $.queueTask((function(_this) { return function() { var iframe; - if (_this.nodes.container && d.activeElement === _this.nodes.counter && (iframe = $('iframe', _this.nodes.container))) { + if (_this.nodes.container && d.activeElement === _this.nodes.counter && (iframe = $('iframe[src^="https://www.google.com/recaptcha/"]', _this.nodes.container))) { iframe.focus(); return QR.focus(); } @@ -18959,6 +23648,7 @@ Captcha = {}; var div, iframe, textarea; iframe = $.el('iframe', { id: 'qr-captcha-iframe', + scrolling: 'no', src: this.noscriptURL() }); div = $.el('div'); @@ -18968,14 +23658,14 @@ Captcha = {}; }, setupJS: function() { return $.global(function() { - var cbNative, render; + var cbNative, render, script; render = function() { var classList, container; classList = document.documentElement.classList; container = document.querySelector('#qr .captcha-container'); return container.dataset.widgetID = window.grecaptcha.render(container, { sitekey: '6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc', - theme: classList.contains('tomorrow') || classList.contains('dark-captcha') ? 'dark' : 'light', + theme: classList.contains('tomorrow') || classList.contains('spooky') || classList.contains('dark-captcha') ? 'dark' : 'light', callback: function(response) { return window.dispatchEvent(new CustomEvent('captcha:success', { detail: response @@ -18987,21 +23677,26 @@ Captcha = {}; return render(); } else { cbNative = window.onRecaptchaLoaded; - return window.onRecaptchaLoaded = function() { + window.onRecaptchaLoaded = function() { render(); return cbNative(); }; + if (!document.head.querySelector('script[src^="https://www.google.com/recaptcha/api.js"]')) { + script = document.createElement('script'); + script.src = 'https://www.google.com/recaptcha/api.js?onload=onRecaptchaLoaded&render=explicit'; + return document.head.appendChild(script); + } } }); }, afterSetup: function(mutations) { - var iframe, j, k, len, len1, mutation, node, ref, textarea; - for (j = 0, len = mutations.length; j < len; j++) { - mutation = mutations[j]; + var i, iframe, j, len, len1, mutation, node, ref, textarea; + for (i = 0, len = mutations.length; i < len; i++) { + mutation = mutations[i]; ref = mutation.addedNodes; - for (k = 0, len1 = ref.length; k < len1; k++) { - node = ref[k]; - if ((iframe = $.x('./descendant-or-self::iframe', node))) { + for (j = 0, len1 = ref.length; j < len1; j++) { + node = ref[j]; + if ((iframe = $.x('./descendant-or-self::iframe[starts-with(@src, "https://www.google.com/recaptcha/")]', node))) { this.setupIFrame(iframe); } if ((textarea = $.x('./descendant-or-self::textarea', node))) { @@ -19011,6 +23706,7 @@ Captcha = {}; } }, setupIFrame: function(iframe) { + var ref, ref1; if (!doc.contains(iframe)) { return; } @@ -19021,15 +23717,15 @@ Captcha = {}; if (d.activeElement === this.nodes.counter) { iframe.focus(); } - return $.global(function() { - var f; - f = document.querySelector('#qr iframe'); - return f.focus = f.blur = function() {}; - }); + if (((ref = $.engine) === 'blink' || ref === 'edge') && (ref1 = iframe.parentNode, indexOf.call($$('#qr .captcha-container > div > div:first-of-type'), ref1) >= 0)) { + return $.on(iframe.parentNode, 'scroll', function() { + return this.scrollTop = 0; + }); + } }, fixQRPosition: function() { if (QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight) { - QR.nodes.el.style.top = null; + QR.nodes.el.style.top = ''; return QR.nodes.el.style.bottom = '0px'; } }, @@ -19041,58 +23737,32 @@ Captcha = {}; })(this)); }, destroy: function() { - var garbage, i, ins, node, ref; if (!this.isEnabled) { return; } delete this.timeouts.destroy; $.rmClass(QR.nodes.el, 'captcha-open'); if (this.nodes.container) { + $.global(function() { + var container; + container = document.querySelector('#qr .captcha-container'); + return window.grecaptcha.reset(container.dataset.widgetID); + }); $.rm(this.nodes.container); + return delete this.nodes.container; } - delete this.nodes.container; - garbage = $.X('//iframe[starts-with(@src, "https://www.google.com/recaptcha/api2/frame")]/ancestor-or-self::*[parent::body]'); - i = 0; - while (node = garbage.snapshotItem(i++)) { - if (((ref = (ins = node.nextSibling)) != null ? ref.nodeName : void 0) === 'INS') { - $.rm(ins); - } - $.rm(node); - } - }, - sync: function(captchas) { - if (captchas == null) { - captchas = []; - } - this.captchas = captchas; - this.clear(); - return this.count(); }, - getOne: function() { - var captcha; - this.clear(); - if ((captcha = this.captchas.shift())) { - $.set('captchas', this.captchas); - this.count(); - return captcha; - } else { - return null; - } + getOne: function(isReply) { + return Captcha.cache.getOne(isReply); }, save: function(pasted, token) { var base, focus, ref; - $.forceSync('captchas'); - this.captchas.push({ + Captcha.cache.save({ response: token || $('textarea', this.nodes.container).value, timeout: Date.now() + this.lifetime }); - this.captchas.sort(function(a, b) { - return a.timeout - b.timeout; - }); - $.set('captchas', this.captchas); - this.count(); focus = ((ref = d.activeElement) != null ? ref.nodeName : void 0) === 'IFRAME' && /https?:\/\/www\.google\.com\/recaptcha\//.test(d.activeElement.src); - if (this.needed()) { + if (Captcha.cache.needed()) { if (focus) { if (QR.cooldown.auto || Conf['Post on Captcha Completion']) { this.nodes.counter.focus(); @@ -19117,34 +23787,12 @@ Captcha = {}; return QR.submit(); } }, - clear: function() { - var captcha, i, j, len, now, ref; - if (!this.captchas.length) { - return; - } - $.forceSync('captchas'); - now = Date.now(); - ref = this.captchas; - for (i = j = 0, len = ref.length; j < len; i = ++j) { - captcha = ref[i]; - if (captcha.timeout > now) { - break; - } - } - if (!i) { - return; - } - this.captchas = this.captchas.slice(i); - this.count(); - $.set('captchas', this.captchas); - return this.setup(d.activeElement === QR.nodes.status); - }, count: function() { - this.nodes.counter.textContent = "Captchas: " + this.captchas.length; - clearTimeout(this.timeouts.clear); - if (this.captchas.length) { - return this.timeouts.clear = setTimeout(this.clear.bind(this), this.captchas[0].timeout - Date.now()); - } + var count, loading; + count = Captcha.cache.getCount(); + loading = Captcha.cache.submitCB ? '...' : ''; + this.nodes.counter.textContent = "Captchas: " + count + loading; + return this.moreNeeded(); }, reload: function() { if ($('iframe[src^="https://www.google.com/recaptcha/api/fallback?"]', this.nodes.container)) { @@ -19157,6 +23805,9 @@ Captcha = {}; return window.grecaptcha.reset(container.dataset.widgetID); }); } + }, + occupied: function() { + return !!this.nodes.container && !this.timeouts.destroy; } }; @@ -19167,7 +23818,7 @@ PassLink = (function() { PassLink = { init: function() { - if (!Conf['Pass Link']) { + if (!(g.SITE.software === 'yotsuba' && Conf['Pass Link'])) { return; } return Main.ready(this.ready); @@ -19180,11 +23831,9 @@ PassLink = (function() { passLink = $.el('span', { className: 'brackets-wrap pass-link-container' }); - $.extend(passLink, { - innerHTML: "4chan Pass" - }); + $.extend(passLink, {innerHTML: "4chan Pass"}); $.on(passLink.firstElementChild, 'click', function() { - return window.open('//sys.4chan.org/auth', Date.now(), 'width=500,height=280,toolbar=0'); + return window.open("//sys." + (location.hostname.split('.')[1]) + ".org/auth", Date.now(), 'width=500,height=280,toolbar=0'); }); return $.before(styleSelector.previousSibling, [passLink, $.tn('\u00A0\u00A0')]); } @@ -19194,6 +23843,52 @@ PassLink = (function() { }).call(this); +PostRedirect = (function() { + var PostRedirect; + + PostRedirect = { + init: function() { + return $.on(d, 'QRPostSuccessful', (function(_this) { + return function(e) { + if (!e.detail.redirect) { + return; + } + _this.event = e; + _this.delays = 0; + return $.queueTask(function() { + if (e === _this.event && _this.delays === 0) { + return location.href = e.detail.redirect; + } + }); + }; + })(this)); + }, + delays: 0, + delay: function() { + var e; + if (!this.event) { + return null; + } + e = this.event; + this.delays++; + return (function(_this) { + return function() { + if (e !== _this.event) { + return; + } + _this.delays--; + if (_this.delays === 0) { + return location.href = e.detail.redirect; + } + }; + })(this); + } + }; + + return PostRedirect; + +}).call(this); + PostSuccessful = (function() { var PostSuccessful; @@ -19232,8 +23927,8 @@ QR = (function() { slice = [].slice; QR = { - mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/vnd.adobe.flash.movie', 'application/x-shockwave-flash', 'video/webm'], - validExtension: /\.(jpe?g|png|gif|pdf|swf|webm)$/i, + mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/vnd.adobe.flash.movie', 'application/x-shockwave-flash', 'video/webm', 'video/mp4'], + validExtension: /\.(jpe?g|png|gif|pdf|swf|webm|mp4)$/i, typeFromExtension: { 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', @@ -19241,7 +23936,8 @@ QR = (function() { 'gif': 'image/gif', 'pdf': 'application/pdf', 'swf': 'application/vnd.adobe.flash.movie', - 'webm': 'video/webm' + 'webm': 'video/webm', + 'mp4': 'video/mp4' }, extensionFromType: { 'image/jpeg': 'jpg', @@ -19250,20 +23946,18 @@ QR = (function() { 'application/pdf': 'pdf', 'application/vnd.adobe.flash.movie': 'swf', 'application/x-shockwave-flash': 'swf', - 'video/webm': 'webm' + 'video/webm': 'webm', + 'video/mp4': 'mp4' }, init: function() { - var sc, version; + var sc; if (!Conf['Quick Reply']) { return; } this.posts = []; - if (g.VIEW === 'archive') { - return; - } - version = Conf['Use Recaptcha v1'] && Main.jsEnabled ? 'v1' : 'v2'; - this.captcha = Captcha[version]; - $.on(d, '4chanXInitFinished', this.initReady); + $.on(d, '4chanXInitFinished', function() { + return BoardConfig.ready(QR.initReady); + }); Callbacks.Post.push({ name: 'Quick Reply', cb: this.node @@ -19288,30 +23982,43 @@ QR = (function() { return Header.addShortcut('qr', sc, 540); }, initReady: function() { - var link, linkBot, navLinksBot, origToggle; - $.off(d, '4chanXInitFinished', this.initReady); - QR.postingIsEnabled = !!$.id('postForm'); - if (!QR.postingIsEnabled) { - return; + var captchaVersion, config, link, linkBot, navLinksBot, origToggle, prop; + captchaVersion = $('#g-recaptcha, #captcha-forced-noscript') ? 'v2' : 't'; + QR.captcha = Captcha[captchaVersion]; + QR.postingIsEnabled = true; + config = g.BOARD.config; + prop = function(key, def) { + var ref; + return +((ref = config[key]) != null ? ref : def); + }; + QR.min_width = prop('min_image_width', 1); + QR.min_height = prop('min_image_height', 1); + QR.max_width = QR.max_height = 10000; + QR.max_size = prop('max_filesize', 4194304); + QR.max_size_video = prop('max_webm_filesize', QR.max_size); + QR.max_comment = prop('max_comment_chars', 2000); + QR.max_width_video = QR.max_height_video = 2048; + QR.max_duration_video = prop('max_webm_duration', 120); + QR.forcedAnon = !!config.forced_anon; + QR.spoiler = !!config.spoilers; + if ((origToggle = $.id('togglePostFormLink'))) { + link = $.el('h1', { + className: "qr-link-container" + }); + $.extend(link, {innerHTML: "" + ((g.VIEW === "thread") ? "Reply to Thread" : "Start a Thread") + ""}); + QR.link = link.firstElementChild; + $.on(link.firstChild, 'click', function() { + QR.open(); + return QR.nodes.com.focus(); + }); + $.before(origToggle, link); + origToggle.firstElementChild.textContent = 'Original Form'; } - link = $.el('h1', { - className: "qr-link-container" - }); - $.extend(link, { - innerHTML: "" + ((g.VIEW === "thread") ? "Reply to Thread" : "Start a Thread") + "" - }); - QR.link = link.firstElementChild; - $.on(link.firstChild, 'click', function() { - QR.open(); - return QR.nodes.com.focus(); - }); if (g.VIEW === 'thread') { linkBot = $.el('div', { className: "brackets-wrap qr-link-container-bottom" }); - $.extend(linkBot, { - innerHTML: "Reply to Thread" - }); + $.extend(linkBot, {innerHTML: "Reply to Thread"}); $.on(linkBot.firstElementChild, 'click', function() { QR.open(); return QR.nodes.com.focus(); @@ -19320,16 +24027,14 @@ QR = (function() { $.prepend(navLinksBot, linkBot); } } - origToggle = $.id('togglePostFormLink'); - $.before(origToggle, link); - origToggle.firstElementChild.textContent = 'Original Form'; $.on(d, 'QRGetFile', QR.getFile); + $.on(d, 'QRDrawFile', QR.drawFile); $.on(d, 'QRSetFile', QR.setFile); $.on(d, 'paste', QR.paste); $.on(d, 'dragover', QR.dragOver); $.on(d, 'drop', QR.dropFile); $.on(d, 'dragstart dragend', QR.drag); - $.on(d, 'IndexRefresh', QR.generatePostableThreadsList); + $.on(d, 'IndexRefreshInternal', QR.generatePostableThreadsList); $.on(d, 'ThreadUpdate', QR.statusCheck); if (!Conf['Persistent QR']) { return; @@ -19345,7 +24050,7 @@ QR = (function() { return; } thread = QR.posts[0].thread; - if (thread !== 'new' && g.threads[g.BOARD + "." + thread].isDead) { + if (thread !== 'new' && g.threads.get(g.BOARD + "." + thread).isDead) { return QR.abort(); } else { return QR.status(); @@ -19368,8 +24073,8 @@ QR = (function() { } else { try { QR.dialog(); - } catch (_error) { - err = _error; + } catch (error) { + err = error; delete QR.nodes; Main.handleErrors({ message: 'Quick Reply dialog creation crashed.', @@ -19388,7 +24093,7 @@ QR = (function() { } QR.nodes.el.hidden = true; QR.cleanNotifications(); - d.activeElement.blur(); + QR.blur(); $.rmClass(QR.nodes.el, 'dump'); $.addClass(QR.shortcut, 'disabled'); new QR.post(true); @@ -19417,7 +24122,7 @@ QR = (function() { }); }, hide: function() { - d.activeElement.blur(); + QR.blur(); $.addClass(QR.nodes.el, 'autohide'); return QR.nodes.autohide.checked = true; }, @@ -19432,6 +24137,11 @@ QR = (function() { return QR.unhide(); } }, + blur: function() { + if (QR.nodes.el.contains(d.activeElement)) { + return d.activeElement.blur(); + } + }, toggleSJIS: function(e) { e.preventDefault(); Conf['sjisPreview'] = !Conf['sjisPreview']; @@ -19449,6 +24159,16 @@ QR = (function() { texPreviewHide: function() { return $.rmClass(QR.nodes.el, 'tex-preview'); }, + addPost: function() { + var wasOpen; + wasOpen = QR.nodes && !QR.nodes.el.hidden; + QR.open(); + if (wasOpen) { + $.addClass(QR.nodes.el, 'dump'); + new QR.post(true); + } + return QR.nodes.com.focus(); + }, setCustomCooldown: function(enabled) { Conf['customCooldownEnabled'] = enabled; QR.cooldown.customCooldown = enabled; @@ -19456,7 +24176,7 @@ QR = (function() { }, toggleCustomCooldown: function() { var enabled; - enabled = $.hasClass(this, 'disabled'); + enabled = $.hasClass(QR.nodes.customCooldown, 'disabled'); QR.setCustomCooldown(enabled); return $.set('customCooldownEnabled', enabled); }, @@ -19496,6 +24216,9 @@ QR = (function() { } } }, + connectionError: function() { + return $.el('span', {innerHTML: "Connection error while posting. [More info]"}); + }, notifications: [], cleanNotifications: function() { var j, len, notification, ref; @@ -19512,7 +24235,7 @@ QR = (function() { return; } thread = QR.posts[0].thread; - if (thread !== 'new' && g.threads[g.BOARD + "." + thread].isDead) { + if (thread !== 'new' && g.threads.get(g.BOARD + "." + thread).isDead) { value = 'Dead'; disabled = true; QR.cooldown.auto = false; @@ -19533,7 +24256,7 @@ QR = (function() { } }, quote: function(e) { - var ancestor, caretPos, com, frag, insideCode, j, k, l, len, len1, len2, len3, len4, len5, n, node, o, post, q, range, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, sel, text, thread; + var ancestor, base, caretPos, com, frag, i, insideCode, j, k, l, len, len1, len2, len3, n, node, o, post, postRange, range, ref, ref1, ref2, ref3, ref4, ref5, ref6, root, sel, text, thread, wasOnlyQuotes; if (e != null) { e.preventDefault(); } @@ -19542,81 +24265,146 @@ QR = (function() { } sel = d.getSelection(); post = Get.postFromNode(this); + root = post.nodes.root; + postRange = new Range(); + postRange.selectNode(root); text = post.board.ID === g.BOARD.ID ? ">>" + post + "\n" : ">>>/" + post.board + "/" + post + "\n"; - if (sel.toString().trim() && post === Get.postFromNode(sel.anchorNode)) { - range = sel.getRangeAt(0); - frag = range.cloneContents(); - ancestor = range.commonAncestorContainer; - if ($.x('ancestor-or-self::*[self::s or contains(@class,"removed-spoiler")]', ancestor)) { - $.prepend(frag, $.tn('[spoiler]')); - $.add(frag, $.tn('[/spoiler]')); - } - if (insideCode = $.x('ancestor-or-self::pre[contains(@class,"prettyprint")]', ancestor)) { - $.prepend(frag, $.tn('[code]')); - $.add(frag, $.tn('[/code]')); - } - ref = $$((insideCode ? 'br' : '.prettyprint br'), frag); - for (j = 0, len = ref.length; j < len; j++) { - node = ref[j]; - $.replace(node, $.tn('\n')); - } - ref1 = $$('br', frag); - for (k = 0, len1 = ref1.length; k < len1; k++) { - node = ref1[k]; - if (node !== frag.lastChild) { - $.replace(node, $.tn('\n>')); + for (i = j = 0, ref = sel.rangeCount; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) { + try { + range = sel.getRangeAt(i); + if (range.compareBoundaryPoints(Range.START_TO_START, postRange) < 0) { + range.setStartBefore(root); } - } - ref2 = $$('s, .removed-spoiler', frag); - for (l = 0, len2 = ref2.length; l < len2; l++) { - node = ref2[l]; - $.replace(node, [$.tn('[spoiler]')].concat(slice.call(node.childNodes), [$.tn('[/spoiler]')])); - } - ref3 = $$('.prettyprint', frag); - for (n = 0, len3 = ref3.length; n < len3; n++) { - node = ref3[n]; - $.replace(node, [$.tn('[code]')].concat(slice.call(node.childNodes), [$.tn('[/code]')])); - } - ref4 = $$('.linkify[data-original]', frag); - for (o = 0, len4 = ref4.length; o < len4; o++) { - node = ref4[o]; - $.replace(node, $.tn(node.dataset.original)); - } - ref5 = $$('.embedder', frag); - for (q = 0, len5 = ref5.length; q < len5; q++) { - node = ref5[q]; - if (((ref6 = node.previousSibling) != null ? ref6.nodeValue : void 0) === ' ') { - $.rm(node.previousSibling); + if (range.compareBoundaryPoints(Range.END_TO_END, postRange) > 0) { + range.setEndAfter(root); } - $.rm(node); - } - text += ">" + (frag.textContent.trim()) + "\n"; + if (!range.toString().trim()) { + continue; + } + frag = range.cloneContents(); + ancestor = range.commonAncestorContainer; + if ($.x('ancestor-or-self::*[self::s or contains(@class,"removed-spoiler")]', ancestor)) { + $.prepend(frag, $.tn('[spoiler]')); + $.add(frag, $.tn('[/spoiler]')); + } + if (insideCode = $.x('ancestor-or-self::pre[contains(@class,"prettyprint")]', ancestor)) { + $.prepend(frag, $.tn('[code]')); + $.add(frag, $.tn('[/code]')); + } + ref1 = $$((insideCode ? 'br' : '.prettyprint br'), frag); + for (k = 0, len = ref1.length; k < len; k++) { + node = ref1[k]; + $.replace(node, $.tn('\n')); + } + ref2 = $$('br', frag); + for (l = 0, len1 = ref2.length; l < len1; l++) { + node = ref2[l]; + if (node !== frag.lastChild) { + $.replace(node, $.tn('\n>')); + } + } + if (typeof (base = g.SITE).insertTags === "function") { + base.insertTags(frag); + } + ref3 = $$('.linkify[data-original]', frag); + for (n = 0, len2 = ref3.length; n < len2; n++) { + node = ref3[n]; + $.replace(node, $.tn(node.dataset.original)); + } + ref4 = $$('.embedder', frag); + for (o = 0, len3 = ref4.length; o < len3; o++) { + node = ref4[o]; + if (((ref5 = node.previousSibling) != null ? ref5.nodeValue : void 0) === ' ') { + $.rm(node.previousSibling); + } + $.rm(node); + } + text += ">" + (frag.textContent.trim()) + "\n"; + } catch (error) {} } QR.openPost(); - ref7 = QR.nodes, com = ref7.com, thread = ref7.thread; + ref6 = QR.nodes, com = ref6.com, thread = ref6.thread; if (!com.value) { thread.value = Get.threadFromNode(this); } + wasOnlyQuotes = QR.selected.isOnlyQuotes(); caretPos = com.selectionStart; com.value = com.value.slice(0, caretPos) + text + com.value.slice(com.selectionEnd); range = caretPos + text.length; com.setSelectionRange(range, range); com.focus(); + if (wasOnlyQuotes) { + QR.selected.quotedText = com.value; + } QR.selected.save(com); return QR.selected.save(thread); }, characterCount: function() { - var count, counter; + var count, counter, splitPost; counter = QR.nodes.charCount; count = QR.nodes.com.value.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '_').length; counter.textContent = count; counter.hidden = count < QR.max_comment / 2; - return (count > QR.max_comment ? $.addClass : $.rmClass)(counter, 'warning'); + (count > QR.max_comment ? $.addClass : $.rmClass)(counter, 'warning'); + splitPost = QR.nodes.splitPost; + return splitPost.hidden = count < QR.max_comment; + }, + splitPost: function() { + var count, currentLength, currentPost, j, lastPostLength, len, line, newComment, post, ref, text; + count = QR.nodes.com.value.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '_').length; + text = QR.nodes.com.value; + if (count < QR.max_comment || QR.selected.isLocked) { + return; + } + lastPostLength = 0; + QR.selected.setComment(""); + ref = text.split("\n"); + for (j = 0, len = ref.length; j < len; j++) { + line = ref[j]; + currentLength = line.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '_').length + 1; + if ((currentLength + lastPostLength) > QR.max_comment) { + post = new QR.post(true); + post.setComment(line); + lastPostLength = currentLength; + } else { + currentPost = QR.selected; + newComment = [currentPost.com, line].filter(function(el) { + return el !== null; + }).join("\n"); + currentPost.setComment(newComment); + lastPostLength += currentLength; + } + } + return QR.nodes.el.classList.add('dump'); }, getFile: function() { var ref; return $.event('QRFile', (ref = QR.selected) != null ? ref.file : void 0); }, + drawFile: function(e) { + var el, file, isVideo, ref; + file = (ref = QR.selected) != null ? ref.file : void 0; + if (!(file && /^(image|video)\//.test(file.type))) { + return; + } + isVideo = /^video\//.test(file); + el = $.el((isVideo ? 'video' : 'img')); + $.on(el, 'error', function() { + return QR.openError(); + }); + $.on(el, (isVideo ? 'loadeddata' : 'load'), function() { + e.target.getContext('2d').drawImage(el, 0, 0); + URL.revokeObjectURL(el.src); + return $.event('QRImageDrawn', null, e.target); + }); + return el.src = URL.createObjectURL(file); + }, + openError: function() { + var div; + div = $.el('div'); + $.extend(div, {innerHTML: "Could not open file. [More info]"}); + return QR.error(div); + }, setFile: function(e) { var file, name, ref, source; ref = e.detail, file = ref.file, name = ref.name, source = ref.source; @@ -19648,30 +24436,34 @@ QR = (function() { return QR.handleFiles(e.dataTransfer.files); }, paste: function(e) { - var blob, files, item, j, len, ref; + var blob, file, file2, item, j, len, ref, score, score2, type; if (!e.clipboardData.items) { return; } - files = []; + file = null; + score = -1; ref = e.clipboardData.items; for (j = 0, len = ref.length; j < len; j++) { item = ref[j]; - if (!(item.kind === 'file')) { + if (!(item.kind === 'file' && (file2 = item.getAsFile()))) { continue; } - blob = item.getAsFile(); - blob.name = 'file'; - if (blob.type) { - blob.name += '.' + blob.type.split('/')[1]; + score2 = 2 * (file2.size <= QR.max_size) + (file2.type === 'image/png'); + if (score2 > score) { + file = file2; + score = score2; } - files.push(blob); } - if (!files.length) { - return; + if (file) { + type = file.type; + blob = new Blob([file], { + type: type + }); + blob.name = Conf['pastedname'] + "." + ($.getOwn(QR.extensionFromType, type) || 'jpg'); + QR.open(); + QR.handleFiles([blob]); + $.addClass(QR.nodes.el, 'dump'); } - QR.open(); - QR.handleFiles(files); - return $.addClass(QR.nodes.el, 'dump'); }, pasteFF: function() { var arr, blob, bstr, i, images, img, j, k, len, m, pasteArea, ref, src; @@ -19693,7 +24485,7 @@ QR = (function() { blob = new Blob([arr], { type: m[1] }); - blob.name = "file." + m[2]; + blob.name = Conf['pastedname'] + "." + m[2]; QR.handleFiles([blob]); } else if (/^https?:\/\//.test(src)) { QR.handleUrl(src); @@ -19701,18 +24493,22 @@ QR = (function() { } }, handleUrl: function(urlDefault) { - var url; - url = prompt('Enter a URL:', urlDefault); - if (url === null) { - return; - } - QR.nodes.fileButton.focus(); - return CrossOrigin.file(url, function(blob) { - if (blob && !/^text\//.test(blob.type)) { - return QR.handleFiles([blob]); - } else { - return QR.error("Can't load file."); + QR.open(); + QR.selected.preventAutoPost(); + return CrossOrigin.permission(function() { + var url; + url = prompt('Enter a URL:', urlDefault); + if (url === null) { + return; } + QR.nodes.fileButton.focus(); + return CrossOrigin.file(url, function(blob) { + if (blob && !/^text\//.test(blob.type)) { + return QR.handleFiles([blob]); + } else { + return QR.error("Can't load file."); + } + }); }); }, handleFiles: function(files) { @@ -19782,11 +24578,9 @@ QR = (function() { return (g.VIEW === 'thread' ? $.addClass : $.rmClass)(QR.nodes.el, 'reply-to-thread'); }, dialog: function() { - var dialog, event, i, items, m, match_max, match_min, name, node, nodes, ref, rules, save, scriptData, setNode; + var classList, config, dialog, event, i, items, name, node, nodes, save, setNode; QR.nodes = nodes = { - el: dialog = UI.dialog('qr', 'top: 50px; right: 0px;', { - innerHTML: "
              ×
              No selected file
              " - }) + el: dialog = UI.dialog('qr', {innerHTML: "
              ×
              No selected file
              "}) }; setNode = function(name, query) { return nodes[name] = $(query, dialog); @@ -19803,6 +24597,7 @@ QR = (function() { setNode('sub', '[data-name=sub]'); setNode('com', '[data-name=com]'); setNode('charCount', '#char-count'); + setNode('splitPost', '#split-post'); setNode('texPreview', '#tex-preview'); setNode('dumpList', '#dump-list'); setNode('addPost', '#add-post'); @@ -19822,33 +24617,14 @@ QR = (function() { setNode('status', '[type=submit]'); setNode('flashTag', '[name=filetag]'); setNode('fileInput', '[type=file]'); - rules = $('ul.rules').textContent.trim(); - match_min = rules.match(/.+smaller than (\d+)x(\d+).+/); - match_max = rules.match(/.+greater than (\d+)x(\d+).+/); - QR.min_width = +(match_min != null ? match_min[1] : void 0) || 1; - QR.min_height = +(match_min != null ? match_min[2] : void 0) || 1; - QR.max_width = +(match_max != null ? match_max[1] : void 0) || 10000; - QR.max_height = +(match_max != null ? match_max[2] : void 0) || 10000; - scriptData = Get.scriptData(); - QR.max_size = (m = scriptData.match(/\bmaxFilesize *= *(\d+)\b/)) ? +m[1] : 4194304; - QR.max_size_video = (m = scriptData.match(/\bmaxWebmFilesize *= *(\d+)\b/)) ? +m[1] : QR.max_size; - QR.max_comment = (m = scriptData.match(/\bcomlen *= *(\d+)\b/)) ? +m[1] : 2000; - QR.max_width_video = QR.max_height_video = 2048; - QR.max_duration_video = (ref = g.BOARD.ID) === 'gif' || ref === 'wsg' ? 300 : 120; - if (Conf['Show New Thread Option in Threads']) { - $.addClass(QR.nodes.el, 'show-new-thread-option'); - } - QR.forcedAnon = !!$('form[name="post"] input[name="name"][type="hidden"]'); - if (QR.forcedAnon) { - $.addClass(QR.nodes.el, 'forced-anon'); - } - QR.spoiler = !!$('.postForm input[name=spoiler]'); - if (QR.spoiler) { - $.addClass(QR.nodes.el, 'has-spoiler'); - } - if (g.BOARD.ID === 'jp' && Conf['sjisPreview']) { - $.addClass(QR.nodes.el, 'sjis-preview'); - } + config = g.BOARD.config; + classList = QR.nodes.el.classList; + classList.toggle('forced-anon', QR.forcedAnon); + classList.toggle('has-spoiler', QR.spoiler); + classList.toggle('has-sjis', !!config.sjis_tags); + classList.toggle('has-math', !!config.math_tags); + classList.toggle('sjis-preview', !!config.sjis_tags && Conf['sjisPreview']); + classList.toggle('show-new-thread-option', Conf['Show New Thread Option in Threads']); if (parseInt(Conf['customCooldown'], 10) > 0) { $.addClass(QR.nodes.fileSubmit, 'custom-cooldown'); $.get('customCooldownEnabled', Conf['customCooldownEnabled'], function(arg) { @@ -19858,12 +24634,15 @@ QR = (function() { return $.sync('customCooldownEnabled', QR.setCustomCooldown); }); } + QR.flagsInput(); $.on(nodes.autohide, 'change', QR.toggleHide); $.on(nodes.close, 'click', QR.close); + $.on(nodes.status, 'click', QR.submit); $.on(nodes.form, 'submit', QR.submit); $.on(nodes.sjisToggle, 'click', QR.toggleSJIS); $.on(nodes.texButton, 'mousedown', QR.texPreviewShow); $.on(nodes.texButton, 'mouseup', QR.texPreviewHide); + $.on(nodes.splitPost, 'click', QR.splitPost); $.on(nodes.addPost, 'click', function() { return new QR.post(true); }); @@ -19894,13 +24673,13 @@ QR = (function() { window.addEventListener('focus', QR.focus, true); window.addEventListener('blur', QR.focus, true); $.on(d, 'click', QR.focus); - if ($.engine === 'gecko') { + if ($.engine === 'gecko' && !window.DataTransferItemList) { nodes.pasteArea.hidden = false; - new MutationObserver(QR.pasteFF).observe(nodes.pasteArea, { - childList: true - }); } - items = ['thread', 'name', 'email', 'sub', 'com', 'filename']; + new MutationObserver(QR.pasteFF).observe(nodes.pasteArea, { + childList: true + }); + items = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'flag']; i = 0; save = function() { return QR.selected.save(this); @@ -19934,35 +24713,80 @@ QR = (function() { QR.oekaki.setup(); return $.event('QRDialogCreation', null, dialog); }, + flags: function() { + var addFlag, ref, select, textContent, value; + select = $.el('select', { + name: 'flag', + className: 'flagSelector' + }); + addFlag = function(value, textContent) { + return $.add(select, $.el('option', { + value: value, + textContent: textContent + })); + }; + addFlag('0', (g.BOARD.config.country_flags ? 'Geographic Location' : 'None')); + ref = g.BOARD.config.board_flags; + for (value in ref) { + textContent = ref[value]; + addFlag(value, textContent); + } + return select; + }, + flagsInput: function() { + var flag, nodes; + nodes = QR.nodes; + if (!nodes) { + return; + } + if (nodes.flag) { + $.rm(nodes.flag); + delete nodes.flag; + } + if (g.BOARD.config.board_flags) { + flag = QR.flags(); + flag.dataset.name = 'flag'; + flag.dataset["default"] = '0'; + nodes.flag = flag; + return $.add(nodes.form, flag); + } + }, submit: function(e) { - var captcha, cb, err, extra, filetag, formData, options, post, ref, textOnly, thread, threadID; + var captcha, cb, err, filetag, force, formData, options, post, ref, thread, threadID; if (e != null) { e.preventDefault(); } + force = e != null ? e.shiftKey : void 0; if (QR.req) { QR.abort(); return; } + $.forceSync('cooldowns'); if (QR.cooldown.seconds) { - QR.cooldown.auto = !QR.cooldown.auto; - QR.status(); - return; + if (force) { + QR.cooldown.clear(); + } else { + QR.cooldown.auto = !QR.cooldown.auto; + QR.status(); + return; + } } post = QR.posts[0]; + delete post.quotedText; post.forceSave(); threadID = post.thread; - thread = g.BOARD.threads[threadID]; + thread = g.BOARD.threads.get(threadID); if (g.BOARD.ID === 'f' && threadID === 'new') { filetag = QR.nodes.flashTag.value; } if (threadID === 'new') { threadID = null; - if (g.BOARD.ID === 'vg' && !post.sub) { + if (!!g.BOARD.config.require_subject && !post.sub) { err = 'New threads require a subject.'; - } else if (!($.hasClass(d.body, 'text_only') || post.file || (textOnly = !!$('input[name=textonly]', $.id('postForm'))))) { + } else if (!(!!g.BOARD.config.text_only || post.file)) { err = 'No file selected.'; } - } else if (g.BOARD.threads[threadID].isClosed) { + } else if (g.BOARD.threads.get(threadID).isClosed) { err = 'You can\'t reply to this thread anymore.'; } else if (!(post.com || post.file)) { err = 'No comment or file.'; @@ -19972,29 +24796,29 @@ QR = (function() { if (g.BOARD.ID === 'r9k' && !((ref = post.com) != null ? ref.match(/[a-z-]/i) : void 0)) { err || (err = 'Original comment required.'); } - if (QR.captcha.isEnabled && !err) { - captcha = QR.captcha.getOne(); + if (QR.captcha.isEnabled && !(QR.captcha === Captcha.v2 && /\b_ct=/.test(d.cookie) && threadID) && !(err && !force)) { + captcha = QR.captcha.getOne(!!threadID); + if (QR.captcha === Captcha.v2) { + captcha || (captcha = Captcha.cache.request(!!threadID)); + } if (!captcha) { err = 'No valid captcha.'; QR.captcha.setup(!QR.cooldown.auto || d.activeElement === QR.nodes.status); } } QR.cleanNotifications(); - if (err) { + if (err && !force) { QR.cooldown.auto = false; QR.status(); QR.error(err); return; } QR.cooldown.auto = QR.posts.length > 1; - if (Conf['Auto Hide QR'] && !QR.cooldown.auto) { - QR.hide(); - } - if (!QR.cooldown.auto && $.x('ancestor::div[@id="qr"]', d.activeElement)) { - d.activeElement.blur(); - } post.lock(); formData = { + MAX_FILE_SIZE: QR.max_size, + mode: 'regist', + pwd: QR.persona.getPassword(), resto: threadID, name: !QR.forcedAnon ? post.name : void 0, email: post.email, @@ -20003,66 +24827,74 @@ QR = (function() { upfile: post.file, filetag: filetag, spoiler: post.spoiler, - textonly: textOnly, - mode: 'regist', - pwd: QR.persona.getPassword() + flag: post.flag }; options = { responseType: 'document', withCredentials: true, - onload: QR.response, - onerror: function() { - delete QR.req; - post.unlock(); - QR.cooldown.auto = false; - QR.status(); - return QR.error($.el('span', { - innerHTML: "Connection error while posting. [More info]" - })); - } - }; - extra = { + onloadend: QR.response, form: $.formData(formData) }; if (Conf['Show Upload Progress']) { - extra.upCallbacks = { - onload: function() { + options.onprogress = function(e) { + var ref1; + if (this !== ((ref1 = QR.req) != null ? ref1.upload : void 0)) { + return; + } + if (e.loaded < e.total) { + QR.req.progress = (Math.round(e.loaded / e.total * 100)) + "%"; + } else { QR.req.isUploadFinished = true; QR.req.progress = '...'; - return QR.status(); - }, - onprogress: function(e) { - QR.req.progress = (Math.round(e.loaded / e.total * 100)) + "%"; - return QR.status(); } + return QR.status(); }; } cb = function(response) { + var key, val; if (response != null) { - if (response.challenge != null) { - extra.form.append('recaptcha_challenge_field', response.challenge); - extra.form.append('recaptcha_response_field', response.response); + QR.currentCaptcha = response; + if (QR.captcha === Captcha.v2) { + if (response.challenge != null) { + options.form.append('recaptcha_challenge_field', response.challenge); + options.form.append('recaptcha_response_field', response.response); + } else { + options.form.append('g-recaptcha-response', response.response); + } } else { - extra.form.append('g-recaptcha-response', response.response); + for (key in response) { + val = response[key]; + options.form.append(key, val); + } } } - QR.req = $.ajax("https://sys.4chan.org/" + g.BOARD + "/post", options, extra); + QR.req = $.ajax("https://sys." + (location.hostname.split('.')[1]) + ".org/" + g.BOARD + "/post", options); return QR.req.progress = '...'; }; if (typeof captcha === 'function') { QR.req = { progress: '...', abort: function() { + if (QR.captcha === Captcha.v2) { + Captcha.cache.abort(); + } return cb = null; } }; captcha(function(response) { - if (response) { + if (QR.captcha === Captcha.v2 && Captcha.cache.haveCookie()) { + if (typeof cb === "function") { + cb(); + } + if (response) { + return Captcha.cache.save(response); + } + } else if (response) { return typeof cb === "function" ? cb(response) : void 0; } else { delete QR.req; post.unlock(); - QR.cooldown.auto = !!QR.captcha.captchas.length; + QR.cooldown.auto = !!Captcha.cache.getCount(); return QR.status(); } }); @@ -20072,39 +24904,52 @@ QR = (function() { return QR.status(); }, response: function() { - var _, ban, err, h1, isReply, lastPostToThread, m, post, postID, postsCount, ref, ref1, ref2, req, resDoc, seconds, threadID, url; - req = QR.req; + var URL, _, base, connErr, err, h1, isReply, j, lastPostToThread, len, m, mi, open, post, postID, postsCount, ref, ref1, ref2, ref3, seconds, threadID; + if (this !== QR.req) { + return; + } delete QR.req; post = QR.posts[0]; post.unlock(); - resDoc = req.response; - if (ban = $('.banType', resDoc)) { - err = $.el('span', ban.textContent.toLowerCase() === 'banned' ? { - innerHTML: "You are banned on " + ($(".board", resDoc)).innerHTML + "! ;_;
              Click here to see the reason." - } : { - innerHTML: "You were issued a warning on " + ($(".board", resDoc)).innerHTML + " as " + ($(".nameBlock", resDoc)).innerHTML + ".
              Reason: " + ($(".reason", resDoc)).innerHTML - }); - } else if (err = resDoc.getElementById('errmsg')) { - if ((ref = $('a', err)) != null) { - ref.target = '_blank'; + if ((err = (ref = this.response) != null ? ref.getElementById('errmsg') : void 0)) { + if ((ref1 = $('a', err)) != null) { + ref1.target = '_blank'; + } + } else if ((connErr = !this.response || this.response.title !== 'Post successful!')) { + err = QR.connectionError(); + if (QR.captcha === Captcha.v2 && QR.currentCaptcha) { + Captcha.cache.save(QR.currentCaptcha); + } + } else if (this.status !== 200) { + err = "Error " + this.statusText + " (" + this.status + ")"; + } + if (!connErr) { + if (typeof (base = QR.captcha).setUsed === "function") { + base.setUsed(); } - } else if (resDoc.title !== 'Post successful!') { - err = 'Connection error with sys.4chan.org.'; - } else if (req.status !== 200) { - err = "Error " + req.statusText + " (" + req.status + ")"; } + delete QR.currentCaptcha; if (err) { - if (/captcha|verification/i.test(err.textContent) || err === 'Connection error with sys.4chan.org.') { + QR.errorCount = (QR.errorCount || 0) + 1; + if (/captcha|verification/i.test(err.textContent) || connErr) { if (/mistyped/i.test(err.textContent)) { err = 'You mistyped the CAPTCHA, or the CAPTCHA malfunctioned.'; } else if (/expired/i.test(err.textContent)) { err = 'This CAPTCHA is no longer valid because it has expired.'; } - QR.cooldown.auto = QR.captcha.isEnabled || err === 'Connection error with sys.4chan.org.'; - QR.cooldown.addDelay(post, 2); - } else if (err.textContent && (m = err.textContent.match(/(?:(\d+)\s+minutes?\s+)?(\d+)\s+second/i)) && !/duplicate|hour/i.test(err.textContent)) { + if (QR.errorCount >= 5) { + QR.cooldown.auto = false; + } else { + QR.cooldown.auto = QR.captcha.isEnabled || connErr; + QR.cooldown.addDelay(post, 2); + } + } else if (err.textContent && (m = err.textContent.match(/\d+\s+(?:minute|second)/gi)) && !/duplicate|hour/i.test(err.textContent)) { QR.cooldown.auto = !/have\s+been\s+muted/i.test(err.textContent); - seconds = 60 * (+(m[1] || 0)) + (+m[2]); + seconds = 0; + for (j = 0, len = m.length; j < len; j++) { + mi = m[j]; + seconds += (/minute/i.test(mi) ? 60 : 1) * (+mi.match(/\d+/)[0]); + } if (/muted/i.test(err.textContent)) { QR.cooldown.addMute(seconds); } else { @@ -20113,16 +24958,14 @@ QR = (function() { } else { QR.cooldown.auto = false; } - QR.captcha.setup(QR.cooldown.auto && ((ref1 = d.activeElement) === QR.nodes.status || ref1 === d.body)); - if (QR.captcha.isEnabled && !QR.captcha.captchas.length) { - QR.cooldown.auto = false; - } + QR.captcha.setup(QR.cooldown.auto && ((ref2 = d.activeElement) === QR.nodes.status || ref2 === d.body)); QR.status(); QR.error(err); return; } - h1 = $('h1', resDoc); - ref2 = h1.nextSibling.textContent.match(/thread:(\d+),no:(\d+)/), _ = ref2[0], threadID = ref2[1], postID = ref2[2]; + delete QR.errorCount; + h1 = $('h1', this.response); + ref3 = h1.nextSibling.textContent.match(/thread:(\d+),no:(\d+)/), _ = ref3[0], threadID = ref3[1], postID = ref3[2]; postID = +postID; threadID = +threadID || postID; isReply = threadID !== postID; @@ -20139,37 +24982,49 @@ QR = (function() { postsCount = QR.posts.length - 1; QR.cooldown.auto = postsCount && isReply; lastPostToThread = !((function() { - var j, len, p, ref3; - ref3 = QR.posts.slice(1); - for (j = 0, len = ref3.length; j < len; j++) { - p = ref3[j]; + var k, len1, p, ref4; + ref4 = QR.posts.slice(1); + for (k = 0, len1 = ref4.length; k < len1; k++) { + p = ref4[k]; if (p.thread === post.thread) { return true; } } })()); - if (!(Conf['Persistent QR'] || postsCount)) { - QR.close(); - } else { + if (postsCount) { post.rm(); QR.captcha.setup(d.activeElement === QR.nodes.status); + } else if (Conf['Persistent QR']) { + post.rm(); + if (Conf['Auto Hide QR']) { + QR.hide(); + } else { + QR.blur(); + } + } else { + QR.close(); } QR.cleanNotifications(); if (Conf['Posting Success Notifications']) { QR.notifications.push(new Notice('success', h1.textContent, 5)); } QR.cooldown.add(threadID, postID); - url = threadID === postID ? window.location.origin + "/" + g.BOARD + "/thread/" + threadID : threadID !== g.THREADID && lastPostToThread ? window.location.origin + "/" + g.BOARD + "/thread/" + threadID + "#p" + postID : void 0; - if (url) { + URL = threadID === postID ? window.location.origin + "/" + g.BOARD + "/thread/" + threadID : threadID !== g.THREADID && lastPostToThread && Conf['Open Post in New Tab'] ? window.location.origin + "/" + g.BOARD + "/thread/" + threadID + "#p" + postID : void 0; + if (URL) { + open = Conf['Open Post in New Tab'] || postsCount ? function() { + return $.open(URL); + } : function() { + return location.href = URL; + }; if (threadID === postID) { - QR.waitForThread(url); + QR.waitForThread(URL, open); } else { - $.open(url); + open(); } } return QR.status(); }, - waitForThread: function(url) { + waitForThread: function(url, cb) { var attempts, check; attempts = 0; check = function() { @@ -20177,21 +25032,26 @@ QR = (function() { onloadend: function() { attempts++; if (attempts >= 6 || this.status === 200) { - return $.open(url); + return cb(); } else { return setTimeout(check, attempts * $.SECOND); } - } - }, { + }, + responseType: 'text', type: 'HEAD' }); }; return check(); }, abort: function() { - if (QR.req && !QR.req.isUploadFinished) { - QR.req.abort(); + var oldReq; + if ((oldReq = QR.req) && !QR.req.isUploadFinished) { delete QR.req; + oldReq.abort(); + if (QR.captcha === Captcha.v2 && QR.currentCaptcha) { + Captcha.cache.save(QR.currentCaptcha); + } + delete QR.currentCaptcha; QR.posts[0].unlock(); QR.cooldown.auto = false; QR.notifications.push(new Notice('info', 'QR upload aborted.', 5)); @@ -20208,26 +25068,19 @@ QR = (function() { QR.cooldown = { seconds: 0, delays: { - thread: 0, - reply: 0, - image: 0, - reply_intra: 0, - image_intra: 0, - deletion: 60, - thread_global: 300 + deletion: 60 }, init: function() { if (!Conf['Quick Reply']) { return; } this.data = Conf['cooldowns']; + this.changes = $.dict(); return $.sync('cooldowns', this.sync); }, setup: function() { - var delay, m, ref, type; - if (m = Get.scriptData().match(/\bcooldowns *= *({[^}]+})/)) { - $.extend(QR.cooldown.delays, JSON.parse(m[1])); - } + var delay, ref, type; + $.extend(QR.cooldown.delays, g.BOARD.cooldowns()); QR.cooldown.maxDelay = 0; ref = QR.cooldown.delays; for (type in ref) { @@ -20249,7 +25102,7 @@ QR = (function() { return QR.cooldown.count(); }, sync: function(data) { - QR.cooldown.data = data || {}; + QR.cooldown.data = data || $.dict(); return QR.cooldown.start(); }, add: function(threadID, postID) { @@ -20270,6 +25123,7 @@ QR = (function() { postID: postID }); } + QR.cooldown.save(); return QR.cooldown.start(); }, addDelay: function(post, delay) { @@ -20280,6 +25134,7 @@ QR = (function() { cooldown = QR.cooldown.categorize(post); cooldown.delay = delay; QR.cooldown.set(g.BOARD.ID, Date.now(), cooldown); + QR.cooldown.save(); return QR.cooldown.start(); }, addMute: function(delay) { @@ -20290,6 +25145,7 @@ QR = (function() { type: 'mute', delay: delay }); + QR.cooldown.save(); return QR.cooldown.start(); }, "delete": function(post) { @@ -20297,22 +25153,21 @@ QR = (function() { if (!QR.cooldown.data) { return; } - $.forceSync('cooldowns'); - cooldowns = ((base = QR.cooldown.data)[name = post.board.ID] || (base[name] = {})); + cooldowns = ((base = QR.cooldown.data)[name = post.board.ID] || (base[name] = $.dict())); for (id in cooldowns) { cooldown = cooldowns[id]; if ((cooldown.delay == null) && cooldown.threadID === post.thread.ID && cooldown.postID === post.ID) { - delete cooldowns[id]; + QR.cooldown.set(post.board.ID, id, null); } } - return QR.cooldown.save([post.board.ID]); + return QR.cooldown.save(); }, secondsDeletion: function(post) { var cooldown, cooldowns, seconds, start; if (!(QR.cooldown.data && Conf['Cooldown'])) { return 0; } - cooldowns = QR.cooldown.data[post.board.ID] || {}; + cooldowns = QR.cooldown.data[post.board.ID] || $.dict(); for (start in cooldowns) { cooldown = cooldowns[start]; if ((cooldown.delay == null) && cooldown.threadID === post.thread.ID && cooldown.postID === post.ID) { @@ -20334,28 +25189,56 @@ QR = (function() { }; } }, + mergeChange: function(data, scope, id, value) { + if (value) { + return (data[scope] || (data[scope] = $.dict()))[id] = value; + } else if (scope in data) { + delete data[scope][id]; + if (Object.keys(data[scope]).length === 0) { + return delete data[scope]; + } + } + }, set: function(scope, id, value) { - var base, cooldowns; - $.forceSync('cooldowns'); - cooldowns = ((base = QR.cooldown.data)[scope] || (base[scope] = {})); - cooldowns[id] = value; - return $.set('cooldowns', QR.cooldown.data); + var base; + QR.cooldown.mergeChange(QR.cooldown.data, scope, id, value); + return ((base = QR.cooldown.changes)[scope] || (base[scope] = $.dict()))[id] = value; }, - save: function(scopes) { - var data, i, len, scope; - data = QR.cooldown.data; - for (i = 0, len = scopes.length; i < len; i++) { - scope = scopes[i]; - if (scope in data && !Object.keys(data[scope]).length) { - delete data[scope]; - } + save: function() { + var changes; + changes = QR.cooldown.changes; + if (!Object.keys(changes).length) { + return; } - return $.set('cooldowns', data); + return $.get('cooldowns', $.dict(), function(arg) { + var cooldowns, id, ref, scope, value; + cooldowns = arg.cooldowns; + for (scope in QR.cooldown.changes) { + ref = QR.cooldown.changes[scope]; + for (id in ref) { + value = ref[id]; + QR.cooldown.mergeChange(cooldowns, scope, id, value); + } + QR.cooldown.data = cooldowns; + } + return $.set('cooldowns', cooldowns, function() { + return QR.cooldown.changes = $.dict(); + }); + }); }, - count: function() { + clear: function() { + QR.cooldown.data = $.dict(); + QR.cooldown.changes = $.dict(); + QR.cooldown.auto = false; + QR.cooldown.update(); + return $.queueTask($["delete"], 'cooldowns'); + }, + update: function() { var base, cooldown, cooldowns, elapsed, i, len, maxDelay, nCooldowns, now, ref, ref1, save, scope, seconds, start, suffix, threadID, type, update; - $.forceSync('cooldowns'); - save = []; + if (!QR.cooldown.isCounting) { + return; + } + save = false; nCooldowns = 0; now = Date.now(); ref = QR.cooldown.categorize(QR.posts[0]), type = ref.type, threadID = ref.threadID; @@ -20364,20 +25247,20 @@ QR = (function() { ref1 = [g.BOARD.ID, 'global']; for (i = 0, len = ref1.length; i < len; i++) { scope = ref1[i]; - cooldowns = ((base = QR.cooldown.data)[scope] || (base[scope] = {})); + cooldowns = ((base = QR.cooldown.data)[scope] || (base[scope] = $.dict())); for (start in cooldowns) { cooldown = cooldowns[start]; start = +start; elapsed = Math.floor((now - start) / $.SECOND); if (elapsed < 0) { - delete cooldowns[start]; - save.push(scope); + QR.cooldown.set(scope, start, null); + save = true; continue; } if (cooldown.delay != null) { if (cooldown.delay <= elapsed) { - delete cooldowns[start]; - save.push(scope); + QR.cooldown.set(scope, start, null); + save = true; } else if ((cooldown.type === type && cooldown.threadID === threadID) || cooldown.type === 'mute') { seconds = Math.max(seconds, cooldown.delay - elapsed); } @@ -20388,23 +25271,23 @@ QR = (function() { maxDelay = Math.max(maxDelay, parseInt(Conf['customCooldown'], 10)); } if (maxDelay <= elapsed) { - delete cooldowns[start]; - save.push(scope); + QR.cooldown.set(scope, start, null); + save = true; continue; } if ((type === 'thread') === (cooldown.threadID === cooldown.postID) && cooldown.boardID !== g.BOARD.ID) { - suffix = scope === 'global' ? '_global' : type !== 'thread' && threadID === cooldown.threadID ? '_intra' : ''; + suffix = scope === 'global' ? '_global' : ''; seconds = Math.max(seconds, QR.cooldown.delays[type + suffix] - elapsed); - } - if (QR.cooldown.customCooldown) { - seconds = Math.max(seconds, parseInt(Conf['customCooldown'], 10) - elapsed); + if (QR.cooldown.customCooldown) { + seconds = Math.max(seconds, parseInt(Conf['customCooldown'], 10) - elapsed); + } } } nCooldowns += Object.keys(cooldowns).length; } } - if (save.length) { - QR.cooldown.save(save); + if (save) { + QR.cooldown.save; } if (nCooldowns) { clearTimeout(QR.cooldown.timeout); @@ -20415,9 +25298,12 @@ QR = (function() { update = seconds !== QR.cooldown.seconds; QR.cooldown.seconds = seconds; if (update) { - QR.status(); + return QR.status(); } - if (seconds === 0 && QR.cooldown.auto && !QR.req) { + }, + count: function() { + QR.cooldown.update(); + if (QR.cooldown.seconds === 0 && QR.cooldown.auto && !QR.req) { return QR.submit(); } } @@ -20441,7 +25327,7 @@ QR = (function() { $.on(a, 'click', this.editFile); return Menu.menu.addEntry({ el: a, - order: 95, + order: 90, open: function(post) { var file; QR.oekaki.menu.post = post; @@ -20478,6 +25364,9 @@ QR = (function() { }); return video.currentTime = currentTime; }); + $.on(video, 'error', function() { + return QR.openError(); + }); return video.src = URL.createObjectURL(blob); } else { blob.name = post.file.name; @@ -20513,15 +25402,15 @@ QR = (function() { }, load: function(cb) { var n, onload, script, style; - if ($('script[src^="//s.4cdn.org/js/painter"]', d.head)) { + if ($('script[src^="//s.4cdn.org/js/tegaki"]', d.head)) { return cb(); } else { style = $.el('link', { rel: 'stylesheet', - href: "//s.4cdn.org/css/painter." + (Date.now()) + ".css" + href: "//s.4cdn.org/css/tegaki." + (Date.now()) + ".css" }); script = $.el('script', { - src: "//s.4cdn.org/js/painter.min." + (Date.now()) + ".js" + src: "//s.4cdn.org/js/tegaki.min." + (Date.now()) + ".js" }); n = 0; onload = function() { @@ -20578,45 +25467,55 @@ QR = (function() { })); }; cb = function(e) { - var file, isVideo; - document.removeEventListener('QRFile', cb, false); - if (!e.detail) { + var canvas, selected; + if (e) { + this.removeEventListener('QRMetadata', cb, false); + } + selected = document.getElementById('selected'); + if (!(selected != null ? selected.dataset.type : void 0)) { return error('No file to edit.'); } - if (!/^(image|video)\//.test(e.detail.type)) { + if (!/^(image|video)\//.test(selected.dataset.type)) { return error('Not an image.'); } - isVideo = /^video\//.test(e.detail.type); - file = document.createElement(isVideo ? 'video' : 'img'); - file.addEventListener('error', function() { - return error('Could not open file.', false); + if (!selected.dataset.height) { + return error('Metadata not available.'); + } + if (selected.dataset.height === 'loading') { + selected.addEventListener('QRMetadata', cb, false); + return; + } + if (Tegaki.bg) { + Tegaki.destroy(); + } + FCX.oekakiName = name; + Tegaki.open({ + onDone: FCX.oekakiCB, + onCancel: function() { + return Tegaki.bgColor = '#ffffff'; + }, + width: +selected.dataset.width, + height: +selected.dataset.height, + bgColor: 'transparent' }); - file.addEventListener((isVideo ? 'loadeddata' : 'load'), function() { - if (Tegaki.bg) { - Tegaki.destroy(); - } - FCX.oekakiName = name; - Tegaki.open({ - onDone: FCX.oekakiCB, - onCancel: function() { - return Tegaki.bgColor = '#ffffff'; - }, - width: file.naturalWidth || file.videoWidth, - height: file.naturalHeight || file.videoHeight, - bgColor: 'transparent' - }); - return Tegaki.activeCtx.drawImage(file, 0, 0); + canvas = document.createElement('canvas'); + canvas.width = canvas.naturalWidth = +selected.dataset.width; + canvas.height = canvas.naturalHeight = +selected.dataset.height; + canvas.hidden = true; + document.body.appendChild(canvas); + canvas.addEventListener('QRImageDrawn', function() { + this.remove(); + return Tegaki.onOpenImageLoaded.call(this); }, false); - return file.src = URL.createObjectURL(e.detail); + return canvas.dispatchEvent(new CustomEvent('QRDrawFile', { + bubbles: true + })); }; if (Tegaki.bg && Tegaki.onDoneCb === FCX.oekakiCB && source === FCX.oekakiLatest) { FCX.oekakiName = name; return Tegaki.resume(); } else { - document.addEventListener('QRFile', cb, false); - return document.dispatchEvent(new CustomEvent('QRGetFile', { - bubbles: true - })); + return cb(); } }); }); @@ -20720,7 +25619,8 @@ QR = (function() { var persona; persona = arg['QR.persona']; persona = { - name: post.name + name: post.name, + flag: post.flag }; return $.set('QR.persona', persona); }); @@ -20743,9 +25643,7 @@ QR = (function() { draggable: true, href: 'javascript:;' }); - $.extend(el, { - innerHTML: "" - }); + $.extend(el, {innerHTML: ""}); this.nodes = { el: el, rm: el.firstChild, @@ -20763,8 +25661,9 @@ QR = (function() { return function(e) { _this.spoiler = e.target.checked; if (_this === QR.selected) { - return QR.nodes.spoiler.checked = _this.spoiler; + QR.nodes.spoiler.checked = _this.spoiler; } + return _this.preventAutoPost(); }; })(this)); ref = $$('label', el); @@ -20789,6 +25688,9 @@ QR = (function() { _this.name = 'name' in QR.persona.always ? QR.persona.always.name : prev ? prev.name : persona.name; _this.email = 'email' in QR.persona.always ? QR.persona.always.email : ''; _this.sub = 'sub' in QR.persona.always ? QR.persona.always.sub : ''; + if (QR.nodes.flag) { + _this.flag = prev ? prev.flag : persona.flag && persona.flag in g.BOARD.config.board_flags ? persona.flag : void 0; + } if (QR.selected === _this) { return _this.load(); } @@ -20798,13 +25700,11 @@ QR = (function() { this.select(); } this.unlock(); - $.queueTask(function() { - return QR.captcha.onNewPost(); - }); + QR.captcha.moreNeeded(); } _Class.prototype.rm = function() { - var index; + var base, index; this["delete"](); index = QR.posts.indexOf(this); if (QR.posts.length === 1) { @@ -20814,7 +25714,8 @@ QR = (function() { (QR.posts[index - 1] || QR.posts[index + 1]).select(); } QR.posts.splice(index, 1); - return QR.status(); + QR.status(); + return typeof (base = QR.captcha).updateThread === "function" ? base.updateThread() : void 0; }; _Class.prototype["delete"] = function() { @@ -20832,7 +25733,7 @@ QR = (function() { if (this !== QR.selected) { return; } - ref = ['thread', 'name', 'email', 'sub', 'com', 'fileButton', 'filename', 'spoiler']; + ref = ['thread', 'name', 'email', 'sub', 'com', 'fileButton', 'filename', 'spoiler', 'flag']; for (i = 0, len = ref.length; i < len; i++) { name = ref[i]; if (node = QR.nodes[name]) { @@ -20865,7 +25766,7 @@ QR = (function() { _Class.prototype.load = function() { var i, len, name, node, ref; - ref = ['thread', 'name', 'email', 'sub', 'com', 'filename']; + ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'flag']; for (i = 0, len = ref.length; i < len; i++) { name = ref[i]; if (!(node = QR.nodes[name])) { @@ -20878,32 +25779,44 @@ QR = (function() { return QR.characterCount(); }; - _Class.prototype.save = function(input) { - var name, ref; + _Class.prototype.save = function(input, forced) { + var base, name, prev; if (input.type === 'checkbox') { this.spoiler = input.checked; return; } name = input.dataset.name; + if (name !== 'thread' && name !== 'name' && name !== 'email' && name !== 'sub' && name !== 'com' && name !== 'filename' && name !== 'flag') { + return; + } + prev = this[name] || input.dataset["default"] || null; this[name] = input.value || input.dataset["default"] || null; switch (name) { case 'thread': (this.thread !== 'new' ? $.addClass : $.rmClass)(QR.nodes.el, 'reply-to-thread'); - return QR.status(); + QR.status(); + if (typeof (base = QR.captcha).updateThread === "function") { + base.updateThread(); + } + break; case 'com': this.updateComment(); - if (QR.cooldown.auto && this === QR.posts[0] && (0 < (ref = QR.cooldown.seconds) && ref <= 5)) { - return QR.cooldown.auto = false; - } break; case 'filename': if (!this.file) { return; } this.saveFilename(); - return this.updateFilename(); + this.updateFilename(); + break; case 'name': - return QR.persona.set(this); + case 'flag': + if (this[name] !== prev) { + QR.persona.set(this); + } + } + if (!forced) { + return this.preventAutoPost(); } }; @@ -20912,13 +25825,22 @@ QR = (function() { if (this !== QR.selected) { return; } - ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler']; + ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler', 'flag']; for (i = 0, len = ref.length; i < len; i++) { name = ref[i]; if (!(node = QR.nodes[name])) { continue; } - this.save(node); + this.save(node, true); + } + }; + + _Class.prototype.preventAutoPost = function() { + if (QR.cooldown.auto && this === QR.posts[0]) { + QR.cooldown.update(); + if (QR.cooldown.seconds <= 5) { + return QR.cooldown.auto = false; + } } }; @@ -20935,9 +25857,14 @@ QR = (function() { QR.characterCount(); } this.nodes.span.textContent = this.com; - return $.queueTask(function() { - return QR.captcha.onPostChange(); - }); + QR.captcha.moreNeeded(); + if (QR.captcha === Captcha.v2) { + return Captcha.cache.prerequest(); + } + }; + + _Class.prototype.isOnlyQuotes = function() { + return (this.com || '').trim() === (this.quotedText || '').trim(); }; _Class.rmErrored = function(e) { @@ -20959,14 +25886,12 @@ QR = (function() { } }; - _Class.prototype.error = function(className, message) { + _Class.prototype.error = function(className, message, link) { var div, ref, rm, rmAll; div = $.el('div', { className: className }); - $.extend(div, { - innerHTML: E(message) + "
              [delete] [delete all]" - }); + $.extend(div, {innerHTML: E(message) + ((link) ? " [More info]" : "") + "
              [delete post] [delete all]"}); (this.errors || (this.errors = [])).push(div); ref = $$('a', div), rm = ref[0], rmAll = ref[1]; $.on(div, 'click', (function(_this) { @@ -20988,8 +25913,8 @@ QR = (function() { return QR.error(div, true); }; - _Class.prototype.fileError = function(message) { - return this.error('file-error', this.filename + ": " + message); + _Class.prototype.fileError = function(message, link) { + return this.error('file-error', this.filename + ": " + message, link); }; _Class.prototype.dismissErrors = function(test) { @@ -21014,7 +25939,7 @@ QR = (function() { var ext, ref; this.file = file1; if (Conf['Randomize Filename'] && g.BOARD.ID !== 'f') { - this.filename = "" + (Date.now() - Math.floor(Math.random() * 365 * $.DAY)); + this.filename = "" + (Date.now() * 1000 - Math.floor(Math.random() * 365 * $.DAY * 1000)); if (ext = this.file.name.match(QR.validExtension)) { this.filename += ext[0]; } @@ -21024,9 +25949,7 @@ QR = (function() { this.filesize = $.bytesToString(this.file.size); this.checkSize(); $.addClass(this.nodes.el, 'has-file'); - $.queueTask(function() { - return QR.captcha.onPostChange(); - }); + QR.captcha.moreNeeded(); URL.revokeObjectURL(this.URL); this.saveFilename(); if (this === QR.selected) { @@ -21034,12 +25957,15 @@ QR = (function() { } else { this.updateFilename(); } - this.nodes.el.style.backgroundImage = null; + this.rmMetadata(); + this.nodes.el.dataset.type = this.file.type; + this.nodes.el.style.backgroundImage = ''; if (ref = this.file.type, indexOf.call(QR.mimeTypes, ref) < 0) { - return this.fileError('Unsupported file type.'); + this.fileError('Unsupported file type.'); } else if (/^(image|video)\//.test(this.file.type)) { - return this.readFile(); + this.readFile(); } + return this.preventAutoPost(); }; _Class.prototype.checkSize = function() { @@ -21066,26 +25992,32 @@ QR = (function() { $.off(el, event, onload); $.off(el, 'error', onerror); _this.checkDimensions(el); - return _this.setThumbnail(el); + _this.setThumbnail(el); + return $.event('QRMetadata', null, _this.nodes.el); }; })(this); onerror = (function(_this) { return function() { $.off(el, event, onload); $.off(el, 'error', onerror); - _this.fileError((isVideo ? 'Video' : 'Image') + " appears corrupt"); - return URL.revokeObjectURL(el.src); + _this.fileError("Corrupt " + (isVideo ? 'video' : 'image') + " or error reading metadata.", 'https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions#error-reading-metadata'); + URL.revokeObjectURL(el.src); + _this.nodes.el.removeAttribute('data-height'); + return $.event('QRMetadata', null, _this.nodes.el); }; })(this); + this.nodes.el.dataset.height = 'loading'; $.on(el, event, onload); $.on(el, 'error', onerror); return el.src = URL.createObjectURL(this.file); }; _Class.prototype.checkDimensions = function(el) { - var duration, height, max_height, max_width, ref, videoHeight, videoWidth, width; + var duration, height, max_height, max_width, videoHeight, videoWidth, width; if (el.tagName === 'IMG') { height = el.height, width = el.width; + this.nodes.el.dataset.height = height; + this.nodes.el.dataset.width = width; if (height > QR.max_height || width > QR.max_width) { this.fileError("Image too large (image: " + height + "x" + width + "px, max: " + QR.max_height + "x" + QR.max_width + "px)"); } @@ -21094,6 +26026,9 @@ QR = (function() { } } else { videoHeight = el.videoHeight, videoWidth = el.videoWidth, duration = el.duration; + this.nodes.el.dataset.height = videoHeight; + this.nodes.el.dataset.width = videoWidth; + this.nodes.el.dataset.duration = duration; max_height = Math.min(QR.max_height, QR.max_height_video); max_width = Math.min(QR.max_width, QR.max_width_video); if (videoHeight > max_height || videoWidth > max_width) { @@ -21107,7 +26042,7 @@ QR = (function() { } else if (duration > QR.max_duration_video) { this.fileError("Video too long (video: " + duration + "s, max: " + QR.max_duration_video + "s)"); } - if (((ref = g.BOARD.ID) !== 'gif' && ref !== 'wsg') && $.hasAudio(el)) { + if (BoardConfig.noAudio(g.BOARD.ID) && $.hasAudio(el)) { return this.fileError('Audio not allowed'); } } @@ -21160,19 +26095,30 @@ QR = (function() { delete this.filesize; this.nodes.el.removeAttribute('title'); QR.nodes.filename.removeAttribute('title'); - this.nodes.el.style.backgroundImage = null; + this.rmMetadata(); + this.nodes.el.style.backgroundImage = ''; $.rmClass(this.nodes.el, 'has-file'); this.showFileData(); URL.revokeObjectURL(this.URL); - return this.dismissErrors(function(error) { + this.dismissErrors(function(error) { return $.hasClass(error, 'file-error'); }); + return this.preventAutoPost(); + }; + + _Class.prototype.rmMetadata = function() { + var attr, i, len, ref; + ref = ['type', 'height', 'width', 'duration']; + for (i = 0, len = ref.length; i < len; i++) { + attr = ref[i]; + this.nodes.el.removeAttribute("data-" + attr); + } }; _Class.prototype.saveFilename = function() { this.file.newName = (this.filename || '').replace(/[\/\\]/g, '-'); if (!QR.validExtension.test(this.filename)) { - return this.file.newName += "." + (QR.extensionFromType[this.file.type] || 'jpg'); + return this.file.newName += "." + ($.getOwn(QR.extensionFromType, this.file.type) || 'jpg'); } }; @@ -21208,6 +26154,7 @@ QR = (function() { _Class.prototype.pasteText = function(file) { var reader; this.pasting = true; + this.preventAutoPost(); reader = new FileReader(); reader.onload = (function(_this) { return function(e) { @@ -21245,7 +26192,7 @@ QR = (function() { }; _Class.prototype.drop = function() { - var el, index, newIndex, oldIndex, post; + var base, el, index, newIndex, oldIndex, post; $.rmClass(this, 'over'); if (!this.draggable) { return; @@ -21256,10 +26203,14 @@ QR = (function() { }; oldIndex = index(el); newIndex = index(this); + if (QR.posts[oldIndex].isLocked || QR.posts[newIndex].isLocked) { + return; + } (oldIndex < newIndex ? $.after : $.before)(this, el); post = QR.posts.splice(oldIndex, 1)[0]; QR.posts.splice(newIndex, 0, post); - return QR.status(); + QR.status(); + return typeof (base = QR.captcha).updateThread === "function" ? base.updateThread() : void 0; }; return _Class; @@ -21272,12 +26223,15 @@ QuoteBacklink = (function() { var QuoteBacklink; QuoteBacklink = { - containers: {}, + containers: $.dict(), init: function() { var ref; if (((ref = g.VIEW) !== 'index' && ref !== 'thread') || !Conf['Quote Backlinks']) { return; } + if ((this.bottomBacklinks = Conf['Bottom Backlinks'])) { + $.addClass(doc, 'bottom-backlinks'); + } Callbacks.Post.push({ name: 'Quote Backlinking Part 1', cb: this.firstNode @@ -21288,17 +26242,13 @@ QuoteBacklink = (function() { }); }, firstNode: function() { - var a, clone, container, containers, hash, i, j, k, len, len1, len2, link, markYours, nodes, post, quote, ref, ref1, ref2; + var a, clone, container, containers, hash, i, j, k, len, len1, len2, link, markYours, nodes, post, quote, ref, ref1; if (this.isClone || !this.quotes.length || this.isRebuilt) { return; } - markYours = Conf['Mark Quotes of You'] && ((ref = QuoteYou.db) != null ? ref.get({ - boardID: this.board.ID, - threadID: this.thread.ID, - postID: this.ID - }) : void 0); + markYours = Conf['Mark Quotes of You'] && QuoteYou.isYou(this); a = $.el('a', { - href: Build.postURL(this.board.ID, this.thread.ID, this.ID), + href: g.SITE.Build.postURL(this.board.ID, this.thread.ID, this.ID), className: this.isHidden ? 'filtered backlink' : 'backlink', textContent: Conf['backlink'].replace(/%(?:id|%)/g, (function(_this) { return function(x) { @@ -21307,16 +26257,19 @@ QuoteBacklink = (function() { '%%': '%' }[x]; }; - })(this)) + (markYours ? '\u00A0(You)' : '') + })(this)) }); - ref1 = this.quotes; - for (i = 0, len = ref1.length; i < len; i++) { - quote = ref1[i]; + if (markYours) { + $.add(a, QuoteYou.mark.cloneNode(true)); + } + ref = this.quotes; + for (i = 0, len = ref.length; i < len; i++) { + quote = ref[i]; containers = [QuoteBacklink.getContainer(quote)]; - if ((post = g.posts[quote]) && post.nodes.backlinkContainer) { - ref2 = post.clones; - for (j = 0, len1 = ref2.length; j < len1; j++) { - clone = ref2[j]; + if ((post = g.posts.get(quote)) && post.nodes.backlinkContainer) { + ref1 = post.clones; + for (j = 0, len1 = ref1.length; j < len1; j++) { + clone = ref1[j]; containers.push(clone.nodes.backlinkContainer); } } @@ -21341,7 +26294,7 @@ QuoteBacklink = (function() { secondNode: function() { var container; if (this.isClone && (this.origin.isReply || Conf['OP Backlinks'])) { - this.nodes.backlinkContainer = $('.container', this.nodes.info); + this.nodes.backlinkContainer = $('.container', this.nodes.post); return; } if (!(this.isReply || Conf['OP Backlinks'])) { @@ -21349,7 +26302,11 @@ QuoteBacklink = (function() { } container = QuoteBacklink.getContainer(this.fullID); this.nodes.backlinkContainer = container; - return $.add(this.nodes.info, container); + if (QuoteBacklink.bottomBacklinks) { + return $.add(this.nodes.post, container); + } else { + return $.add(this.nodes.info, container); + } }, getContainer: function(id) { var base; @@ -21375,7 +26332,10 @@ QuoteCT = (function() { if (Conf['Comment Expansion']) { ExpandComment.callbacks.push(this.node); } - this.text = '\u00A0(Cross-thread)'; + this.mark = $.el('span', { + textContent: '\u00A0(Cross-thread)', + className: 'qmark-ct' + }); return Callbacks.Post.push({ name: 'Mark Cross-thread Quotes', cb: this.node @@ -21395,10 +26355,10 @@ QuoteCT = (function() { continue; } if (this.isClone) { - quotelink.textContent = quotelink.textContent.replace(QuoteCT.text, ''); + $.rm($('.qmark-ct', quotelink)); } if (boardID === board.ID && threadID !== thread.ID) { - $.add(quotelink, $.tn(QuoteCT.text)); + $.add(quotelink, QuoteCT.mark.cloneNode(true)); } } } @@ -21458,11 +26418,14 @@ QuoteInline = (function() { }, toggle: function(e) { var boardID, context, postID, quoter, ref, ref1, threadID; - if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { + if ($.modifiedClick(e)) { return; } ref = Get.postDataFromLink(this), boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID; - if (Conf['Inline Cross-thread Quotes Only'] && g.VIEW === 'thread' && ((ref1 = g.posts[boardID + "." + postID]) != null ? ref1.nodes.root.offsetParent : void 0)) { + if (Conf['Inline Cross-thread Quotes Only'] && g.VIEW === 'thread' && ((ref1 = g.posts.get(boardID + "." + postID)) != null ? ref1.nodes.root.offsetParent : void 0)) { + return; + } + if ($.hasClass(doc, 'catalog-mode')) { return; } e.preventDefault(); @@ -21480,7 +26443,7 @@ QuoteInline = (function() { }, findRoot: function(quotelink, isBacklink) { if (isBacklink) { - return quotelink.parentNode.parentNode; + return $.x('ancestor::*[parent::*[contains(@class,"post")]][1]', quotelink); } else { return $.x('ancestor-or-self::*[parent::blockquote][1]', quotelink); } @@ -21497,7 +26460,7 @@ QuoteInline = (function() { qroot = $.x('ancestor::*[contains(@class,"postContainer")][1]', root); $.addClass(qroot, 'hasInline'); new Fetcher(boardID, threadID, postID, inline, quoter); - if (!((post = g.posts[boardID + "." + postID]) && context.thread === post.thread)) { + if (!((post = g.posts.get(boardID + "." + postID)) && context.thread === post.thread)) { return; } if (isBacklink && Conf['Forward Hiding']) { @@ -21510,21 +26473,23 @@ QuoteInline = (function() { return Unread.readSinglePost(post); }, rm: function(quotelink, boardID, threadID, postID, context) { - var el, inlined, isBacklink, post, qroot, ref, root; + var el, inlined, isBacklink, parentNode, post, qroot, ref, root; isBacklink = $.hasClass(quotelink, 'backlink'); root = QuoteInline.findRoot(quotelink, isBacklink); root = $.x("following-sibling::div[@data-full-i-d='" + boardID + "." + postID + "'][1]", root); qroot = $.x('ancestor::*[contains(@class,"postContainer")][1]', root); + parentNode = root.parentNode; $.rm(root); + $.event('PostsRemoved', null, parentNode); if (!$('.inline', qroot)) { $.rmClass(qroot, 'hasInline'); } if (!(el = root.firstElementChild)) { return; } - post = g.posts[boardID + "." + postID]; + post = g.posts.get(boardID + "." + postID); post.rmClone(el.dataset.clone); - if (Conf['Forward Hiding'] && isBacklink && context.thread === g.threads[boardID + "." + threadID] && !--post.forwarded) { + if (Conf['Forward Hiding'] && isBacklink && context.thread === g.threads.get(boardID + "." + threadID) && !--post.forwarded) { delete post.forwarded; $.rmClass(post.nodes.root, 'forwarded'); } @@ -21553,7 +26518,10 @@ QuoteOP = (function() { if (Conf['Comment Expansion']) { ExpandComment.callbacks.push(this.node); } - this.text = '\u00A0(OP)'; + this.mark = $.el('span', { + textContent: '\u00A0(OP)', + className: 'qmark-op' + }); return Callbacks.Post.push({ name: 'Mark OP Quotes', cb: this.node @@ -21571,7 +26539,7 @@ QuoteOP = (function() { if (this.isClone && (ref = this.thread.fullID, indexOf.call(quotes, ref) >= 0)) { i = 0; while (quotelink = quotelinks[i++]) { - quotelink.textContent = quotelink.textContent.replace(QuoteOP.text, ''); + $.rm($('.qmark-op', quotelink)); } } fullID = this.context.thread.fullID; @@ -21582,7 +26550,7 @@ QuoteOP = (function() { while (quotelink = quotelinks[i++]) { ref1 = Get.postDataFromLink(quotelink), boardID = ref1.boardID, postID = ref1.postID; if ((boardID + "." + postID) === fullID) { - $.add(quotelink, $.tn(QuoteOP.text)); + $.add(quotelink, QuoteOP.mark.cloneNode(true)); } } } @@ -21599,7 +26567,17 @@ QuotePreview = (function() { QuotePreview = { init: function() { var ref; - if (!(((ref = g.VIEW) === 'index' || ref === 'thread') && Conf['Quote Previewing'])) { + if (!Conf['Quote Previewing']) { + return; + } + if (g.VIEW === 'archive') { + $.on(d, 'mouseover', function(e) { + if (e.target.nodeName === 'A' && $.hasClass(e.target, 'quotelink')) { + return QuotePreview.mouseover.call(e.target, e); + } + }); + } + if ((ref = g.VIEW) !== 'index' && ref !== 'thread') { return; } if (Conf['Comment Expansion']) { @@ -21620,7 +26598,7 @@ QuotePreview = (function() { }, mouseover: function(e) { var boardID, i, len, origin, post, postID, posts, qp, ref, threadID; - if ($.hasClass(this, 'inlined') || !d.contains(this)) { + if (($.hasClass(this, 'inlined') && !$.hasClass(doc, 'catalog-mode')) || !d.contains(this)) { return; } ref = Get.postDataFromLink(this), boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID; @@ -21637,7 +26615,7 @@ QuotePreview = (function() { endEvents: 'mouseout click', cb: QuotePreview.mouseout }); - if (Conf['Quote Highlighting'] && (origin = g.posts[boardID + "." + postID])) { + if (Conf['Quote Highlighting'] && (origin = g.posts.get(boardID + "." + postID))) { posts = [origin].concat(origin.clones); posts.pop(); for (i = 0, len = posts.length; i < len; i++) { @@ -21651,6 +26629,7 @@ QuotePreview = (function() { if (!(root = this.el.firstElementChild)) { return; } + $.event('PostsRemoved', null, Header.hover); clone = Get.postFromRoot(root); post = clone.origin; post.rmClone(root.dataset.clone); @@ -21692,7 +26671,7 @@ QuoteStrikeThrough = (function() { for (i = 0, len = ref.length; i < len; i++) { quotelink = ref[i]; ref1 = Get.postDataFromLink(quotelink), boardID = ref1.boardID, postID = ref1.postID; - if ((ref2 = g.posts[boardID + "." + postID]) != null ? ref2.isHidden : void 0) { + if ((ref2 = g.posts.get(boardID + "." + postID)) != null ? ref2.isHidden : void 0) { $.addClass(quotelink, 'filtered'); } } @@ -21716,16 +26695,12 @@ QuoteThreading = if (!(Conf['Quote Threading'] && g.VIEW === 'thread')) { return; } - this.controls = $.el('label', { - innerHTML: " Threading" - }); + this.controls = $.el('label', {innerHTML: " Threading"}); this.threadNewLink = $.el('span', { className: 'brackets-wrap threadnewlink', hidden: true }); - $.extend(this.threadNewLink, { - innerHTML: "Thread New Posts" - }); + $.extend(this.threadNewLink, {innerHTML: "Thread New Posts"}); this.input = $('input', this.controls); this.input.checked = Conf['Thread Quotes']; $.on(this.input, 'change', this.setEnabled); @@ -21749,15 +26724,26 @@ QuoteThreading = cb: this.node }); }, - parent: {}, - children: {}, - inserted: {}, + parent: $.dict(), + children: $.dict(), + inserted: $.dict(), + toggleThreading: function() { + return this.setThreadingState(!Conf['Thread Quotes']); + }, + setThreadingState: function(enabled) { + this.input.checked = enabled; + this.setEnabled.call(this.input); + return this.rethread.call(this.input); + }, setEnabled: function() { var other, ref; - other = (ref = ReplyPruning.inputs) != null ? ref.enabled : void 0; - if (this.checked && (other != null ? other.checked : void 0)) { - other.checked = false; - $.event('change', null, other); + if (this.checked) { + $.set('Prune All Threads', false); + other = (ref = ReplyPruning.inputs) != null ? ref.enabled : void 0; + if (other != null ? other.checked : void 0) { + other.checked = false; + $.event('change', null, other); + } } return $.cb.checked.call(this); }, @@ -21782,7 +26768,7 @@ QuoteThreading = ref = this.quotes; for (j = 0, len = ref.length; j < len; j++) { quote = ref[j]; - if (parent = g.posts[quote]) { + if (parent = g.posts.get(quote)) { if (!parent.isFetchedQuote && parent.isReply && parent.ID < this.ID) { parents.add(parent.ID); if (!lastParent || parent.ID > lastParent.ID) { @@ -21890,7 +26876,7 @@ QuoteThreading = } else { nodes = []; Unread.order = new RandomAccessList(); - QuoteThreading.inserted = {}; + QuoteThreading.inserted = $.dict(); posts.forEach(function(post) { if (post.isFetchedQuote) { return; @@ -21906,7 +26892,7 @@ QuoteThreading = return delete post.nodes.threadContainer; } }); - $.add(thread.OP.nodes.root.parentNode, nodes); + $.add(thread.nodes.root, nodes); } Unread.position = Unread.order.first; Unread.updatePosition(); @@ -21934,19 +26920,23 @@ QuoteYou = (function() { return Conf['Remember Your Posts'] = enabled; }); $.on(d, 'QRPostSuccessful', function(e) { - var boardID, postID, ref, threadID; - $.forceSync('Remember Your Posts'); - if (Conf['Remember Your Posts']) { + var cb; + cb = PostRedirect.delay(); + return $.get('Remember Your Posts', Conf['Remember Your Posts'], function(items) { + var boardID, postID, ref, threadID; + if (!items['Remember Your Posts']) { + return; + } ref = e.detail, boardID = ref.boardID, threadID = ref.threadID, postID = ref.postID; return QuoteYou.db.set({ boardID: boardID, threadID: threadID, postID: postID, val: true - }); - } + }, cb); + }); }); - if ((ref = g.VIEW) !== 'index' && ref !== 'thread') { + if ((ref = g.VIEW) !== 'index' && ref !== 'thread' && ref !== 'archive') { return; } if (Conf['Highlight Own Posts']) { @@ -21958,22 +26948,30 @@ QuoteYou = (function() { if (Conf['Comment Expansion']) { ExpandComment.callbacks.push(this.node); } - this.text = '\u00A0(You)'; - return Callbacks.Post.push({ + this.mark = $.el('span', { + textContent: '\u00A0(You)', + className: 'qmark-you' + }); + Callbacks.Post.push({ name: 'Mark Quotes of You', cb: this.node }); + return QuoteYou.menu.init(); + }, + isYou: function(post) { + var ref; + return !!((ref = QuoteYou.db) != null ? ref.get({ + boardID: post.boardID, + threadID: post.threadID, + postID: post.ID + }) : void 0); }, node: function() { var i, len, quotelink, ref; if (this.isClone) { return; } - if (QuoteYou.db.get({ - boardID: this.board.ID, - threadID: this.thread.ID, - postID: this.ID - })) { + if (QuoteYou.isYou(this)) { $.addClass(this.nodes.root, 'yourPost'); } if (!this.quotes.length) { @@ -21986,17 +26984,73 @@ QuoteYou = (function() { continue; } if (Conf['Mark Quotes of You']) { - $.add(quotelink, $.tn(QuoteYou.text)); + $.add(quotelink, QuoteYou.mark.cloneNode(true)); } $.addClass(quotelink, 'you'); $.addClass(this.nodes.root, 'quotesYou'); } }, + menu: { + init: function() { + var input, label, ref; + label = $.el('label', { + className: 'toggle-you' + }, {innerHTML: " You"}); + input = $('input', label); + $.on(input, 'change', QuoteYou.menu.toggle); + return (ref = Menu.menu) != null ? ref.addEntry({ + el: label, + order: 80, + open: function(post) { + QuoteYou.menu.post = post.origin || post; + input.checked = QuoteYou.isYou(post); + return true; + } + }) : void 0; + }, + toggle: function() { + var clone, data, i, j, len, len1, post, quotelink, quoter, ref, ref1; + post = QuoteYou.menu.post; + data = { + boardID: post.board.ID, + threadID: post.thread.ID, + postID: post.ID, + val: true + }; + if (this.checked) { + QuoteYou.db.set(data); + } else { + QuoteYou.db["delete"](data); + } + ref = [post].concat(post.clones); + for (i = 0, len = ref.length; i < len; i++) { + clone = ref[i]; + clone.nodes.root.classList.toggle('yourPost', this.checked); + } + ref1 = Get.allQuotelinksLinkingTo(post); + for (j = 0, len1 = ref1.length; j < len1; j++) { + quotelink = ref1[j]; + if (this.checked) { + if (Conf['Mark Quotes of You']) { + $.add(quotelink, QuoteYou.mark.cloneNode(true)); + } + } else { + $.rm($('.qmark-you', quotelink)); + } + quotelink.classList.toggle('you', this.checked); + if ($.hasClass(quotelink, 'quotelink')) { + quoter = Get.postFromNode(quotelink).nodes.root; + quoter.classList.toggle('quotesYou', !!$('.quotelink.you', quoter)); + } + } + } + }, cb: { seek: function(type) { - var highlight, post, posts, result, str; - if (highlight = $('.highlight')) { - $.rmClass(highlight, 'highlight'); + var highlight, highlighted, post, posts, result, str; + highlight = g.SITE.classes.highlight; + if ((highlighted = $("." + highlight))) { + $.rmClass(highlighted, highlight); } if (!(QuoteYou.lastRead && doc.contains(QuoteYou.lastRead) && $.hasClass(QuoteYou.lastRead, 'quotesYou'))) { if (!(post = QuoteYou.lastRead = $('.quotesYou'))) { @@ -22019,15 +27073,22 @@ QuoteYou = (function() { return QuoteYou.cb.scroll(posts[type === 'following' ? 0 : posts.length - 1]); }, scroll: function(root) { - var post; - post = $('.post', root); - if (!post.getBoundingClientRect().height) { + var node, post, sel; + post = Get.postFromRoot(root); + if (!post.nodes.post.getBoundingClientRect().height) { return false; } else { QuoteYou.lastRead = root; - window.location = "#" + post.id; - Header.scrollTo(post); - $.addClass(post, 'highlight'); + location.href = Get.url('post', post); + Header.scrollTo(post.nodes.post); + if (post.isReply) { + sel = "" + g.SITE.selectors.postContainer + g.SITE.selectors.highlightable.reply; + node = post.nodes.root; + if (!node.matches(sel)) { + node = $(sel, node); + } + $.addClass(node, g.SITE.classes.highlight); + } return true; } } @@ -22049,6 +27110,7 @@ Quotify = (function() { if (((ref = g.VIEW) !== 'index' && ref !== 'thread') || !Conf['Resurrect Quotes']) { return; } + $.addClass(doc, 'resurrect-quotes'); if (Conf['Comment Expansion']) { ExpandComment.callbacks.push(this.node); } @@ -22075,16 +27137,16 @@ Quotify = (function() { } }, parseArchivelink: function(link) { - var boardID, m, postID, threadID; + var boardID, m, postID, ref, threadID; if (!(m = link.pathname.match(/^\/([^\/]+)\/thread\/S?(\d+)\/?$/))) { return; } - if (link.hostname === 'boards.4chan.org') { + if ((ref = link.hostname) === 'boards.4chan.org' || ref === 'boards.4channel.org') { return; } boardID = m[1]; threadID = m[2]; - postID = link.hash.match(/^#p?(\d+)$|$/)[1] || threadID; + postID = link.hash.match(/^#[pq]?(\d+)$|$/)[1] || threadID; if (Redirect.to('post', { boardID: boardID, postID: postID @@ -22114,19 +27176,20 @@ Quotify = (function() { } boardID = (m = quote.match(/^>>>\/([a-z\d]+)/)) ? m[1] : this.board.ID; quoteID = boardID + "." + postID; - if (post = g.posts[quoteID]) { + if (post = g.posts.get(quoteID)) { if (!post.isDead) { a = $.el('a', { - href: Build.postURL(boardID, post.thread.ID, postID), + href: g.SITE.Build.postURL(boardID, post.thread.ID, postID), className: 'quotelink', textContent: quote }); } else { a = $.el('a', { - href: Build.postURL(boardID, post.thread.ID, postID), + href: g.SITE.Build.postURL(boardID, post.thread.ID, postID), className: 'quotelink deadlink', - textContent: quote + "\u00A0(Dead)" + textContent: quote }); + $.add(a, Post.deadMark.cloneNode(true)); $.extend(a.dataset, { boardID: boardID, threadID: post.thread.ID, @@ -22147,8 +27210,9 @@ Quotify = (function() { a = $.el('a', { href: redirect || 'javascript:;', className: 'deadlink', - textContent: quote + "\u00A0(Dead)" + textContent: quote }); + $.add(a, Post.deadMark.cloneNode(true)); if (fetchable) { $.addClass(a, 'quotelink'); $.extend(a.dataset, { @@ -22162,7 +27226,7 @@ Quotify = (function() { this.quotes.push(quoteID); } if (!a) { - deadlink.textContent = quote + "\u00A0(Dead)"; + $.add(deadlink, Post.deadMark.cloneNode(true)); return; } $.replace(deadlink, a); @@ -22188,35 +27252,36 @@ Quotify = (function() { }).call(this); Main = (function() { - var Main; + var Main, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; Main = { init: function() { - var db, flatten, items, j, key, len, ref; - if (d.body && !$('title', d.head)) { - return; - } - if (window['4chan X antidup']) { - return; - } - window['4chan X antidup'] = true; - if (location.hostname === 'www.google.com') { - $.get('Captcha Fixes', true, function(arg1) { - var enabled; - enabled = arg1['Captcha Fixes']; - if (enabled) { - return $.ready(function() { - return Captcha.fixes.init(); - }); - } - }); - return; - } + var db, flatten, i, items, j, k, key, len, mountedCB, ref, ref1, ref2, w; + try { + w = window; + if ($.platform === 'crx') { + w = w.wrappedJSObject || w; + } + if ('4chan X antidup' in w) { + return; + } + w['4chan X antidup'] = true; + } catch (error1) {} try { - if (window.frameElement && window.frameElement.src === '') { + if (window.frameElement && ((ref = window.frameElement.src) === '' || ref === 'about:blank')) { return; } - } catch (_error) {} + } catch (error1) {} + if (doc && $.hasClass(doc, 'fourchan-x')) { + return; + } + $.asap(docSet, function() { + $.addClass(doc, 'fourchan-x', 'seaweedchan'); + if ($.engine) { + return $.addClass(doc, "ua-" + $.engine); + } + }); $.on(d, '4chanXInitFinished', function() { if (Main.expectInitFinished) { return delete Main.expectInitFinished; @@ -22225,10 +27290,25 @@ Main = (function() { return $.addClass(doc, 'tainted'); } }); + mountedCB = function() { + var cb, j, len, ref1, results; + d.removeEventListener('mounted', mountedCB, true); + Main.isMounted = true; + ref1 = Main.mountedCBs; + results = []; + for (j = 0, len = ref1.length; j < len; j++) { + cb = ref1[j]; + try { + results.push(cb()); + } catch (error1) {} + } + return results; + }; + d.addEventListener('mounted', mountedCB, true); flatten = function(parent, obj) { var key, val; if (obj instanceof Array) { - Conf[parent] = obj[0]; + Conf[parent] = $.dict.clone(obj[0]); } else if (typeof obj === 'object') { for (key in obj) { val = obj[key]; @@ -22238,77 +27318,95 @@ Main = (function() { Conf[parent] = obj; } }; - flatten(null, Config); - ref = DataBoard.keys; - for (j = 0, len = ref.length; j < len; j++) { - db = ref[j]; - Conf[db] = { - boards: {} - }; + if ((ref1 = location.hostname) === 'boards.4chan.org' || ref1 === 'boards.4channel.org') { + $.global(function() { + var fromCharCode0; + fromCharCode0 = String.fromCharCode; + return String.fromCharCode = function() { + if (document.body) { + String.fromCharCode = fromCharCode0; + } else if (document.currentScript && !document.currentScript.src) { + throw Error(); + } + return fromCharCode0.apply(this, arguments); + }; + }); + $.asap(docSet, function() { + return $.onExists(doc, 'iframe[srcdoc]', $.rm); + }); } + flatten(null, Config); + ref2 = DataBoard.keys; + for (j = 0, len = ref2.length; j < len; j++) { + db = ref2[j]; + Conf[db] = $.dict(); + } + Conf['customTitles'] = $.dict.clone({ + '4chan.org': { + boards: { + 'qa': { + 'boardTitle': { + orig: '/qa/ - Question & Answer', + title: '/qa/ - 2D/Random' + } + } + } + } + }); + Conf['boardConfig'] = { + boards: $.dict() + }; Conf['archives'] = Redirect.archives; - Conf['selectedArchives'] = {}; - Conf['cooldowns'] = {}; - Conf['Index Sort'] = {}; + Conf['selectedArchives'] = $.dict(); + Conf['cooldowns'] = $.dict(); + Conf['Index Sort'] = $.dict(); + for (i = k = 0; k < 2; i = ++k) { + Conf["Last Long Reply Thresholds " + i] = $.dict(); + } + Conf['siteProperties'] = $.dict(); Conf['Except Archives from Encryption'] = false; Conf['JSON Navigation'] = true; Conf['Oekaki Links'] = true; Conf['Show Name and Subject'] = false; Conf['QR Shortcut'] = true; Conf['Bottom QR Link'] = true; - if ($.platform === 'crx') { - $.global(function() { - var k, key, len1, oldFun, ref1, whitelist; - whitelist = document.currentScript.dataset.whitelist; - whitelist = whitelist.split('\n').filter(function(x) { - return x[0] !== "'"; - }); - whitelist.push(location.protocol + "//" + location.host); - oldFun = {}; - ref1 = ['createElement', 'write']; - for (k = 0, len1 = ref1.length; k < len1; k++) { - key = ref1[k]; - oldFun[key] = document[key]; - document[key] = (function(key) { - return function(arg) { - var s; - s = document.currentScript; - if (s && s.src && whitelist.indexOf(s.src.split('/').slice(0, 3).join('/')) < 0) { - throw Error(); - } - return oldFun[key].call(document, arg); - }; - })(key); + Conf['Toggleable Thread Watcher'] = true; + Conf['siteSoftware'] = ''; + Conf['Use Faster Image Host'] = 'true'; + Conf['Captcha Fixes'] = true; + Conf['captchaServiceDomain'] = ''; + Conf['captchaServiceKey'] = $.dict(); + if (/\.4chan(?:nel)?\.org$/.test(location.hostname) && !SW.yotsuba.regexp.pass.test(location.href) && !SW.yotsuba.regexp.captcha.test(location.href) && !$$('script:not([src])', d).filter(function(s) { + return /this\[/.test(s.textContent); + }).length) { + ($.getSync || $.get)({ + 'jsWhitelist': Conf['jsWhitelist'] + }, function(arg) { + var jsWhitelist, parsedList; + jsWhitelist = arg.jsWhitelist; + parsedList = jsWhitelist.replace(/^#.*$/mg, '').replace(/[\s;]+/g, ' ').trim(); + if (/\S/.test(parsedList)) { + return $.addCSP("script-src " + parsedList); } - return document.addEventListener('csp-ready', function() { - var results; - results = []; - for (key in oldFun) { - results.push(document[key] = oldFun[key]); - } - return results; - }, false); - }, { - whitelist: Conf['jsWhitelist'] }); } - items = {}; + items = $.dict(); for (key in Conf) { items[key] = void 0; } items['previousversion'] = void 0; return ($.getSync || $.get)(items, function(items) { - var jsWhitelist, ref1; - jsWhitelist = (ref1 = items['jsWhitelist']) != null ? ref1 : Conf['jsWhitelist']; - $.addCSP("script-src " + (jsWhitelist.replace(/[\s;]+/g, ' '))); - if ($.platform === 'crx') { - $.event('csp-ready'); + var ref3; + if (!$.perProtocolSettings && /\.4chan(?:nel)?\.org$/.test(location.hostname) && ((ref3 = items['Redirect to HTTPS']) != null ? ref3 : Conf['Redirect to HTTPS']) && location.protocol !== 'https:') { + location.replace('https://' + location.host + location.pathname + location.search + location.hash); + return; } return $.asap(docSet, function() { - var ref2, val; + var ref4, val; if ($.cantSet) { } else if (items.previousversion == null) { + Main.isFirstRun = true; Main.ready(function() { $.set('previousversion', g.VERSION); return Settings.open(); @@ -22318,9 +27416,9 @@ Main = (function() { } for (key in Conf) { val = Conf[key]; - Conf[key] = (ref2 = items[key]) != null ? ref2 : val; + Conf[key] = (ref4 = items[key]) != null ? ref4 : val; } - return Main.initFeatures(); + return Site.init(Main.initFeatures); }); }); }, @@ -22332,100 +27430,105 @@ Main = (function() { return $.set(changes, function() { var el, ref; if ((ref = items['Show Updated Notifications']) != null ? ref : true) { - el = $.el('span', { - innerHTML: "4chan X has been updated to version " + E(g.VERSION) + "." - }); + el = $.el('span', {innerHTML: "4chan X has been updated to version " + E(g.VERSION) + "."}); return new Notice('info', el, 15); } }); }, + parseURL: function(site, url) { + var pathname, r, ref; + if (site == null) { + site = g.SITE; + } + if (url == null) { + url = location; + } + r = {}; + if (!site) { + return r; + } + r.siteID = site.ID; + if (typeof site.isBoardlessPage === "function" ? site.isBoardlessPage(url) : void 0) { + return r; + } + pathname = url.pathname.split(/\/+/); + r.boardID = pathname[1]; + if (site.isFileURL(url)) { + r.VIEW = 'file'; + } else if (typeof site.isAuxiliaryPage === "function" ? site.isAuxiliaryPage(url) : void 0) { + + } else if ((ref = pathname[2]) === 'thread' || ref === 'res') { + r.VIEW = 'thread'; + r.threadID = r.THREADID = +pathname[3].replace(/\.\w+$/, ''); + } else if (pathname[2] === 'archive' && pathname[3] === 'res') { + r.VIEW = 'thread'; + r.threadID = r.THREADID = +pathname[4].replace(/\.\w+$/, ''); + r.threadArchived = true; + } else if (/^(?:catalog|archive)(?:\.\w+)?$/.test(pathname[2])) { + r.VIEW = pathname[2].replace(/\.\w+$/, ''); + } else if (/^(?:index|\d*)(?:\.\w+)?$/.test(pathname[2])) { + r.VIEW = 'index'; + } + return r; + }, initFeatures: function() { - var err, feature, hostname, j, len, match, name, pathname, ref, ref1, ref2, ref3, search; - hostname = location.hostname, search = location.search; - pathname = location.pathname.split(/\/+/); - if (hostname !== 'www.4chan.org') { - g.BOARD = new Board(pathname[1]); + var base, err, feature, j, len, name, ref, ref1; + $.global(function() { + document.documentElement.classList.add('js-enabled'); + return window.FCX = {}; + }); + Main.jsEnabled = $.hasClass(doc, 'js-enabled'); + if (typeof $.ajaxPageInit === "function") { + $.ajaxPageInit(); } - if (hostname === 'boards.4chan.org' || hostname === 'sys.4chan.org' || hostname === 'www.4chan.org') { - $.global(function() { - document.documentElement.classList.add('js-enabled'); - return window.FCX = {}; - }); - Main.jsEnabled = $.hasClass(doc, 'js-enabled'); + $.extend(g, Main.parseURL()); + if (g.boardID) { + g.BOARD = new Board(g.boardID); } - switch (hostname) { - case 'www.4chan.org': - $.onExists(doc, 'body', function() { - return $.addStyle(CSS.www); - }); - Captcha.replace.init(); - return; - case 'sys.4chan.org': - if (pathname[2] === 'imgboard.php') { - if (/\bmode=report\b/.test(search)) { - Report.init(); - } else if ((match = search.match(/\bres=(\d+)/))) { - $.ready(function() { - var ref; - if (Conf['404 Redirect'] && ((ref = $.id('errmsg')) != null ? ref.textContent : void 0) === 'Error: Specified thread does not exist.') { - return Redirect.navigate('thread', { - boardID: g.BOARD.ID, - postID: +match[1] - }); - } - }); + if (!g.VIEW) { + if (typeof (base = g.SITE).initAuxiliary === "function") { + base.initAuxiliary(); + } + return; + } + if (g.VIEW === 'file') { + $.asap((function() { + return d.readyState !== 'loading'; + }), function() { + var base1, pathname, video; + if (g.SITE.software === 'yotsuba' && Conf['404 Redirect'] && (typeof (base1 = g.SITE).is404 === "function" ? base1.is404() : void 0)) { + pathname = location.pathname.split(/\/+/); + return Redirect.navigate('file', { + boardID: g.BOARD.ID, + filename: pathname[pathname.length - 1] + }); + } else if (video = $('video')) { + if (Conf['Volume in New Tab']) { + Volume.setup(video); } - } else if (pathname[2] === 'post') { - PostSuccessful.init(); - } - return; - case 'i.4cdn.org': - if (!(pathname[2] && !/s\.jpg$/.test(pathname[2]))) { - return; - } - $.asap((function() { - return d.readyState !== 'loading'; - }), function() { - var ref, video; - if (Conf['404 Redirect'] && ((ref = d.title) === '4chan - Temporarily Offline' || ref === '4chan - 404 Not Found')) { - return Redirect.navigate('file', { - boardID: g.BOARD.ID, - filename: pathname[pathname.length - 1] - }); - } else if (video = $('video')) { - if (Conf['Volume in New Tab']) { - Volume.setup(video); - } - if (Conf['Loop in New Tab']) { - video.loop = true; - video.controls = false; - video.play(); - return ImageCommon.addControls(video); - } + if (Conf['Loop in New Tab']) { + video.loop = true; + video.controls = false; + video.play(); + return ImageCommon.addControls(video); } - }); - return; - } - if ((ref = pathname[2]) === 'thread' || ref === 'res') { - g.VIEW = 'thread'; - g.THREADID = +pathname[3]; - } else if ((ref1 = pathname[2]) === 'catalog' || ref1 === 'archive') { - g.VIEW = pathname[2]; - } else if (pathname[2].match(/^\d*$/)) { - g.VIEW = 'index'; - } else { + } + }); return; } g.threads = new SimpleDict(); g.posts = new SimpleDict(); $.onExists(doc, 'body', Main.initStyle); - ref2 = Main.features; - for (j = 0, len = ref2.length; j < len; j++) { - ref3 = ref2[j], name = ref3[0], feature = ref3[1]; + ref = Main.features; + for (j = 0, len = ref.length; j < len; j++) { + ref1 = ref[j], name = ref1[0], feature = ref1[1]; + if (g.SITE.disabledFeatures && indexOf.call(g.SITE.disabledFeatures, name) >= 0) { + continue; + } try { feature.init(); - } catch (_error) { - err = _error; + } catch (error1) { + err = error1; Main.handleErrors({ message: "\"" + name + "\" initialization crashed.", error: err @@ -22442,13 +27545,11 @@ Main = (function() { if ((ref = $('link[href*=mobile]', d.head)) != null) { ref.disabled = true; } - $.addClass(doc, 'fourchan-x', 'seaweedchan'); + doc.dataset.host = location.host; + $.addClass(doc, "sw-" + g.SITE.software); $.addClass(doc, g.VIEW === 'thread' ? 'thread-view' : g.VIEW); - if ($.engine) { - $.addClass(doc, "ua-" + $.engine); - } - $.onExists(doc, '.ad-cnt', function(ad) { - return $.onExists(ad, 'img', function() { + $.onExists(doc, '.ad-cnt, .adg-rects > .desktop', function(ad) { + return $.onExists(ad, 'img, iframe', function() { return $.addClass(doc, 'ads-loaded'); }); }); @@ -22462,7 +27563,7 @@ Main = (function() { return $.toggleClass(doc, 'autohiding-scrollbar'); } }); - $.addStyle(CSS.boards, 'fourchanx-css'); + $.addStyle(CSS.sub(CSS.boards), 'fourchanx-css'); Main.bgColorStyle = $.el('style', { id: 'fourchanx-bgcolor-css' }); @@ -22481,135 +27582,152 @@ Main = (function() { return Main.setClass(); }, setClass: function() { - var mainStyleSheet, setStyle, style, styleSheets; - if (g.VIEW === 'catalog') { - $.addClass(doc, $.id('base-css').href.match(/catalog_(\w+)/)[1].replace('_new', '').replace(/_+/g, '-')); - return; + var j, knownStyles, len, mainStyleSheet, ref, ref1, setStyle, style, styleSheet, styleSheets; + knownStyles = ['yotsuba', 'yotsuba-b', 'futaba', 'burichan', 'photon', 'tomorrow', 'spooky']; + if (g.SITE.software === 'yotsuba' && g.VIEW === 'catalog') { + if ((mainStyleSheet = $.id('base-css'))) { + style = (ref = mainStyleSheet.href.match(/catalog_(\w+)/)) != null ? ref[1].replace('_new', '').replace(/_+/g, '-') : void 0; + if (indexOf.call(knownStyles, style) >= 0) { + $.addClass(doc, style); + return; + } + } } - style = 'yotsuba-b'; - mainStyleSheet = $('link[title=switch]', d.head); - styleSheets = $$('link[rel="alternate stylesheet"]', d.head); + style = mainStyleSheet = styleSheets = null; setStyle = function() { - var bgColor, div, j, len, styleSheet; - $.rmClass(doc, style); - style = null; - for (j = 0, len = styleSheets.length; j < len; j++) { - styleSheet = styleSheets[j]; - if (styleSheet.href === (mainStyleSheet != null ? mainStyleSheet.href : void 0)) { - style = styleSheet.title.toLowerCase().replace('new', '').trim().replace(/\s+/g, '-'); - break; + var bgColor, css, div, j, len, rgb, s, styleSheet; + if (g.SITE.software === 'yotsuba') { + $.rmClass(doc, style); + style = null; + for (j = 0, len = styleSheets.length; j < len; j++) { + styleSheet = styleSheets[j]; + if (styleSheet.href === (mainStyleSheet != null ? mainStyleSheet.href : void 0)) { + style = styleSheet.title.toLowerCase().replace('new', '').trim().replace(/\s+/g, '-'); + if (style === '_special') { + style = styleSheet.href.match(/[a-z]*(?=[^\/]*$)/)[0]; + } + if (indexOf.call(knownStyles, style) < 0) { + style = null; + } + break; + } + } + if (style) { + $.addClass(doc, style); + $.rm(Main.bgColorStyle); + return; } } - if (style) { - $.addClass(doc, style); - return $.rm(Main.bgColorStyle); - } else { - div = $.el('div', { - className: 'reply' - }); - div.style.cssText = 'position: absolute; visibility: hidden;'; - $.add(d.body, div); - bgColor = window.getComputedStyle(div).backgroundColor; - $.rm(div); - Main.bgColorStyle.textContent = ".dialog, .suboption-list > div:last-of-type {\n background-color: " + bgColor + ";\n}"; - return $.after($.id('fourchanx-css'), Main.bgColorStyle); + div = g.SITE.bgColoredEl(); + div.style.position = 'absolute'; + div.style.visibility = 'hidden'; + $.add(d.body, div); + bgColor = window.getComputedStyle(div).backgroundColor; + $.rm(div); + rgb = bgColor.match(/[\d.]+/g); + if (!/^rgb\(/.test(bgColor)) { + s = window.getComputedStyle(d.body); + bgColor = s.backgroundColor + " " + s.backgroundImage + " " + s.backgroundRepeat + " " + s.backgroundPosition; + } + css = ".dialog, .suboption-list > div:last-of-type, :root.catalog-hover-expand .catalog-container:hover > .post {\n background: " + bgColor + ";\n}\n.unread-mark-read {\n background-color: rgba(" + (rgb.slice(0, 3).join(', ')) + ", " + (0.5 * (rgb[3] || 1)) + ");\n}"; + if ($.luma(rgb) < 100) { + css += ".watch-thread-link {\n background-image: url(\"data:image/svg+xml,\");\n}"; } + Main.bgColorStyle.textContent = css; + return $.after($.id('fourchanx-css'), Main.bgColorStyle); }; - setStyle(); + $.onExists(d.head, g.SITE.selectors.styleSheet, function(el) { + mainStyleSheet = el; + if (g.SITE.software === 'yotsuba') { + styleSheets = $$('link[rel="alternate stylesheet"]', d.head); + } + new MutationObserver(setStyle).observe(mainStyleSheet, { + attributes: true, + attributeFilter: ['href'] + }); + $.on(mainStyleSheet, 'load', setStyle); + return setStyle(); + }); if (!mainStyleSheet) { - return; + ref1 = $$('link[rel="stylesheet"]', d.head); + for (j = 0, len = ref1.length; j < len; j++) { + styleSheet = ref1[j]; + $.on(styleSheet, 'load', setStyle); + } + return setStyle(); } - return new MutationObserver(setStyle).observe(mainStyleSheet, { - attributes: true, - attributeFilter: ['href'] - }); }, initReady: function() { - var msg, ref, ref1, ref2; - if (g.VIEW === 'thread' && (((ref = d.title) === '4chan - Temporarily Offline' || ref === '4chan - 404 Not Found') || ($('.board') && !$('.opContainer')))) { - ThreadWatcher.set404(g.BOARD.ID, g.THREADID, function() { - if (Conf['404 Redirect']) { - return Redirect.navigate('thread', { - boardID: g.BOARD.ID, - threadID: g.THREADID, - postID: +location.hash.match(/\d+/) - }, "/" + g.BOARD + "/"); - } - }); - return; - } - if ((ref1 = d.title) === '4chan - Temporarily Offline' || ref1 === '4chan - 404 Not Found') { + var base, base1, msg; + if (typeof (base = g.SITE).is404 === "function" ? base.is404() : void 0) { + if (g.VIEW === 'thread') { + ThreadWatcher.set404(g.BOARD.ID, g.THREADID, function() { + if (Conf['404 Redirect']) { + return Redirect.navigate('thread', { + boardID: g.BOARD.ID, + threadID: g.THREADID, + postID: +location.hash.match(/\d+/) + }, "/" + g.BOARD + "/"); + } + }); + } return; } - if (((ref2 = g.VIEW) === 'index' || ref2 === 'thread') && !$('.board + *')) { - msg = $.el('div', { - innerHTML: "The page didn't load completely.
              Some features may not work unless you reload." - }); + if (typeof (base1 = g.SITE).isIncomplete === "function" ? base1.isIncomplete() : void 0) { + msg = $.el('div', {innerHTML: "The page didn't load completely.
              Some features may not work unless you reload."}); $.on($('a', msg), 'click', function() { return location.reload(); }); new Notice('warning', msg); } - if (!(Conf['JSON Index'] && g.VIEW === 'index')) { - return Main.initThread(); + if (g.VIEW === 'catalog') { + return Main.initCatalog(); + } else if (!Index.enabled) { + if (g.SITE.awaitBoard) { + return g.SITE.awaitBoard(Main.initThread); + } else { + return Main.initThread(); + } } else { Main.expectInitFinished = true; return $.event('4chanXInitFinished'); } }, initThread: function() { - var board, err, errors, j, k, len, len1, m, postRoot, posts, ref, ref1, scriptData, thread, threadRoot, threads; - if ((board = $('.board'))) { + var base, base1, board, errors, posts, ref, s, threads; + s = g.SITE.selectors; + if ((board = $(((ref = s.boardFor) != null ? ref[g.VIEW] : void 0) || s.board))) { threads = []; posts = []; - ref = $$('.board > .thread', board); - for (j = 0, len = ref.length; j < len; j++) { - threadRoot = ref[j]; - thread = new Thread(+threadRoot.id.slice(1), g.BOARD); - threads.push(thread); - ref1 = $$('.thread > .postContainer', threadRoot); - for (k = 0, len1 = ref1.length; k < len1; k++) { - postRoot = ref1[k]; - if ($('.postMessage', postRoot)) { - try { - posts.push(new Post(postRoot, thread, g.BOARD)); - } catch (_error) { - err = _error; - if (!errors) { - errors = []; - } - errors.push({ - message: "Parsing of Post No." + (postRoot.id.match(/\d+/)) + " failed. Post will be skipped.", - error: err - }); - } - } + errors = []; + try { + if (typeof (base = g.SITE).preParsingFixes === "function") { + base.preParsingFixes(board); } - } - if (errors) { + } catch (error1) {} + Main.addThreadsObserver = new MutationObserver(Main.addThreads); + Main.addPostsObserver = new MutationObserver(Main.addPosts); + Main.addThreadsObserver.observe(board, { + childList: true + }); + Main.parseThreads($$(s.thread, board), threads, posts, errors); + if (errors.length) { Main.handleErrors(errors); } if (g.VIEW === 'thread') { - scriptData = Get.scriptData(); - threads[0].postLimit = /\bbumplimit *= *1\b/.test(scriptData); - threads[0].fileLimit = /\bimagelimit *= *1\b/.test(scriptData); - threads[0].ipCount = (m = scriptData.match(/\bunique_ips *= *(\d+)\b/)) ? +m[1] : void 0; - } - if (g.BOARD.ID === 'f' && g.VIEW === 'thread') { - $.ajax("//a.4cdn.org/f/thread/" + g.THREADID + ".json", { - timeout: $.MINUTE, - onloadend: function() { - if (this.response && posts[0].file) { - return posts[0].file.text.dataset.md5 = posts[0].file.MD5 = this.response.posts[0].md5; - } - } - }); + if (g.threadArchived) { + threads[0].isArchived = true; + threads[0].kill(); + } + if (typeof (base1 = g.SITE).parseThreadMetadata === "function") { + base1.parseThreadMetadata(threads[0]); + } } Main.callbackNodes('Thread', threads); return Main.callbackNodesDB('Post', posts, function() { - var l, len2, post; - for (l = 0, len2 = posts.length; l < len2; l++) { - post = posts[l]; + var j, len, post; + for (j = 0, len = posts.length; j < len; j++) { + post = posts[j]; QuoteThreading.insert(post); } Main.expectInitFinished = true; @@ -22620,6 +27738,189 @@ Main = (function() { return $.event('4chanXInitFinished'); } }, + parseThreads: function(threadRoots, threads, posts, errors) { + var boardID, boardObj, j, len, postRoots, ref, thread, threadID, threadRoot; + for (j = 0, len = threadRoots.length; j < len; j++) { + threadRoot = threadRoots[j]; + boardObj = (boardID = threadRoot.dataset.board) ? (boardID = encodeURIComponent(boardID), g.boards[boardID] || new Board(boardID)) : g.BOARD; + threadID = +threadRoot.id.match(/\d*$/)[0]; + if (!threadID || ((ref = boardObj.threads.get(threadID)) != null ? ref.nodes.root : void 0)) { + return; + } + thread = new Thread(threadID, boardObj); + thread.nodes.root = threadRoot; + threads.push(thread); + postRoots = $$(g.SITE.selectors.postContainer, threadRoot); + if (g.SITE.isOPContainerThread) { + postRoots.unshift(threadRoot); + } + Main.parsePosts(postRoots, thread, posts, errors); + Main.addPostsObserver.observe(threadRoot, { + childList: true + }); + } + }, + parsePosts: function(postRoots, thread, posts, errors) { + var err, j, len, postRoot; + for (j = 0, len = postRoots.length; j < len; j++) { + postRoot = postRoots[j]; + if (!(postRoot.dataset.fullID && g.posts.get(postRoot.dataset.fullID)) && $(g.SITE.selectors.comment, postRoot)) { + try { + posts.push(new Post(postRoot, thread, thread.board)); + } catch (error1) { + err = error1; + errors.push({ + message: "Parsing of Post No." + (postRoot.id.match(/\d+/)) + " failed. Post will be skipped.", + error: err, + html: postRoot.outerHTML + }); + } + } + } + }, + addThreads: function(records) { + var errors, j, k, len, len1, node, posts, record, ref, threadRoots, threads; + threadRoots = []; + for (j = 0, len = records.length; j < len; j++) { + record = records[j]; + ref = record.addedNodes; + for (k = 0, len1 = ref.length; k < len1; k++) { + node = ref[k]; + if (node.nodeType === Node.ELEMENT_NODE && node.matches(g.SITE.selectors.thread)) { + threadRoots.push(node); + } + } + } + if (!threadRoots.length) { + return; + } + threads = []; + posts = []; + errors = []; + Main.parseThreads(threadRoots, threads, posts, errors); + if (errors.length) { + Main.handleErrors(errors); + } + Main.callbackNodes('Thread', threads); + return Main.callbackNodesDB('Post', posts, function() { + return $.event('PostsInserted', null, records[0].target); + }); + }, + addPosts: function(records) { + var anyRemoved, el, errors, j, k, l, len, len1, len2, n, node, postRoots, posts, record, ref, ref1, ref2, thread, threads, threadsRM; + threads = []; + threadsRM = []; + posts = []; + errors = []; + for (j = 0, len = records.length; j < len; j++) { + record = records[j]; + thread = Get.threadFromRoot(record.target); + postRoots = []; + ref = record.addedNodes; + for (k = 0, len1 = ref.length; k < len1; k++) { + node = ref[k]; + if (node.nodeType === Node.ELEMENT_NODE) { + if (node.matches(g.SITE.selectors.postContainer) || (node = $(g.SITE.selectors.postContainer, node))) { + postRoots.push(node); + } + } + } + n = posts.length; + Main.parsePosts(postRoots, thread, posts, errors); + if (posts.length > n && indexOf.call(threads, thread) < 0) { + threads.push(thread); + } + anyRemoved = false; + ref1 = record.removedNodes; + for (l = 0, len2 = ref1.length; l < len2; l++) { + el = ref1[l]; + if (((ref2 = Get.postFromRoot(el)) != null ? ref2.nodes.root : void 0) === el && !doc.contains(el)) { + anyRemoved = true; + break; + } + } + if (anyRemoved && indexOf.call(threadsRM, thread) < 0) { + threadsRM.push(thread); + } + } + if (errors.length) { + Main.handleErrors(errors); + } + return Main.callbackNodesDB('Post', posts, function() { + var len3, len4, m, o; + for (m = 0, len3 = threads.length; m < len3; m++) { + thread = threads[m]; + $.event('PostsInserted', null, thread.nodes.root); + } + for (o = 0, len4 = threadsRM.length; o < len4; o++) { + thread = threadsRM[o]; + $.event('PostsRemoved', null, thread.nodes.root); + } + }); + }, + initCatalog: function() { + var board, errors, s, threads; + s = g.SITE.selectors.catalog; + if (s && (board = $(s.board))) { + threads = []; + errors = []; + Main.addCatalogThreadsObserver = new MutationObserver(Main.addCatalogThreads); + Main.addCatalogThreadsObserver.observe(board, { + childList: true + }); + Main.parseCatalogThreads($$(s.thread, board), threads, errors); + if (errors.length) { + Main.handleErrors(errors); + } + Main.callbackNodes('CatalogThreadNative', threads); + } + Main.expectInitFinished = true; + return $.event('4chanXInitFinished'); + }, + parseCatalogThreads: function(threadRoots, threads, errors) { + var err, j, len, ref, thread, threadRoot; + for (j = 0, len = threadRoots.length; j < len; j++) { + threadRoot = threadRoots[j]; + try { + thread = new CatalogThreadNative(threadRoot); + if (((ref = thread.thread.catalogViewNative) != null ? ref.nodes.root : void 0) !== threadRoot) { + thread.thread.catalogViewNative = thread; + threads.push(thread); + } + } catch (error1) { + err = error1; + errors.push({ + message: "Parsing of Catalog Thread No." + ((threadRoot.dataset.id || threadRoot.id).match(/\d+/)) + " failed. Thread will be skipped.", + error: err, + html: threadRoot.outerHTML + }); + } + } + }, + addCatalogThreads: function(records) { + var errors, j, k, len, len1, node, record, ref, threadRoots, threads; + threadRoots = []; + for (j = 0, len = records.length; j < len; j++) { + record = records[j]; + ref = record.addedNodes; + for (k = 0, len1 = ref.length; k < len1; k++) { + node = ref[k]; + if (node.nodeType === Node.ELEMENT_NODE && node.matches(g.SITE.selectors.catalog.thread)) { + threadRoots.push(node); + } + } + } + if (!threadRoots.length) { + return; + } + threads = []; + errors = []; + Main.parseCatalogThreads(threadRoots, threads, errors); + if (errors.length) { + Main.handleErrors(errors); + } + return Main.callbackNodes('CatalogThreadNative', threads); + }, callbackNodes: function(klass, nodes) { var cb, i, node; i = 0; @@ -22655,11 +27956,21 @@ Main = (function() { return softTask(); }, handleErrors: function(errors) { - var div, error, j, len, logs; + var div, enabled, error, j, len, logs, msg; if (d.body && $.hasClass(d.body, 'fourchan_x') && !$.hasClass(doc, 'tainted')) { new Notice('error', 'Error: Multiple copies of 4chan X are enabled.'); $.addClass(doc, 'tainted'); } + if (g.SITE.testNativeExtension && !$.hasClass(doc, 'tainted')) { + enabled = g.SITE.testNativeExtension().enabled; + if (enabled) { + $.addClass(doc, 'tainted'); + if (Conf['Disable Native Extension'] && !Main.isFirstRun) { + msg = $.el('div', {innerHTML: "Failed to disable the native extension. You may need to block it."}); + new Notice('error', msg); + } + } + } if (!(errors instanceof Array)) { error = errors; } else if (errors.length === 1) { @@ -22669,9 +27980,7 @@ Main = (function() { new Notice('error', Main.parseError(error, Main.reportLink([error])), 15); return; } - div = $.el('div', { - innerHTML: E(errors.length) + " errors occurred." + (Main.reportLink(errors)).innerHTML + " [show]" - }); + div = $.el('div', {innerHTML: E(errors.length) + " errors occurred." + (Main.reportLink(errors)).innerHTML + " [show]"}); $.on(div.lastElementChild, 'click', function() { var ref; return ref = this.textContent === 'show' ? ['hide', false] : ['show', true], this.textContent = ref[0], logs.hidden = ref[1], ref; @@ -22688,9 +27997,7 @@ Main = (function() { parseError: function(data, reportLink) { var context, error, lines, message, ref, ref1; c.error(data.message, data.error.stack); - message = $.el('div', { - innerHTML: E(data.message) + ((reportLink) ? (reportLink).innerHTML : "") - }); + message = $.el('div', {innerHTML: E(data.message) + ((reportLink) ? (reportLink).innerHTML : "")}); error = $.el('div', { textContent: (data.error.name || 'Error') + ": " + (data.error.message || 'see console for details') }); @@ -22701,7 +28008,7 @@ Main = (function() { return [message, error, context]; }, reportLink: function(errors) { - var addDetails, data, details, title, url; + var addDetails, data, details, info, title, url; data = errors[0]; title = data.message; if (errors.length > 1) { @@ -22709,11 +28016,14 @@ Main = (function() { } details = ''; addDetails = function(text) { - if (!(encodeURIComponent(title + details + text + '\n').length > 8110)) { + if (!(encodeURIComponent(title + details + text + '\n').length > 8143)) { return details += text + '\n'; } }; - addDetails("[Please describe the steps needed to reproduce this error.]\n\nScript: 4chan X ccd0 v" + g.VERSION + " " + $.platform + "\nUser agent: " + navigator.userAgent + "\nURL: " + location.href); + addDetails("[Please describe the steps needed to reproduce this error.]\n\nScript: 4chan X ccd0 v" + g.VERSION + " " + $.platform + "\nURL: " + location.href + "\nUser agent: " + navigator.userAgent); + if ($.platform === 'userscript' && (info = typeof GM !== "undefined" && GM !== null ? GM.info : (typeof GM_info !== "undefined" && GM_info !== null ? GM_info : void 0))) { + addDetails("Userscript manager: " + info.scriptHandler + " " + info.version); + } addDetails('\n' + data.error); if (data.error.stack) { addDetails(data.error.stack.replace(data.error.toString(), '').trim()); @@ -22722,15 +28032,12 @@ Main = (function() { addDetails('\n`' + data.html + '`'); } details = details.replace(/file:\/{3}.+\//g, ''); - url = "https://gitreports.com/issue/ccd0/4chan-x?issue_title=" + (encodeURIComponent(title)) + "&details=" + (encodeURIComponent(details)); - return { - innerHTML: " [report]" - }; + url = 'https://github.com/ccd0/4chan-x/issues'.replace('%title', encodeURIComponent(title)).replace('%details', encodeURIComponent(details)); + return {innerHTML: " [report]"}; }, isThisPageLegit: function() { - var ref; if (!('thisPageIsLegit' in Main)) { - Main.thisPageIsLegit = location.hostname === 'boards.4chan.org' && !$('link[href*="favicon-status.ico"]', d.head) && ((ref = d.title) !== '4chan - Temporarily Offline' && ref !== '4chan - Error' && ref !== '504 Gateway Time-out'); + Main.thisPageIsLegit = g.SITE.isThisPageLegit ? g.SITE.isThisPageLegit() : !/^[45]\d\d\b/.test(document.title) && !/\.(?:json|rss)$/.test(location.pathname); } return Main.thisPageIsLegit; }, @@ -22741,7 +28048,15 @@ Main = (function() { } }); }, - features: [['Polyfill', Polyfill], ['Normalize URL', NormalizeURL], ['Captcha Configuration', Captcha.replace], ['Redirect', Redirect], ['Header', Header], ['Catalog Links', CatalogLinks], ['Settings', Settings], ['Index Generator', Index], ['Disable Autoplay', AntiAutoplay], ['Announcement Hiding', PSAHiding], ['Fourchan thingies', Fourchan], ['Color User IDs', IDColor], ['Highlight by User ID', IDHighlight], ['Custom CSS', CustomCSS], ['Thread Links', ThreadLinks], ['Linkify', Linkify], ['Reveal Spoilers', RemoveSpoilers], ['Resurrect Quotes', Quotify], ['Filter', Filter], ['Thread Hiding Buttons', ThreadHiding], ['Reply Hiding Buttons', PostHiding], ['Recursive', Recursive], ['Strike-through Quotes', QuoteStrikeThrough], ['Quick Reply Personas', QR.persona], ['Quick Reply', QR], ['Cooldown', QR.cooldown], ['Pass Link', PassLink], ['Menu', Menu], ['Index Generator (Menu)', Index.menu], ['Report Link', ReportLink], ['Thread Hiding (Menu)', ThreadHiding.menu], ['Reply Hiding (Menu)', PostHiding.menu], ['Delete Link', DeleteLink], ['Filter (Menu)', Filter.menu], ['Edit Link', QR.oekaki.menu], ['Download Link', DownloadLink], ['Archive Link', ArchiveLink], ['Quote Inlining', QuoteInline], ['Quote Previewing', QuotePreview], ['Quote Backlinks', QuoteBacklink], ['Mark Quotes of You', QuoteYou], ['Mark OP Quotes', QuoteOP], ['Mark Cross-thread Quotes', QuoteCT], ['Anonymize', Anonymize], ['Time Formatting', Time], ['Relative Post Dates', RelativeDates], ['File Info Formatting', FileInfo], ['Fappe Tyme', FappeTyme], ['Gallery', Gallery], ['Gallery (menu)', Gallery.menu], ['Sauce', Sauce], ['Image Expansion', ImageExpand], ['Image Expansion (Menu)', ImageExpand.menu], ['Reveal Spoiler Thumbnails', RevealSpoilers], ['Image Loading', ImageLoader], ['Image Hover', ImageHover], ['Volume Control', Volume], ['WEBM Metadata', Metadata], ['Comment Expansion', ExpandComment], ['Thread Expansion', ExpandThread], ['Favicon', Favicon], ['Unread', Unread], ['Quote Threading', QuoteThreading], ['Thread Stats', ThreadStats], ['Thread Updater', ThreadUpdater], ['Thread Watcher', ThreadWatcher], ['Thread Watcher (Menu)', ThreadWatcher.menu], ['Mark New IPs', MarkNewIPs], ['Index Navigation', Nav], ['Keybinds', Keybinds], ['Banner', Banner], ['Flash Features', Flash], ['Reply Pruning', ReplyPruning]] + mounted: function(cb) { + if (Main.isMounted) { + return cb(); + } else { + return Main.mountedCBs.push(cb); + } + }, + mountedCBs: [], + features: [['Polyfill', Polyfill], ['Board Configuration', BoardConfig], ['Normalize URL', NormalizeURL], ['Delay Redirect on Post', PostRedirect], ['Captcha Configuration', Captcha.replace], ['Image Host Rewriting', ImageHost], ['Redirect', Redirect], ['Header', Header], ['Catalog Links', CatalogLinks], ['Settings', Settings], ['Index Generator', Index], ['Disable Autoplay', AntiAutoplay], ['Announcement Hiding', PSAHiding], ['Fourchan thingies', Fourchan], ['Tinyboard Glue', Tinyboard], ['Color User IDs', IDColor], ['Highlight by User ID', IDHighlight], ['Count Posts by ID', IDPostCount], ['Custom CSS', CustomCSS], ['Thread Links', ThreadLinks], ['Linkify', Linkify], ['Reveal Spoilers', RemoveSpoilers], ['Resurrect Quotes', Quotify], ['Filter', Filter], ['Thread Hiding Buttons', ThreadHiding], ['Reply Hiding Buttons', PostHiding], ['Recursive', Recursive], ['Strike-through Quotes', QuoteStrikeThrough], ['Quick Reply Personas', QR.persona], ['Quick Reply', QR], ['Cooldown', QR.cooldown], ['Post Jumper', PostJumper], ['Pass Link', PassLink], ['Menu', Menu], ['Index Generator (Menu)', Index.menu], ['Report Link', ReportLink], ['Copy Text Link', CopyTextLink], ['Thread Hiding (Menu)', ThreadHiding.menu], ['Reply Hiding (Menu)', PostHiding.menu], ['Delete Link', DeleteLink], ['Filter (Menu)', Filter.menu], ['Edit Link', QR.oekaki.menu], ['Download Link', DownloadLink], ['Archive Link', ArchiveLink], ['Quote Inlining', QuoteInline], ['Quote Previewing', QuotePreview], ['Quote Backlinks', QuoteBacklink], ['Mark Quotes of You', QuoteYou], ['Mark OP Quotes', QuoteOP], ['Mark Cross-thread Quotes', QuoteCT], ['Anonymize', Anonymize], ['Time Formatting', Time], ['Relative Post Dates', RelativeDates], ['File Info Formatting', FileInfo], ['Fappe Tyme', FappeTyme], ['Gallery', Gallery], ['Gallery (menu)', Gallery.menu], ['Sauce', Sauce], ['Image Expansion', ImageExpand], ['Image Expansion (Menu)', ImageExpand.menu], ['Reveal Spoiler Thumbnails', RevealSpoilers], ['Image Loading', ImageLoader], ['Image Hover', ImageHover], ['Volume Control', Volume], ['WEBM Metadata', Metadata], ['Comment Expansion', ExpandComment], ['Thread Expansion', ExpandThread], ['Favicon', Favicon], ['Unread', Unread], ['Unread Line in Index', UnreadIndex], ['Quote Threading', QuoteThreading], ['Thread Stats', ThreadStats], ['Thread Updater', ThreadUpdater], ['Thread Watcher', ThreadWatcher], ['Thread Watcher (Menu)', ThreadWatcher.menu], ['Mark New IPs', MarkNewIPs], ['Index Navigation', Nav], ['Keybinds', Keybinds], ['Banner', Banner], ['Announcements', PSA], ['Flash Features', Flash], ['Reply Pruning', ReplyPruning], ['Mod Contact Links', ModContact]] }; return Main; diff --git a/builds/4chan-X.zip b/builds/4chan-X.zip index 890b3ce301..d72d4c5766 100644 Binary files a/builds/4chan-X.zip and b/builds/4chan-X.zip differ diff --git a/builds/updates-beta.json b/builds/updates-beta.json new file mode 100644 index 0000000000..8e1f4e1718 --- /dev/null +++ b/builds/updates-beta.json @@ -0,0 +1,12 @@ +{ + "addons": { + "4chan-x@4chan-x.net": { + "updates": [ + { + "version": "1.14.23.1", + "update_link": "https://www.4chan-x.net/builds/4chan-X-beta.crx" + } + ] + } + } +} diff --git a/builds/updates-beta.xml b/builds/updates-beta.xml index 271906efb2..e1765e476b 100644 --- a/builds/updates-beta.xml +++ b/builds/updates-beta.xml @@ -1,7 +1,7 @@ - + diff --git a/builds/updates.json b/builds/updates.json new file mode 100644 index 0000000000..e58ec859bb --- /dev/null +++ b/builds/updates.json @@ -0,0 +1,12 @@ +{ + "addons": { + "4chan-x@4chan-x.net": { + "updates": [ + { + "version": "1.14.23.1", + "update_link": "https://www.4chan-x.net/builds/4chan-X.crx" + } + ] + } + } +} diff --git a/builds/updates.xml b/builds/updates.xml index 8b6d4f4ebe..0a2a8e8201 100644 --- a/builds/updates.xml +++ b/builds/updates.xml @@ -1,7 +1,7 @@ - + diff --git a/crx-chromium-version.txt b/crx-chromium-version.txt new file mode 100644 index 0000000000..2d4f0bd7a1 --- /dev/null +++ b/crx-chromium-version.txt @@ -0,0 +1 @@ +Chromium 73.0.3683.75 built on Debian buster/sid, running on Debian buster/sid diff --git a/index.html b/index.html index a51129a3be..32db078d7f 100644 --- a/index.html +++ b/index.html @@ -12,25 +12,26 @@

              4chan X

              Source Code Changelog FAQ -Report Bugs +Privacy +Report Bugs Screenshot -

              Adds various features to 4chan. -Previously developed by aeosynth, Mayhem, ihavenoface, Zixaphir, Seaweed, and Spittie, with contributions from many others.

              +

              4chan X is a script that adds various features to anonymous imageboards. It was originally developed for 4chan but has no affiliation with it.

              +

              It was previously developed by aeosynth, Mayhem, ihavenoface, Zixaphir, Seaweed, and Spittie, with contributions from many others.

              If you're looking for a maintained fork of OneeChan (a style script used in addition to 4chan X), try -https://github.com/Nebukazar/OneeChan.

              +https://github.com/KevinParnell/OneeChan.

              Please note

              -

              Uninstalling: 4chan X disables the native extension, so if you uninstall 4chan X, you'll need to re-enable it. To do this, click the [Settings] link in the top right corner, uncheck "Disable the native extension" in the panel that appears, and click the "Save Settings" button.

              -

              Private browsing: 4chan X does not yet support private browsing / incognito mode. Although it may work in this mode, browsing data recorded by 4chan X, such as your last read post in a thread and which posts are yours, will still need to be cleared manually by resetting your settings. To control what browsing data 4chan X records, use the Remember Last Read Post and Mark Quotes of You options in the settings panel.

              -

              HTTPS: 4chan X currently shares your settings and post history between the HTTP and HTTPS versions of 4chan. If you are concerned about protecting your privacy against a man-in-the-middle attack, you should disable 4chan X on the HTTP version of 4chan and/or install HTTPS Everywhere.

              +

              Uninstalling: 4chan X disables the native extension, so if you uninstall 4chan X, you'll need to re-enable it. To do this, click the [Settings] link in the top right corner, uncheck "Disable the native extension" in the panel that appears, and click the "Save Settings" button. If you don't see a "Save Settings" button, it may be being hidden by your ad blocker.

              +

              Private browsing: By default, 4chan X remembers your last read post in a thread and which posts were made by you, even if you are in private browsing / incognito mode. If you want to turn this off, uncheck the Remember Last Read Post and Remember Your Posts options in the settings panel. You can clear all 4chan browsing history saved by 4chan X by resetting your settings.

              +

              Use of the "Link Title" feature to fetch titles of Youtube links is subject to Youtube's Terms of Service and Privacy Policy. For more details on what information is sent to Youtube and other sites, and how to turn it off if you don't want the feature, see 4chan X's privacy documentation.

              Install

              -

              Install Greasemonkey, then click here to install 4chan X.

              +

              Install Violentmonkey, Tampermonkey, or Greasemonkey (issues since v4: #2526, #2576), then click here to install 4chan X.

              Ports of Greasemonkey are available for SeaMonkey and Pale Moon.

              -

              Userscript: Install Violentmonkey (Opera store / Chrome store) or Tampermonkey, then click here to install 4chan X.

              +

              Userscript: Install Violentmonkey or Tampermonkey, then click here to install 4chan X.

              Chrome extension: 4chan X is also available as a standalone Chrome extension. The Chrome extension has the additional feature of being able to sync your settings and data with other devices via Chrome Sync. But there is an issue when the script updates: Whenever the Chrome extension is updated, until you hard refresh (F5) the tab, 4chan X is unable to save any data (such as posts marked as yours and settings changes). The userscript version above does not have this problem when 4chan X updates, only when Violentmonkey / Tampermonkey is updated. To install as a Chrome extension:

              • Chromium, Vivaldi: Download 4chan X, then open chrome://extensions and drag the downloaded file onto the page. Alternatively, you can install 4chan X from the Chrome store.
              • @@ -39,14 +40,14 @@

                Install

              Note: This version of 4chan X does not work with Opera 12. If you need Opera 12 support, try loadletter's fork instead.

              -

              Several WebKitGTK+ based browsers have support for userscripts and can run 4chan X. Due to the lack of the cross-site GM_* API, and lack of support for userscripts in iframes, not all features will work. You may experience crashes when repeatedly solving the default image-based captchas. You can avoid this problem by enabling Use Recaptcha v1 in your settings.

              +

              Install the Userscripts extension. Enable it by pressing ⌘,, navigating to the extensions pane and checking Userscripts checkbox. Now open the Userscripts editor by clicking on the </> button in the taskbar. Then click on the + button and select the New Javascript option. Replace the default text with the contents of the 4chan X script. Finally save it by pressing ⌘s.

              +

              +

              Several minimal browsers have support for userscripts and can run 4chan X. Due to the lack of the cross-site GM_* API, and lack of support for userscripts in iframes, not all features will work. You may experience crashes when repeatedly solving the default image-based captchas. You can avoid this problem by enabling Use Recaptcha v1 in your settings.

              • dwb: Install the userscripts extension, then save the script to the $XDG_CONFIG_HOME/dwb/greasemonkey or $HOME/.config/dwb/greasemonkey directory (creating it if necessary):

                -
                  dwbem -N -i userscripts
                -  wget -P ${XDG_CONFIG_HOME:-$HOME/.config}/dwb/greasemonkey https://www.4chan-x.net/builds/4chan-X.user.js
                +
                dwbem -N -i userscripts
                +wget -P ${XDG_CONFIG_HOME:-$HOME/.config}/dwb/greasemonkey https://www.4chan-x.net/builds/4chan-X.user.js
                 
              • @@ -56,22 +57,29 @@

                Install

                Luakit: Navigate to the script, then type the command :usi to install it.

              • -

                uzbl: Install the script from https://github.com/singpolyma/singpolyma/blob/master/uzbl/data/scripts/userscript.sh, enable it in your config file, and then save 4chan X to $XDG_DATA_HOME/uzbl/userscripts (or $HOME/.local/share/uzbl/userscripts).

                -
                  wget -P ${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/scripts https://raw.githubusercontent.com/singpolyma/singpolyma/master/uzbl/data/scripts/userscript.sh
                -  chmod +x ${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/scripts/userscript.sh
                -  echo '@on_event LOAD_COMMIT spawn @scripts_dir/userscript.sh document-start' >> ${XDG_CONFIG_HOME:-$HOME/.config}/uzbl/config
                -  echo '@on_event LOAD_FINISH spawn @scripts_dir/userscript.sh document-end'   >> ${XDG_CONFIG_HOME:-$HOME/.config}/uzbl/config
                -  wget -P ${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/userscripts https://www.4chan-x.net/builds/4chan-X.user.js
                +

                uzbl: Install the script from https://github.com/singpolyma/singpolyma/blob/master/uzbl/data/scripts/userscript.sh, enable it in your config file, and then save 4chan X to $XDG_DATA_HOME/uzbl/userscripts (or $HOME/.local/share/uzbl/userscripts). The commands below assume you have run uzbl at least once to create its config file.

                +
                wget -P "${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/scripts" https://raw.githubusercontent.com/singpolyma/singpolyma/master/uzbl/data/scripts/userscript.sh
                +chmod +x "${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/scripts/userscript.sh"
                +echo '@on_event LOAD_COMMIT spawn @scripts_dir/userscript.sh document-start' >> "${XDG_CONFIG_HOME:-$HOME/.config}/uzbl/config"
                +echo '@on_event LOAD_FINISH spawn @scripts_dir/userscript.sh document-end'   >> "${XDG_CONFIG_HOME:-$HOME/.config}/uzbl/config"
                +wget -P "${XDG_DATA_HOME:-$HOME/.local/share}/uzbl/userscripts" https://www.4chan-x.net/builds/4chan-X.user.js
                +
                +
              • +
              • +

                qutebrowser: Save the script to the $XDG_DATA_HOME/qutebrowser/greasemonkey or $HOME/.local/share/qutebrowser/greasemonkey directory:

                +
                wget -P ${XDG_DATA_HOME:-$HOME/.local/share}/qutebrowser/greasemonkey https://www.4chan-x.net/builds/4chan-X.user.js
                 
              +

              -

              4chan X can be used in some browsers that do not support userscripts, such as Microsoft Edge, using a local proxy. Not all features will work.

              +

              4chan X can be used in some browsers that do not support userscripts using a local proxy. Not all features will work.

              Beta version

              New features and non-urgent bugfixes are released on the beta channel for further testing before they are moved the stable version. Please report any issues you find, and be sure to mention which version you're using. You should back up your settings regularly to prevent them from being lost due to bugs.

              To install the beta version and get updates whenever there's a new beta version:

              To install the current beta version but get updates from the stable channel (for example, if just you want a particular recent feature):

              @@ -80,7 +88,7 @@

              Install

            • Download Chrome extension
            • Troubleshooting

              -

              If you encounter a bug, try the steps here, then report it to the issue tracker. You can report bugs without a Github account via this form. If the bug seems to be caused by a script update, you can install a old version from the changelog.

              +

              If you encounter a bug, try the steps here, then report it to the issue tracker. If the bug seems to be caused by a script update, you can install a old version from the changelog.

              ') %> - el.src = E.url content - el + style: '' + el: do -> + counter = 0 + (a) -> + el = $.el 'pre', + hidden: true + id: "gist-embed-#{counter++}" + CrossOrigin.cache "https://api.github.com/gists/#{a.dataset.uid}", -> + el.textContent = Object.values(@response.files)[0].content + el.className = 'prettyprint' + $.global -> + window.prettyPrint? (() ->), document.getElementById(document.currentScript.dataset.id).parentNode + , id: el.id + el.hidden = false + el title: api: (uid) -> "https://api.github.com/gists/#{uid}" text: ({files}) -> @@ -245,16 +321,15 @@ Embedding = src: "https://paste.installgentoo.com/view/embed/#{a.dataset.uid}" , key: 'LiveLeak' - regExp: /^\w+:\/\/(?:\w+\.)?liveleak\.com\/.*\?.*i=(\w+)/ - httpOnly: true + regExp: /^\w+:\/\/(?:\w+\.)?liveleak\.com\/.*\?.*[tif]=(\w+)/ el: (a) -> el = $.el 'iframe', - src: "http://www.liveleak.com/ll_embed?i=#{a.dataset.uid}", + src: "https://www.liveleak.com/e/#{a.dataset.uid}", el.setAttribute "allowfullscreen", "true" el , key: 'Loopvid' - regExp: /^\w+:\/\/(?:www\.)?loopvid.appspot.com\/#?((?:pf|kd|lv|gd|gh|db|dx|nn|cp|wu|ig|ky|mf|m2|pc|1c|pi|wl|ko|gc)\/[\w\-\/]+(?:,[\w\-\/]+)*|fc\/\w+\/\d+|https?:\/\/.+)/ + regExp: /^\w+:\/\/(?:www\.)?loopvid.appspot.com\/#?((?:pf|kd|lv|gd|gh|db|dx|nn|cp|wu|ig|ky|mf|m2|pc|1c|pi|ni|wl|ko|mm|ic|gc)\/[\w\-\/]+(?:,[\w\-\/]+)*|fc\/\w+\/\d+|https?:\/\/.+)/ style: 'max-width: 80vw; max-height: 80vh;' el: (a) -> el = $.el 'video', @@ -275,39 +350,42 @@ Embedding = urls = switch host # list from src/common.py at http://loopvid.appspot.com/source.html when 'pf' then ["https://kastden.org/_loopvid_media/pf/#{base}", "https://web.archive.org/web/2/http://a.pomf.se/#{base}"] - when 'kd' then ["http://kastden.org/loopvid/#{base}"] - when 'lv' then ["http://lv.kastden.org/#{base}"] + when 'kd' then ["https://kastden.org/loopvid/#{base}"] + when 'lv' then ["https://lv.kastden.org/#{base}"] when 'gd' then ["https://docs.google.com/uc?export=download&id=#{base}"] when 'gh' then ["https://googledrive.com/host/#{base}"] when 'db' then ["https://dl.dropboxusercontent.com/u/#{base}"] when 'dx' then ["https://dl.dropboxusercontent.com/#{base}"] - when 'nn' then ["http://naenara.eu/loopvids/#{base}"] + when 'nn' then ["https://kastden.org/_loopvid_media/nn/#{base}"] when 'cp' then ["https://copy.com/#{base}"] when 'wu' then ["http://webmup.com/#{base}/vid.webm"] when 'ig' then ["https://i.imgur.com/#{base}"] - when 'ky' then ["https://kiyo.me/#{base}"] + when 'ky' then ["https://kastden.org/_loopvid_media/ky/#{base}"] when 'mf' then ["https://kastden.org/_loopvid_media/mf/#{base}", "https://web.archive.org/web/2/https://d.maxfile.ro/#{base}"] when 'm2' then ["https://kastden.org/_loopvid_media/m2/#{base}"] - when 'pc' then ["http://a.pomf.cat/#{base}"] + when 'pc' then ["https://kastden.org/_loopvid_media/pc/#{base}", "https://web.archive.org/web/2/http://a.pomf.cat/#{base}"] when '1c' then ["http://b.1339.cf/#{base}"] - when 'pi' then ["https://u.pomf.is/#{base}"] + when 'pi' then ["https://kastden.org/_loopvid_media/pi/#{base}", "https://web.archive.org/web/2/https://u.pomf.is/#{base}"] + when 'ni' then ["https://kastden.org/_loopvid_media/ni/#{base}", "https://web.archive.org/web/2/https://u.nya.is/#{base}"] when 'wl' then ["http://webm.land/media/#{base}"] when 'ko' then ["https://kordy.kastden.org/loopvid/#{base}"] - when 'fc' then ["//i.4cdn.org/#{base}.webm"] + when 'mm' then ["https://kastden.org/_loopvid_media/mm/#{base}", "https://web.archive.org/web/2/https://my.mixtape.moe/#{base}"] + when 'ic' then ["https://media.8ch.net/file_store/#{base}"] + when 'fc' then ["//#{ImageHost.host()}/#{base}.webm"] when 'gc' then ["https://#{type}.gfycat.com/#{name}.webm"] + for url in urls $.add el, $.el 'source', src: url el , key: 'Openings.moe' - regExp: /^\w+:\/\/openings.moe\/\?video=([^&=]+\.webm)/ - style: 'max-width: 80vw; max-height: 80vh;' + regExp: /^\w+:\/\/openings.moe\/\?video=([^.&=]+)/ + style: 'width: 1280px; height: 720px; max-width: 80vw; max-height: 80vh;' el: (a) -> - $.el 'video', - controls: true - preload: 'auto' - src: "//openings.moe/video/#{a.dataset.uid}" - loop: true + el = $.el 'iframe', + src: "https://openings.moe/?video=#{a.dataset.uid}", + el.setAttribute "allowfullscreen", "true" + el , key: 'Pastebin' regExp: /^\w+:\/\/(?:\w+\.)?pastebin\.com\/(?!u\/)(?:[\w.]+(?:\/|\?i\=))?(\w+)/ @@ -322,7 +400,7 @@ Embedding = $.el 'iframe', src: "https://w.soundcloud.com/player/?visual=true&show_comments=false&url=https%3A%2F%2Fsoundcloud.com%2F#{encodeURIComponent a.dataset.uid}" title: - api: (uid) -> "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F#{encodeURIComponent uid}" + api: (uid) -> "#{location.protocol}//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F#{encodeURIComponent uid}" text: (_) -> _.title , key: 'StrawPoll' @@ -330,25 +408,65 @@ Embedding = style: 'border: 0; width: 600px; height: 406px;' el: (a) -> $.el 'iframe', - src: "//www.strawpoll.me/embed_1/#{a.dataset.uid}" + src: "https://www.strawpoll.me/embed_1/#{a.dataset.uid}" + , + key: 'Streamable' + regExp: /^\w+:\/\/(?:www\.)?streamable\.com\/(\w+)/ + el: (a) -> + el = $.el 'iframe', + src: "https://streamable.com/o/#{a.dataset.uid}" + el.setAttribute "allowfullscreen", "true" + el + title: + api: (uid) -> "https://api.streamable.com/oembed?url=https://streamable.com/#{uid}" + text: (_) -> _.title , key: 'TwitchTV' - regExp: /^\w+:\/\/(?:www\.|secure\.)?twitch\.tv\/(\w[^#\&\?]*)/ + regExp: /^\w+:\/\/(?:www\.|secure\.|clips\.|m\.)?twitch\.tv\/(\w[^#\&\?]*)/ el: (a) -> - m = a.dataset.uid.match /(\w+)(?:\/v\/(\d+))?/ - url = "//player.twitch.tv/?#{if m[2] then "video=v#{m[2]}" else "channel=#{m[1]}"}&autoplay=false" - if (time = a.dataset.href.match /\bt=(\w+)/) - url += "&time=#{time[1]}" + m = a.dataset.href.match /^\w+:\/\/(?:(clips\.)|\w+\.)?twitch\.tv\/(?:\w+\/)?(clip\/)?(\w[^#\&\?]*)/; + if m[1] or m[2] + url = "//clips.twitch.tv/embed?clip=#{m[3]}&parent=#{location.hostname}" + else + m = a.dataset.uid.match /(\w+)(?:\/(?:v\/)?(\d+))?/ + url = "//player.twitch.tv/?#{if m[2] then "video=v#{m[2]}" else "channel=#{m[1]}"}&autoplay=false&parent=#{location.hostname}" + if (time = a.dataset.href.match /\bt=(\w+)/) + url += "&time=#{time[1]}" el = $.el 'iframe', src: url el.setAttribute "allowfullscreen", "true" el , key: 'Twitter' - regExp: /^\w+:\/\/(?:www\.)?twitter\.com\/(\w+\/status\/\d+)/ + regExp: /^\w+:\/\/(?:www\.|mobile\.)?twitter\.com\/(\w+\/status\/\d+)/ + style: 'border: none; width: 550px; height: 250px; overflow: hidden; resize: both;' el: (a) -> - $.el 'iframe', - src: "https://twitframe.com/show?url=https://twitter.com/#{a.dataset.uid}" + el = $.el 'iframe' + $.on el, 'load', -> + @contentWindow.postMessage {element: 't', query: 'height'}, 'https://twitframe.com' + onMessage = (e) -> + if e.source is el.contentWindow and e.origin is 'https://twitframe.com' + $.off window, 'message', onMessage + (cont or el).style.height = "#{+$.minmax(e.data.height, 250, 0.8 * doc.clientHeight)}px" + $.on window, 'message', onMessage + el.src = "https://twitframe.com/show?url=https://twitter.com/#{a.dataset.uid}" + if $.engine is 'gecko' + # XXX https://bugzilla.mozilla.org/show_bug.cgi?id=680823 + el.style.cssText = 'border: none; width: 100%; height: 100%;' + cont = $.el 'div' + $.add cont, el + cont + else + el + , + key: 'VidLii' + regExp: /^\w+:\/\/(?:www\.)?vidlii\.com\/watch\?v=(\w{11})/ + style: 'border: none; width: 640px; height: 392px;' + el: (a) -> + el = $.el 'iframe', + src: "https://www.vidlii.com/embed?v=#{a.dataset.uid}&a=0" + el.setAttribute "allowfullscreen", "true" + el , key: 'Vimeo' regExp: /^\w+:\/\/(?:www\.)?vimeo\.com\/(\d+)/ @@ -369,18 +487,18 @@ Embedding = src: "https://vine.co/v/#{a.dataset.uid}/card" , key: 'Vocaroo' - regExp: /^\w+:\/\/(?:www\.)?vocaroo\.com\/i\/(\w+)/ + regExp: /^\w+:\/\/(?:(?:www\.|old\.)?vocaroo\.com|voca\.ro)\/((?:i\/)?\w+)/ style: '' el: (a) -> - el = $.el 'audio', - controls: true - preload: 'auto' - type = if el.canPlayType 'audio/webm' then 'webm' else 'mp3' - el.src = "http://vocaroo.com/media_command.php?media=#{a.dataset.uid}&command=download_#{type}" + el = $.el 'iframe' + el.width = 300 + el.height = 60 + el.setAttribute('frameborder', 0) + el.src = "https://vocaroo.com/embed/#{a.dataset.uid.replace(/^i\//, '')}?autoplay=0" el , key: 'YouTube' - regExp: /^\w+:\/\/(?:youtu.be\/|[\w.]*youtube[\w.]*\/.*(?:v=|\bembed\/|\bv\/))([\w\-]{11})(.*)/ + regExp: /^\w+:\/\/(?:youtu.be\/|[\w.]*youtube[\w.]*\/.*(?:v=|\bembed\/|\bv\/|live\/|shorts\/))([\w\-]{11})(.*)/ el: (a) -> start = a.dataset.options.match /\b(?:star)?t\=(\w+)/ start = start[1] if start @@ -388,19 +506,19 @@ Embedding = start += ' 0h0m0s' start = 3600 * start.match(/(\d+)h/)[1] + 60 * start.match(/(\d+)m/)[1] + 1 * start.match(/(\d+)s/)[1] el = $.el 'iframe', - src: "//www.youtube.com/embed/#{a.dataset.uid}?wmode=opaque#{if start then '&start=' + start else ''}" + src: "//www.youtube.com/embed/#{a.dataset.uid}?rel=0&wmode=opaque#{if start then '&start=' + start else ''}" el.setAttribute "allowfullscreen", "true" el title: - batchSize: 50 - api: (uids) -> - ids = encodeURIComponent uids.join(',') - key = '<%= meta.youtubeAPIKey %>' - "https://www.googleapis.com/youtube/v3/videos?part=snippet&id=#{ids}&fields=items%28id%2Csnippet%28title%29%29&key=#{key}" - text: (data, uid) -> - for item in data.items when item.id is uid - return item.snippet.title - 'Not Found' + api: (uid) -> "https://www.youtube.com/oembed?url=https%3A//www.youtube.com/watch%3Fv%3D#{uid}&format=json" + text: (_) -> _.title + status: (_) -> + if _.error + m = _.error.match(/^(\d*)\s*(.*)/) + [+m[1], m[2]] + else + [200, 'OK'] + preview: + url: (uid) -> "https://img.youtube.com/vi/#{uid}/0.jpg" + height: 360 ] - -return Embedding diff --git a/src/Linkification/Linkify.coffee b/src/Linkification/Linkify.coffee index 5307b06d21..8c2ffa4d17 100644 --- a/src/Linkification/Linkify.coffee +++ b/src/Linkification/Linkify.coffee @@ -1,6 +1,6 @@ Linkify = init: -> - return if g.VIEW not in ['index', 'thread'] or not Conf['Linkify'] + return if g.VIEW not in ['index', 'thread', 'archive'] or not Conf['Linkify'] if Conf['Comment Expansion'] ExpandComment.callbacks.push @node @@ -9,26 +9,20 @@ Linkify = name: 'Linkify' cb: @node - Callbacks.CatalogThread.push - name: 'Linkify' - cb: @catalogNode - Embedding.init() node: -> return Embedding.events @ if @isClone return unless Linkify.regString.test @info.comment - for link in $$ 'a[href^="http://i.4cdn.org/"], a[href^="https://i.4cdn.org/"]', @nodes.comment + for link in $$ 'a', @nodes.comment when g.SITE.isLinkified?(link) $.addClass link, 'linkify' + ImageHost.fixLinks [link] if ImageHost.useFaster Embedding.process link, @ links = Linkify.process @nodes.comment + ImageHost.fixLinks links if ImageHost.useFaster Embedding.process link, @ for link in links return - catalogNode: -> - return unless Linkify.regString.test @thread.OP.info.comment - Linkify.process @nodes.comment - process: (node) -> test = /[^\s"]+/g space = /[\s"]/ @@ -48,7 +42,7 @@ Linkify = test.lastIndex = 0 while (saved = snapshot.snapshotItem i++) - if saved.nodeName is 'BR' + if saved.nodeName is 'BR' or (saved.parentElement.nodeName is 'P' and !saved.previousSibling) if ( # link deliberately split (part1 = word.match /(https?:\/\/)?([a-z\d-]+\.)*[a-z\d-]+$/i) and @@ -59,6 +53,9 @@ Linkify = else break + if saved.parentElement.nodeName is "A" and not Linkify.regString.test(word) + break + endNode = saved {data} = saved @@ -74,7 +71,10 @@ Linkify = if Linkify.regString.test word links.push Linkify.makeRange node, endNode, index, length - <% if (readJSON('/.tests_enabled')) { %><%= assert('word is links[links.length-1].toString()') %><% } %> + <% if (readJSON('/.tests_enabled')) { %> + Test.assert -> + word is links[links.length-1].toString() + <% } %> break unless test.lastIndex and node is endNode @@ -155,5 +155,3 @@ Linkify = range.insertNode a a - -return Linkify diff --git a/src/Menu/ArchiveLink.coffee b/src/Menu/ArchiveLink.coffee index a2db55abc8..3a31025c66 100644 --- a/src/Menu/ArchiveLink.coffee +++ b/src/Menu/ArchiveLink.coffee @@ -1,13 +1,13 @@ ArchiveLink = init: -> - return unless g.VIEW in ['index', 'thread'] and Conf['Menu'] and Conf['Archive Link'] + return unless g.SITE.software is 'yotsuba' and g.VIEW in ['index', 'thread'] and Conf['Menu'] and Conf['Archive Link'] div = $.el 'div', textContent: 'Archive' entry = el: div - order: 90 + order: 60 open: ({ID, thread, board}) -> !!Redirect.to 'thread', {postID: ID, threadID: thread.ID, boardID: board.ID} subEntries: [] @@ -38,15 +38,19 @@ ArchiveLink = true else (post) -> + typeParam = if type is 'country' and post.info.flagCodeTroll + 'troll_country' + else + type value = if type is 'country' - post.info.flagCode + post.info.flagCode or post.info.flagCodeTroll?.toLowerCase() else - Filter[type] post + Filter.values(type, post)[0] # We want to parse the exact same stuff as the filter does already. return false unless value el.href = Redirect.to 'search', boardID: post.board.ID - type: type + type: typeParam value: value isSearch: true true @@ -55,5 +59,3 @@ ArchiveLink = el: el open: open } - -return ArchiveLink diff --git a/src/Menu/CopyTextLink.coffee b/src/Menu/CopyTextLink.coffee new file mode 100644 index 0000000000..26e2e4b47f --- /dev/null +++ b/src/Menu/CopyTextLink.coffee @@ -0,0 +1,26 @@ +CopyTextLink = + init: -> + return unless g.VIEW in ['index', 'thread'] and Conf['Menu'] and Conf['Copy Text Link'] + + a = $.el 'a', + className: 'copy-text-link' + href: 'javascript:;' + textContent: 'Copy Text' + $.on a, 'click', CopyTextLink.copy + + Menu.menu.addEntry + el: a + order: 12 + open: (post) -> + CopyTextLink.text = (post.origin or post).commentOrig() + true + + copy: -> + el = $.el 'textarea', + className: 'copy-text-element', + value: CopyTextLink.text + $.add d.body, el + el.select() + try + d.execCommand 'copy' + $.rm el diff --git a/src/Menu/DeleteLink.coffee b/src/Menu/DeleteLink.coffee index 3033e515f3..fba4beeb95 100644 --- a/src/Menu/DeleteLink.coffee +++ b/src/Menu/DeleteLink.coffee @@ -1,5 +1,5 @@ DeleteLink = - auto: [{}, {}] + auto: [$.dict(), $.dict()] init: -> return unless g.VIEW in ['index', 'thread'] and Conf['Menu'] and Conf['Delete Link'] @@ -77,20 +77,23 @@ DeleteLink = mode: 'usrdel' onlyimgdel: fileOnly pwd: QR.persona.getPassword() - form[post.ID] = 'delete' + form[+post.ID] = 'delete' $.ajax $.id('delform').action.replace("/#{g.BOARD}/", "/#{post.board}/"), responseType: 'document' withCredentials: true - onload: -> DeleteLink.load link, post, fileOnly, @response - onerror: -> DeleteLink.error link, post - , + onloadend: -> DeleteLink.load link, post, fileOnly, @response form: $.formData form 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 if resDoc.title is '4chan - Banned' # Ban/warn check - el = $.el 'span', <%= html('You can't delete posts because you are banned.') %> + el = $.el 'span', `<%= html('You can't delete posts because you are banned.') %>` new Notice 'warning', el, 20 else if msg = resDoc.getElementById 'errmsg' # error! new Notice 'warning', msg.textContent, 20 @@ -106,12 +109,8 @@ DeleteLink = (post.origin or post).kill fileOnly 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: - seconds: {} + seconds: $.dict() start: (post, seconds) -> # Already counting. @@ -132,5 +131,3 @@ DeleteLink = for fileOnly in [false, true] when DeleteLink.auto[+fileOnly][post.fullID] DeleteLink.delete post, fileOnly return - -return DeleteLink diff --git a/src/Menu/DownloadLink.coffee b/src/Menu/DownloadLink.coffee index 3b787d8da9..f5973ae78a 100644 --- a/src/Menu/DownloadLink.coffee +++ b/src/Menu/DownloadLink.coffee @@ -17,5 +17,3 @@ DownloadLink = a.href = file.url a.download = file.name true - -return DownloadLink diff --git a/src/Menu/Menu.coffee b/src/Menu/Menu.coffee index 538523737c..50ff0f29c5 100644 --- a/src/Menu/Menu.coffee +++ b/src/Menu/Menu.coffee @@ -6,7 +6,7 @@ Menu = className: 'menu-button' href: 'javascript:;' - $.extend @button, <%= html('') %> + $.extend @button, `<%= html('') %>` @menu = new UI.Menu 'post' Callbacks.Post.push @@ -21,7 +21,7 @@ Menu = if @isClone button = $ '.menu-button', @nodes.info $.rmClass button, 'active' - $.rm $('.dialog', button) + $.rm $('.dialog', @nodes.info) Menu.makeButton @, button return $.add @nodes.info, Menu.makeButton @ @@ -34,5 +34,3 @@ Menu = $.on button, 'click', (e) -> Menu.menu.toggle e, @, post button - -return Menu diff --git a/src/Menu/ReportLink.coffee b/src/Menu/ReportLink.coffee index 7b026f7c13..8cee54a6dd 100644 --- a/src/Menu/ReportLink.coffee +++ b/src/Menu/ReportLink.coffee @@ -5,28 +5,22 @@ ReportLink = a = $.el 'a', className: 'report-link' href: 'javascript:;' + textContent: 'Report' $.on a, 'click', ReportLink.report Menu.menu.addEntry el: a order: 10 open: (post) -> - unless post.isDead or (post.thread.isDead and not post.thread.isArchived) - a.textContent = 'Report' - ReportLink.url = "//sys.4chan.org/#{post.board}/imgboard.php?mode=report&no=#{post}" - if (Conf['Use Recaptcha v1 in Reports'] and Main.jsEnabled) or d.cookie.indexOf('pass_enabled=1') >= 0 - ReportLink.url += '&altc=1' - ReportLink.dims = 'width=350,height=275' - else - ReportLink.dims = 'width=400,height=550' + ReportLink.url = "//sys.#{location.hostname.split('.')[1]}.org/#{post.board}/imgboard.php?mode=report&no=#{post}" + if d.cookie.indexOf('pass_enabled=1') >= 0 + ReportLink.dims = 'width=350,height=275' else - ReportLink.url = '' - !!ReportLink.url + ReportLink.dims = 'width=400,height=550' + true report: -> {url, dims} = ReportLink id = Date.now() set = "toolbar=0,scrollbars=1,location=0,status=1,menubar=0,resizable=1,#{dims}" window.open url, id, set - -return ReportLink diff --git a/src/Miscellaneous/AntiAutoplay.coffee b/src/Miscellaneous/AntiAutoplay.coffee index 60d3308abc..4ac65210d5 100644 --- a/src/Miscellaneous/AntiAutoplay.coffee +++ b/src/Miscellaneous/AntiAutoplay.coffee @@ -7,9 +7,6 @@ AntiAutoplay = Callbacks.Post.push name: 'Disable Autoplaying Sounds' cb: @node - Callbacks.CatalogThread.push - name: 'Disable Autoplaying Sounds' - cb: @node $.ready => @process d.body stop: (audio) -> @@ -21,15 +18,16 @@ AntiAutoplay = $.addClass audio, 'controls-added' node: -> - AntiAutoplay.process @nodes.root + AntiAutoplay.process @nodes.comment process: (root) -> for iframe in $$ 'iframe[src*="youtube"][src*="autoplay=1"]', root - iframe.src = iframe.src.replace(/\?autoplay=1&?/, '?').replace('&autoplay=1', '') - $.addClass iframe, 'autoplay-removed' + AntiAutoplay.processVideo iframe, 'src' for object in $$ 'object[data*="youtube"][data*="autoplay=1"]', root - object.data = object.data.replace(/\?autoplay=1&?/, '?').replace('&autoplay=1', '') - $.addClass object, 'autoplay-removed' + AntiAutoplay.processVideo object, 'data' return -return AntiAutoplay + processVideo: (el, attr) -> + el[attr] = el[attr].replace(/\?autoplay=1&?/, '?').replace('&autoplay=1', '') + el.style.display = 'block' if window.getComputedStyle(el).display is 'none' + $.addClass el, 'autoplay-removed' diff --git a/src/Miscellaneous/Banner.coffee b/src/Miscellaneous/Banner.coffee index 9bffcd49ad..e3ff530e16 100644 --- a/src/Miscellaneous/Banner.coffee +++ b/src/Miscellaneous/Banner.coffee @@ -1,6 +1,4 @@ Banner = - banners: `<%= JSON.stringify(readJSON('banners.json')) %>` - init: -> if Conf['Custom Board Titles'] @db = new DataBoard 'customTitles', null, true @@ -16,6 +14,9 @@ Banner = banner = $ ".boardBanner" {children} = banner + if g.VIEW is 'thread' and Conf['Remove Thread Excerpt'] + Banner.setTitle children[1].textContent + children[0].title = "Click to change" $.on children[0], 'click', Banner.cb.toggle @@ -41,7 +42,7 @@ Banner = cb: toggle: -> unless Banner.choices?.length - Banner.choices = Banner.banners.slice() + Banner.choices = Conf['knownBanners'].split(',').slice() i = Math.floor(Banner.choices.length * Math.random()) banner = Banner.choices.splice i, 1 $('img', @parentNode).src = "//s.4cdn.org/image/title/#{banner}" @@ -74,7 +75,7 @@ Banner = boardID: g.BOARD.ID threadID: @className - original: {} + original: $.dict() custom: (child) -> {className} = child @@ -90,5 +91,3 @@ Banner = child.textContent = data.title else Banner.db.delete {boardID: g.BOARD.ID, threadID: className} - -return Banner diff --git a/src/Miscellaneous/CatalogLinks.coffee b/src/Miscellaneous/CatalogLinks.coffee index 5f23fe174c..75a5e1e7d6 100644 --- a/src/Miscellaneous/CatalogLinks.coffee +++ b/src/Miscellaneous/CatalogLinks.coffee @@ -1,6 +1,6 @@ CatalogLinks = init: -> - if (Conf['External Catalog'] or Conf['JSON Index']) and !(Conf['JSON Index'] and g.VIEW is 'index') + if g.SITE.software is 'yotsuba' and (Conf['External Catalog'] or Conf['JSON Index']) and !(Conf['JSON Index'] and g.VIEW is 'index') selector = switch g.VIEW when 'thread', 'archive' then '.navLinks.desktop > a' when 'catalog' then '.navLinks > :first-child > a' @@ -13,22 +13,20 @@ CatalogLinks = link.href = CatalogLinks.index() when "/#{g.BOARD}/catalog" link.href = CatalogLinks.catalog() - if g.VIEW is 'catalog' and Conf['JSON Index'] and Conf['Use <%= meta.name %> Catalog'] + if g.VIEW is 'catalog' and (catalogURL = CatalogLinks.catalog()) isnt g.SITE.urls.catalog?(g.BOARD) catalogLink = link.parentNode.cloneNode true - catalogLink.firstElementChild.textContent = '<%= meta.name %> Catalog' - catalogLink.firstElementChild.href = CatalogLinks.catalog() + link2 = catalogLink.firstElementChild + link2.href = catalogURL + link2.textContent = if link2.hostname is location.hostname then '<%= meta.name %> Catalog' else 'External Catalog' $.after link.parentNode, [$.tn(' '), catalogLink] return - if Conf['JSON Index'] and Conf['Use <%= meta.name %> Catalog'] + if g.SITE.software is 'yotsuba' and Conf['JSON Index'] and Conf['Use <%= meta.name %> Catalog'] Callbacks.Post.push name: 'Catalog Link Rewrite' cb: @node - Callbacks.CatalogThread.push - name: 'Catalog Link Rewrite' - cb: @node - if Conf['Catalog Links'] + if (@enabled = Conf['Catalog Links']) CatalogLinks.el = el = UI.checkbox 'Header catalog links', 'Catalog Links' el.id = 'toggleCatalog' input = $ 'input', el @@ -40,51 +38,83 @@ CatalogLinks = node: -> for a in $$ 'a', @nodes.comment - if m = a.href.match /^https?:\/\/boards\.4chan\.org\/([^\/]+)\/catalog(#s=.*)?/ - a.href = "//boards.4chan.org/#{m[1]}/#{m[2] or '#catalog'}" + if m = a.href.match /^https?:\/\/(boards\.4chan(?:nel)?\.org\/[^\/]+)\/catalog(#s=.*)?/ + a.href = "//#{m[1]}/#{m[2] or '#catalog'}" return - # Set links on load or custom board list change. - # Called by Header when both board lists (header and footer) are ready. - initBoardList: -> - return unless CatalogLinks.el - CatalogLinks.set Conf['Header catalog links'] - toggle: -> $.event 'CloseMenu' $.set 'Header catalog links', @checked CatalogLinks.set @checked set: (useCatalog) -> - for a in $$('a:not([data-only])', Header.boardList).concat $$('a', Header.bottomBoardList) - continue if a.hostname not in ['boards.4chan.org', 'catalog.neet.tv'] or - !(board = a.pathname.split('/')[1]) or - board in ['f', 'status', '4chan'] or - a.pathname.split('/')[2] is 'archive' or - $.hasClass a, 'external' + Conf['Header catalog links'] = useCatalog + CatalogLinks.setLinks Header.boardList + CatalogLinks.setLinks Header.bottomBoardList + CatalogLinks.el.title = "Turn catalog links #{if useCatalog then 'off' else 'on'}." + $('input', CatalogLinks.el).checked = useCatalog - # Href is easier than pathname because then we don't have - # conditions where External Catalog has been disabled between switches. - a.href = if useCatalog then CatalogLinks.catalog(board) else "/#{board}/" + # Also called by Header when board lists are loaded / generated. + setLinks: (list) -> + return unless (CatalogLinks.enabled ? Conf['Catalog Links']) and list - if a.dataset.indexOptions and a.hostname is 'boards.4chan.org' and a.pathname.split('/')[2] is '' - a.href += (if a.hash then '/' else '#') + a.dataset.indexOptions + # do not transform links unless they differ from the expected value at most by this tail + tail = /(?:index)?(?:\.\w+)?$/ - CatalogLinks.el.title = "Turn catalog links #{if useCatalog then 'off' else 'on'}." - $('input', CatalogLinks.el).checked = useCatalog + for a in $$('a:not([data-only])', list) + {siteID, boardID} = a.dataset + unless siteID and boardID + {siteID, boardID, VIEW} = Site.parseURL a + continue unless ( + siteID and boardID and + VIEW in ['index', 'catalog'] and + (a.dataset.indexOptions or a.href.replace(tail, '') is (Get.url(VIEW, {siteID, boardID}) or '').replace(tail, '')) + ) + $.extend a.dataset, {siteID, boardID} - 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'] - "http://catalog.neet.tv/#{board}/" - else if Conf['JSON Index'] and Conf['Use <%= meta.name %> Catalog'] - if g.BOARD.ID is board and g.VIEW is 'index' then '#catalog' else "/#{board}/#catalog" + board = {siteID, boardID} + url = if Conf['Header catalog links'] then CatalogLinks.catalog(board) else Get.url('index', board) + if url + a.href = url + if a.dataset.indexOptions and url.split('#')[0] is Get.url('index', board) + a.href += (if a.hash then '/' else '#') + a.dataset.indexOptions + return + + externalParse: -> + CatalogLinks.externalList = $.dict() + for line in Conf['externalCatalogURLs'].split '\n' + continue if line[0] is '#' + url = line.split(';')[0] + boards = Filter.parseBoards(line.match(/;boards:([^;]+)/)?[1] or '*') + excludes = Filter.parseBoards(line.match(/;exclude:([^;]+)/)?[1]) or $.dict() + for board of boards + unless excludes[board] or excludes[board.split('/')[0] + '/*'] + CatalogLinks.externalList[board] = url + return + + external: ({siteID, boardID}) -> + CatalogLinks.externalParse() unless CatalogLinks.externalList + external = (CatalogLinks.externalList["#{siteID}/#{boardID}"] or CatalogLinks.externalList["#{siteID}/*"]) + if external then external.replace(/%board/g, boardID) else undefined + + jsonIndex: (board, hash) -> + if g.SITE.ID is board.siteID and g.BOARD.ID is board.boardID and g.VIEW is 'index' + hash else - "/#{board}/catalog" + Get.url('index', board) + hash - index: (board=g.BOARD.ID) -> - if Conf['JSON Index'] and board isnt 'f' - if g.BOARD.ID is board and g.VIEW is 'index' then '#index' else "/#{board}/#index" + catalog: (board=g.BOARD) -> + if Conf['External Catalog'] and (external = CatalogLinks.external board) + external + else if Index.enabledOn(board) and Conf['Use <%= meta.name %> Catalog'] + CatalogLinks.jsonIndex board, '#catalog' + else if (nativeCatalog = Get.url 'catalog', board) + nativeCatalog else - "/#{board}/" + CatalogLinks.external board -return CatalogLinks + index: (board=g.BOARD) -> + if Index.enabledOn(board) + CatalogLinks.jsonIndex board, '#index' + else + Get.url 'index', board diff --git a/src/Miscellaneous/CustomCSS.coffee b/src/Miscellaneous/CustomCSS.coffee index 67e8d0057c..666d09c85d 100644 --- a/src/Miscellaneous/CustomCSS.coffee +++ b/src/Miscellaneous/CustomCSS.coffee @@ -4,7 +4,7 @@ CustomCSS = @addStyle() addStyle: -> - @style = $.addStyle Conf['usercss'], 'custom-css', '#fourchanx-css' + @style = $.addStyle CSS.sub(Conf['usercss']), 'custom-css', '#fourchanx-css' rmStyle: -> if @style @@ -14,6 +14,4 @@ CustomCSS = update: -> unless @style return @addStyle() - @style.textContent = Conf['usercss'] - -return CustomCSS + @style.textContent = CSS.sub Conf['usercss'] diff --git a/src/Miscellaneous/ExpandComment.coffee b/src/Miscellaneous/ExpandComment.coffee index 263c99c910..dbf473c18d 100644 --- a/src/Miscellaneous/ExpandComment.coffee +++ b/src/Miscellaneous/ExpandComment.coffee @@ -2,9 +2,6 @@ ExpandComment = init: -> 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 name: 'Comment Expansion' cb: @node @@ -24,9 +21,9 @@ ExpandComment = $.replace post.nodes.shortComment, post.nodes.longComment post.nodes.comment = post.nodes.longComment return - return unless a = $ '.abbr > a', post.nodes.comment + return if not (a = $ '.abbr > a', post.nodes.comment) a.textContent = "Post No.#{post} Loading..." - $.cache "//a.4cdn.org#{a.pathname.split(/\/+/).splice(0,4).join('/')}.json", -> ExpandComment.parse @, a, post + $.cache g.SITE.urls.threadJSON({boardID: post.boardID, threadID: post.threadID}), -> ExpandComment.parse @, a, post contract: (post) -> return unless post.nodes.shortComment @@ -38,12 +35,12 @@ ExpandComment = parse: (req, a, post) -> {status} = req unless status in [200, 304] - a.textContent = "Error #{req.statusText} (#{status})" + a.textContent = if status then "Error #{req.statusText} (#{status})" else 'Connection Error' return posts = req.response.posts if spoilerRange = posts[0].custom_spoiler - Build.spoilerRange[g.BOARD] = spoilerRange + g.SITE.Build.spoilerRange[g.BOARD] = spoilerRange for postObj in posts break if postObj.no is post.ID @@ -71,5 +68,3 @@ ExpandComment = for callback in ExpandComment.callbacks callback.call post return - -return ExpandComment diff --git a/src/Miscellaneous/ExpandThread.coffee b/src/Miscellaneous/ExpandThread.coffee index 738842881c..672353b280 100644 --- a/src/Miscellaneous/ExpandThread.coffee +++ b/src/Miscellaneous/ExpandThread.coffee @@ -1,27 +1,29 @@ ExpandThread = - statuses: {} + statuses: $.dict() init: -> - return if g.VIEW is 'thread' or !Conf['Thread Expansion'] + return if not (g.VIEW is 'index' and Conf['Thread Expansion']) if Conf['JSON Index'] - $.on d, 'IndexRefresh', @onIndexRefresh + $.on d, 'IndexRefreshInternal', @onIndexRefresh else Callbacks.Thread.push name: 'Expand Thread' cb: -> ExpandThread.setButton @ setButton: (thread) -> - return unless a = $.x 'following-sibling::*[contains(@class,"summary")][1]', thread.OP.nodes.root - a.textContent = Build.summaryText '+', a.textContent.match(/\d+/g)... + return if not (thread.nodes.root and (a = $ '.summary', thread.nodes.root)) + a.textContent = g.SITE.Build.summaryText '+', a.textContent.match(/\d+/g)... a.style.cursor = 'pointer' $.on a, 'click', ExpandThread.cbToggle disconnect: (refresh) -> return if g.VIEW is 'thread' or !Conf['Thread Expansion'] for threadID, status of ExpandThread.statuses - status.req?.abort() + if (oldReq = status.req) + delete status.req + oldReq.abort() delete ExpandThread.statuses[threadID] - $.off d, 'IndexRefresh', @onIndexRefresh unless refresh + $.off d, 'IndexRefreshInternal', @onIndexRefresh unless refresh onIndexRefresh: -> ExpandThread.disconnect true @@ -29,43 +31,46 @@ ExpandThread = ExpandThread.setButton thread cbToggle: (e) -> - return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0 + return if $.modifiedClick e e.preventDefault() ExpandThread.toggle Get.threadFromNode @ + cbToggleBottom: (e) -> + return if $.modifiedClick e + e.preventDefault() + thread = Get.threadFromNode @ + $.rm @ # remove before fixing bottom of thread position + {bottom} = thread.nodes.root.getBoundingClientRect() + ExpandThread.toggle thread + window.scrollBy 0, (thread.nodes.root.getBoundingClientRect().bottom - bottom) + toggle: (thread) -> - threadRoot = thread.OP.nodes.root.parentNode - return unless a = $ '.summary', threadRoot + return if not (thread.nodes.root and (a = $ '.summary', thread.nodes.root)) if thread.ID of ExpandThread.statuses - ExpandThread.contract thread, a, threadRoot + ExpandThread.contract thread, a, thread.nodes.root else ExpandThread.expand thread, a expand: (thread, a) -> ExpandThread.statuses[thread] = status = {} - a.textContent = Build.summaryText '...', a.textContent.match(/\d+/g)... - status.req = $.cache "//a.4cdn.org/#{thread.board}/thread/#{thread}.json", -> + a.textContent = g.SITE.Build.summaryText '...', a.textContent.match(/\d+/g)... + status.req = $.cache g.SITE.urls.threadJSON({boardID: thread.board.ID, threadID: thread.ID}), -> + return if @ isnt status.req # aborted delete status.req ExpandThread.parse @, thread, a + status.numReplies = $$(g.SITE.selectors.replyOriginal, thread.nodes.root).length contract: (thread, a, threadRoot) -> status = ExpandThread.statuses[thread] delete ExpandThread.statuses[thread] - if status.req - status.req.abort() - a.textContent = Build.summaryText '+', a.textContent.match(/\d+/g)... if a + if (oldReq = status.req) + delete status.req + oldReq.abort() + a.textContent = g.SITE.Build.summaryText '+', a.textContent.match(/\d+/g)... if a return replies = $$ '.thread > .replyContainer', threadRoot - if !Conf['JSON Index'] or Conf['Show Replies'] - num = if thread.isSticky - 1 - else switch g.BOARD.ID - # XXX boards config - when 'b', 'vg' then 3 - when 't' then 1 - else 5 - replies = replies[...-num] + replies = replies[...(-status.numReplies)] if status.numReplies postsCount = 0 filesCount = 0 for reply in replies @@ -74,34 +79,42 @@ ExpandThread = postsCount++ filesCount++ if 'file' of Get.postFromRoot reply $.rm reply - a.textContent = Build.summaryText '+', postsCount, filesCount + if Index.enabled # otherwise handled by Main.addPosts + $.event 'PostsRemoved', null, a.parentNode + a.textContent = g.SITE.Build.summaryText '+', postsCount, filesCount + $.rm $('.summary-bottom', threadRoot) parse: (req, thread, a) -> 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 - Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler + g.SITE.Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler posts = [] postsRoot = [] filesCount = 0 for postData in req.response.posts continue if postData.no is thread.ID - if (post = thread.posts[postData.no]) and not post.isFetchedQuote + if (post = thread.posts.get(postData.no)) and not post.isFetchedQuote filesCount++ if 'file' of post - postsRoot.push post.nodes.root + {root} = post.nodes + postsRoot.push root continue - root = Build.postFromObject postData, thread.board.ID + root = g.SITE.Build.postFromObject postData, thread.board.ID post = new Post root, thread, thread.board filesCount++ if 'file' of post posts.push post postsRoot.push root Main.callbackNodes 'Post', posts $.after a, postsRoot - $.event 'PostsInserted' + $.event 'PostsInserted', null, a.parentNode postsCount = postsRoot.length - a.textContent = Build.summaryText '-', postsCount, filesCount + a.textContent = g.SITE.Build.summaryText '-', postsCount, filesCount -return ExpandThread + if root + a2 = a.cloneNode true + a2.classList.add 'summary-bottom' + $.on a2, 'click', ExpandThread.cbToggleBottom + $.after root, a2 diff --git a/src/Miscellaneous/FileInfo.coffee b/src/Miscellaneous/FileInfo.coffee index 374580b134..a7f6137853 100644 --- a/src/Miscellaneous/FileInfo.coffee +++ b/src/Miscellaneous/FileInfo.coffee @@ -1,6 +1,6 @@ FileInfo = init: -> - return if g.VIEW not in ['index', 'thread'] or !Conf['File Info Formatting'] + return if g.VIEW not in ['index', 'thread', 'archive'] or !Conf['File Info Formatting'] Callbacks.Post.push name: 'File Info Formatting' @@ -11,6 +11,8 @@ FileInfo = if @isClone for a in $$ '.file-info .download-button', @file.text $.on a, 'click', ImageCommon.download + for a in $$ '.file-info .quick-filter-md5', @file.text + $.on a, 'click', Filter.quickFilterMD5 return oldInfo = $.el 'span', {className: 'fileText-original'} @@ -24,37 +26,38 @@ FileInfo = format: (formatString, post, outputNode) -> output = [] formatString.replace /%(.)|[^%]+/g, (s, c) -> - output.push if c of FileInfo.formatters + output.push if $.hasOwn(FileInfo.formatters, c) FileInfo.formatters[c].call post else - <%= html('${s}') %> + `<%= html('${s}') %>` '' - $.extend outputNode, <%= html('@{output}') %> + $.extend outputNode, `<%= html('@{output}') %>` for a in $$ '.download-button', outputNode $.on a, 'click', ImageCommon.download + for a in $$ '.quick-filter-md5', outputNode + $.on a, 'click', Filter.quickFilterMD5 return formatters: - t: -> <%= html('${this.file.url.match(/[^\/]*$/)[0]}') %> - T: -> <%= html('&{FileInfo.formatters.t.call(this)}') %> - l: -> <%= html('&{FileInfo.formatters.n.call(this)}') %> - L: -> <%= html('&{FileInfo.formatters.N.call(this)}') %> + t: -> `<%= html('${this.file.url.match(/[^\/]*$/)[0]}') %>` + T: -> `<%= html('&{FileInfo.formatters.t.call(this)}') %>` + l: -> `<%= html('&{FileInfo.formatters.n.call(this)}') %>` + L: -> `<%= html('&{FileInfo.formatters.N.call(this)}') %>` n: -> fullname = @file.name - shortname = Build.shortFilename @file.name, @isReply + shortname = SW.yotsuba.Build.shortFilename @file.name, @isReply if fullname is shortname - <%= html('${fullname}') %> + `<%= html('${fullname}') %>` else - <%= html('${shortname}${fullname}') %> - N: -> <%= html('${this.file.name}') %> - d: -> <%= html('') %> - p: -> <%= html('?{this.file.isSpoiler}{Spoiler, }') %> - s: -> <%= html('${this.file.size}') %> - B: -> <%= html('${Math.round(this.file.sizeInBytes)} Bytes') %> - K: -> <%= html('${Math.round(this.file.sizeInBytes/1024)} KB') %> - M: -> <%= html('${Math.round(this.file.sizeInBytes/1048576*100)/100} MB') %> - r: -> <%= html('${this.file.dimensions || "PDF"}') %> - g: -> <%= html('?{this.file.tag}{, ${this.file.tag}}{}') %> - '%': -> <%= html('%') %> - -return FileInfo + `<%= html('${shortname}${fullname}') %>` + N: -> `<%= html('${this.file.name}') %>` + d: -> `<%= html('') %>` + f: -> `<%= html('') %>` + p: -> `<%= html('?{this.file.isSpoiler}{Spoiler, }') %>` + s: -> `<%= html('${this.file.size}') %>` + B: -> `<%= html('${Math.round(this.file.sizeInBytes)} Bytes') %>` + K: -> `<%= html('${Math.round(this.file.sizeInBytes/1024)} KB') %>` + M: -> `<%= html('${Math.round(this.file.sizeInBytes/1048576*100)/100} MB') %>` + r: -> `<%= html('${this.file.dimensions || "PDF"}') %>` + g: -> `<%= html('?{this.file.tag}{, ${this.file.tag}}{}') %>` + '%': -> `<%= html('%') %>` diff --git a/src/Miscellaneous/Flash.coffee b/src/Miscellaneous/Flash.coffee index 02ed6a4afd..37fd42177e 100644 --- a/src/Miscellaneous/Flash.coffee +++ b/src/Miscellaneous/Flash.coffee @@ -5,10 +5,8 @@ Flash = initReady: -> if $.hasStorage - $.global -> window.SWFEmbed.init() if JSON.parse(localStorage['4chan-settings'] or '{}').disableAll + $.global -> (window.SWFEmbed.init() if JSON.parse(localStorage['4chan-settings'] or '{}').disableAll) else if g.VIEW is 'thread' $.global -> window.Main.tid = location.pathname.split(/\/+/)[3] $.global -> window.SWFEmbed.init() - -return Flash diff --git a/src/Miscellaneous/Fourchan.coffee b/src/Miscellaneous/Fourchan.coffee index a75086683e..6c8b88b4f1 100644 --- a/src/Miscellaneous/Fourchan.coffee +++ b/src/Miscellaneous/Fourchan.coffee @@ -1,30 +1,36 @@ Fourchan = init: -> - return unless g.VIEW in ['index', 'thread'] + return unless g.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) -> - return unless post = g.posts[e.detail.ID] - return unless pre = $$('.prettyprint', post.nodes.comment)[e.detail.i] + return if not (post = g.posts.get(e.detail.ID)) + return if not (pre = $$('.prettyprint', post.nodes.comment)[+e.detail.i]) unless $.hasClass pre, 'prettyprinted' pre.innerHTML = e.detail.html $.addClass pre, 'prettyprinted' - $.globalEval ''' - window.addEventListener('prettyprint', function(e) { + $.global -> + window.addEventListener('prettyprint', (e) -> window.dispatchEvent(new CustomEvent('prettyprint:cb', { detail: { ID: e.detail.ID, i: e.detail.i, - html: prettyPrintOne(e.detail.html) + html: window.prettyPrintOne(e.detail.html) } - })); - }, false); - ''' + })) + , false) Callbacks.Post.push - name: 'Parse /g/ code' - cb: @code + name: 'Parse [code] tags' + cb: Fourchan.code + g.posts.forEach (post) -> + if post.callbacksExecuted + 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 -> window.addEventListener 'mathjax', (e) -> if window.MathJax @@ -40,19 +46,20 @@ Fourchan = , false , false Callbacks.Post.push - name: 'Parse /sci/ math' - cb: @math - Callbacks.CatalogThread.push - name: 'Parse /sci/ math' - cb: @math + name: 'Parse [math] tags' + cb: Fourchan.math + g.posts.forEach (post) -> + if post.callbacksExecuted + 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. - Main.ready -> - $.global -> - window.clickable_ids = false - for node in document.querySelectorAll '.posteruid, .capcode' - node.removeEventListener 'click', window.idClick, false - return + # Disable 4chan's ID highlighting (replaced by IDHighlight) and reported post hiding. + initReady: -> + $.global -> + window.clickable_ids = false + for node in document.querySelectorAll '.posteruid, .capcode' + node.removeEventListener 'click', window.idClick, false + return code: -> return if @isClone @@ -73,5 +80,3 @@ Fourchan = $.event 'mathjax', null, @nodes.comment $.on d, 'PostsInserted', cb cb() - -return Fourchan diff --git a/src/Miscellaneous/IDColor.coffee b/src/Miscellaneous/IDColor.coffee index 515c48c7b5..d7155d18f3 100644 --- a/src/Miscellaneous/IDColor.coffee +++ b/src/Miscellaneous/IDColor.coffee @@ -1,16 +1,15 @@ IDColor = init: -> return unless g.VIEW in ['index', 'thread'] and Conf['Color User IDs'] - @ids = { - Heaven: [0, 0, 0, '#fff'] - } + @ids = $.dict() + @ids['Heaven'] = [0, 0, 0, '#fff'] Callbacks.Post.push name: 'Color User IDs' cb: @node node: -> - return if @isClone or !((uid = @info.uniqueID) and (span = $ 'span.hand', @nodes.uniqueID)) + return if @isClone or !((uid = @info.uniqueID) and (span = @nodes.uniqueID)) rgb = IDColor.ids[uid] or IDColor.compute uid @@ -23,29 +22,20 @@ IDColor = compute: (uid) -> # Convert chars to integers, bitshift and math to create a larger integer # Create a nice string of binary - hash = IDColor.hash uid + hash = if g.SITE.uidColor then g.SITE.uidColor(uid) else parseInt(uid, 16) # Convert binary string to numerical values with bitshift and '&' truncation. rgb = [ - (hash >> 24) & 0xFF (hash >> 16) & 0xFF (hash >> 8) & 0xFF + hash & 0xFF ] # Weight color luminance values, assign a font color that should be readable. - rgb.push if (rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114) > 125 + rgb.push if $.luma(rgb) > 125 '#000' else '#fff' # Cache. @ids[uid] = rgb - - hash: (uid) -> - msg = 0 - i = 0 - while i < 8 - msg = (msg << 5) - msg + uid.charCodeAt i++ - msg - -return IDColor diff --git a/src/Miscellaneous/IDHighlight.coffee b/src/Miscellaneous/IDHighlight.coffee index 52ddcb47b5..bf8aaa9338 100644 --- a/src/Miscellaneous/IDHighlight.coffee +++ b/src/Miscellaneous/IDHighlight.coffee @@ -9,8 +9,8 @@ IDHighlight = uniqueID: null node: -> - $.on @nodes.uniqueID, 'click', IDHighlight.click @ if @nodes.uniqueID - $.on @nodes.capcode, 'click', IDHighlight.click @ if @nodes.capcode + $.on @nodes.uniqueIDRoot, 'click', IDHighlight.click @ if @nodes.uniqueIDRoot + $.on @nodes.capcode, 'click', IDHighlight.click @ if @nodes.capcode IDHighlight.set @ unless @isClone set: (post) -> @@ -21,5 +21,3 @@ IDHighlight = uniqueID = post.info.uniqueID or post.info.capcode IDHighlight.uniqueID = if IDHighlight.uniqueID is uniqueID then null else uniqueID g.posts.forEach IDHighlight.set - -return IDHighlight diff --git a/src/Miscellaneous/IDPostCount.coffee b/src/Miscellaneous/IDPostCount.coffee new file mode 100644 index 0000000000..5e8896c70e --- /dev/null +++ b/src/Miscellaneous/IDPostCount.coffee @@ -0,0 +1,20 @@ +IDPostCount = + init: -> + return unless g.VIEW is 'thread' and Conf['Count Posts by ID'] + Callbacks.Thread.push + name: 'Count Posts by ID' + cb: -> IDPostCount.thread = @ + Callbacks.Post.push + name: 'Count Posts by ID' + cb: @node + + node: -> + if @nodes.uniqueID and @thread is IDPostCount.thread + $.on @nodes.uniqueID, 'mouseover', IDPostCount.count + + count: -> + {uniqueID} = Get.postFromNode(@).info + n = 0 + IDPostCount.thread.posts.forEach (post) -> + (n++ if post.info.uniqueID is uniqueID) + @title = "#{n} post#{if n is 1 then '' else 's'} by this ID" diff --git a/src/Miscellaneous/Keybinds.coffee b/src/Miscellaneous/Keybinds.coffee index 1ce5a7a842..3768d365e9 100644 --- a/src/Miscellaneous/Keybinds.coffee +++ b/src/Miscellaneous/Keybinds.coffee @@ -17,18 +17,13 @@ Keybinds = Conf[hotkey] = key keydown: (e) -> - return unless key = Keybinds.keyCode e + return if not (key = Keybinds.keyCode e) {target} = e if target.nodeName in ['INPUT', 'TEXTAREA'] return unless /(Esc|Alt|Ctrl|Meta|Shift\+\w{2,})/.test(key) and not /^Alt\+(\d|Up|Down|Left|Right)$/.test(key) - unless ( - g.VIEW not in ['index', 'thread'] or - g.VIEW is 'index' and Conf['JSON Index'] and Conf['Index Mode'] is 'catalog' or - g.VIEW is 'index' and g.BOARD.ID is 'f' - ) + if g.VIEW in ['index', 'thread'] threadRoot = Nav.getThread() - if op = $ '.op', threadRoot - thread = Get.postFromNode(op).thread + thread = Get.threadFromRoot threadRoot switch key # QR & Options when Conf['Toggle board list'] @@ -77,6 +72,15 @@ Keybinds = when Conf['Toggle sage'] return unless QR.nodes and !QR.nodes.el.hidden Keybinds.sage() + when Conf['Toggle Cooldown'] + return unless QR.nodes and !QR.nodes.el.hidden and $.hasClass(QR.nodes.fileSubmit, 'custom-cooldown') + QR.toggleCustomCooldown() + when Conf['Post from URL'] + return unless QR.postingIsEnabled + QR.handleUrl '' + when Conf['Add new post'] + return unless QR.postingIsEnabled + QR.addPost() when Conf['Submit QR'] return unless QR.nodes and !QR.nodes.el.hidden QR.submit() if !QR.status() @@ -84,10 +88,10 @@ Keybinds = when Conf['Update'] switch g.VIEW when 'thread' - return unless Conf['Thread Updater'] + return unless ThreadUpdater.enabled ThreadUpdater.update() when 'index' - return unless Conf['JSON Index'] and g.BOARD.ID isnt 'f' + return unless Index.enabled Index.update() else return @@ -97,65 +101,79 @@ Keybinds = when Conf['Update thread watcher'] return unless ThreadWatcher.enabled ThreadWatcher.buttonFetchAll() + when Conf['Toggle thread watcher'] + return unless ThreadWatcher.enabled + ThreadWatcher.toggleWatcher() + when Conf['Toggle threading'] + return unless QuoteThreading.ready + QuoteThreading.toggleThreading() + when Conf['Mark thread read'] + return unless g.VIEW is 'index' and thread and UnreadIndex.enabled + UnreadIndex.markRead.call threadRoot # Images when Conf['Expand image'] return unless ImageExpand.enabled and threadRoot - Keybinds.img threadRoot + post = Get.postFromNode Keybinds.post threadRoot + ImageExpand.toggle post if post.file when Conf['Expand images'] - return unless ImageExpand.enabled and threadRoot - Keybinds.img threadRoot, true + return unless ImageExpand.enabled + ImageExpand.cb.toggleAll() when Conf['Open Gallery'] return unless Gallery.enabled Gallery.cb.toggle() when Conf['fappeTyme'] - return unless Conf['Fappe Tyme'] and g.VIEW in ['index', 'thread'] + return unless FappeTyme.nodes?.fappe FappeTyme.toggle 'fappe' when Conf['werkTyme'] - return unless Conf['Werk Tyme'] and g.VIEW in ['index', 'thread'] + return unless FappeTyme.nodes?.werk FappeTyme.toggle 'werk' # Board Navigation when Conf['Front page'] - if Conf['JSON Index'] and g.VIEW is 'index' and g.BOARD.ID isnt 'f' + if Index.enabled Index.userPageNav 1 else - window.location = "/#{g.BOARD}/" + location.href = "/#{g.BOARD}/" when Conf['Open front page'] - $.open "/#{g.BOARD}/" + $.open "#{location.origin}/#{g.BOARD}/" when Conf['Next page'] - return unless g.VIEW is 'index' and g.BOARD.ID isnt 'f' - if Conf['JSON Index'] + return unless g.VIEW is 'index' and !g.SITE.isOnePage?(g.BOARD) + if Index.enabled return unless Conf['Index Mode'] in ['paged', 'infinite'] $('.next button', Index.pagelist).click() else - if form = $ '.next form' - window.location = form.action + $(g.SITE.selectors.nav.next)?.click() when Conf['Previous page'] - return unless g.VIEW is 'index' and g.BOARD.ID isnt 'f' - if Conf['JSON Index'] + return unless g.VIEW is 'index' and !g.SITE.isOnePage?(g.BOARD) + if Index.enabled return unless Conf['Index Mode'] in ['paged', 'infinite'] $('.prev button', Index.pagelist).click() else - if form = $ '.prev form' - window.location = form.action + $(g.SITE.selectors.nav.prev)?.click() when Conf['Search form'] - return unless g.VIEW is 'index' and g.BOARD.ID isnt 'f' - searchInput = if Conf['JSON Index'] then Index.searchInput else $.id('search-box') + return unless g.VIEW is 'index' + searchInput = if Index.enabled + Index.searchInput + else if g.SITE.selectors.searchBox + $ g.SITE.selectors.searchBox + else + undefined + return unless searchInput Header.scrollToIfNeeded searchInput searchInput.focus() when Conf['Paged mode'] - return unless Conf['JSON Index'] and g.BOARD.ID isnt 'f' - window.location = if g.VIEW is 'index' then '#paged' else "/#{g.BOARD}/#paged" + return unless Index.enabledOn(g.BOARD) + location.href = if g.VIEW is 'index' then '#paged' else "/#{g.BOARD}/#paged" when Conf['Infinite scrolling mode'] - return unless Conf['JSON Index'] and g.BOARD.ID isnt 'f' - window.location = if g.VIEW is 'index' then '#infinite' else "/#{g.BOARD}/#infinite" + return unless Index.enabledOn(g.BOARD) + location.href = if g.VIEW is 'index' then '#infinite' else "/#{g.BOARD}/#infinite" when Conf['All pages mode'] - return unless Conf['JSON Index'] and g.BOARD.ID isnt 'f' - window.location = if g.VIEW is 'index' then '#all-pages' else "/#{g.BOARD}/#all-pages" + return unless Index.enabledOn(g.BOARD) + location.href = if g.VIEW is 'index' then '#all-pages' else "/#{g.BOARD}/#all-pages" when Conf['Open catalog'] - return if g.BOARD.ID is 'f' - window.location = CatalogLinks.catalog() + return unless (catalog = CatalogLinks.catalog()) + location.href = catalog when Conf['Cycle sort type'] - return unless Conf['JSON Index'] and g.VIEW is 'index' and g.BOARD.ID isnt 'f' + return unless Index.enabled Index.cycleSortType() # Thread Navigation when Conf['Next thread'] @@ -167,6 +185,8 @@ Keybinds = when Conf['Expand thread'] return unless g.VIEW is 'index' and threadRoot ExpandThread.toggle thread + # Keep thread from moving off screen when contracted. + Header.scrollTo threadRoot when Conf['Open thread'] return unless g.VIEW is 'index' and threadRoot Keybinds.open thread @@ -187,17 +207,17 @@ Keybinds = return unless thread and ThreadHiding.db Header.scrollTo threadRoot ThreadHiding.toggle thread + when Conf['Quick Filter MD5'] + return unless threadRoot + post = Keybinds.post threadRoot + Keybinds.hl +1, threadRoot + Filter.quickFilterMD5.call post, e when Conf['Previous Post Quoting You'] return unless threadRoot and QuoteYou.db QuoteYou.cb.seek 'preceding' when Conf['Next Post Quoting You'] return unless threadRoot and QuoteYou.db QuoteYou.cb.seek 'following' - <% if (readJSON('/.tests_enabled')) { %> - when 't' - return unless threadRoot - Build.Test.testAll() - <% } %> else return e.preventDefault() @@ -243,19 +263,28 @@ Keybinds = if e.shiftKey then key = 'Shift+' + key key + post: (thread) -> + s = g.SITE.selectors + ( + $("#{s.postContainer}#{s.highlightable.reply}.#{g.SITE.classes.highlight}", thread) or + $("#{if g.SITE.isOPContainerThread then s.thread else s.postContainer}#{s.highlightable.op}", thread) + ) + qr: (thread) -> QR.open() if thread? - QR.quote.call $ 'input', $('.post.highlight', thread) or thread + QR.quote.call Keybinds.post thread QR.nodes.com.focus() tags: (tag, ta) -> - supported = switch tag - when 'spoiler' then !!$ '.postForm input[name=spoiler]' - when 'code' then g.BOARD.ID is 'g' - when 'math', 'eqn' then g.BOARD.ID is 'sci' - when 'sjis' then g.BOARD.ID is 'jp' - new Notice 'warning', "[#{tag}] tags are not supported on /#{g.BOARD}/.", 20 unless supported + BoardConfig.ready -> + {config} = g.BOARD + supported = switch tag + when 'spoiler' then !!config.spoilers + when 'code' then !!config.code_tags + when 'math', 'eqn' then !!config.math_tags + when 'sjis' then !!config.sjis_tags + (new Notice 'warning', "[#{tag}] tags are not supported on /#{g.BOARD}/.", 20 unless supported) value = ta.value selStart = ta.selectionStart @@ -267,7 +296,7 @@ Keybinds = value[selEnd..] # Move the caret to the end of the selection. - range = "[#{tag}]".length + selEnd + range = ("[#{tag}]").length + selEnd ta.setSelectionRange range, range # Fire the 'input' event @@ -279,51 +308,44 @@ Keybinds = "" else "sage" - img: (thread, all) -> - if all - ImageExpand.cb.toggleAll() - else - post = Get.postFromNode $('.post.highlight', thread) or $ '.op', thread - ImageExpand.toggle post - open: (thread, tab) -> return if g.VIEW isnt 'index' - url = "/#{thread.board}/thread/#{thread}" + url = Get.url 'thread', thread if tab $.open url else location.href = url hl: (delta, thread) -> - postEl = $ '.reply.highlight', thread + replySelector = "#{g.SITE.selectors.postContainer}#{g.SITE.selectors.highlightable.reply}" + {highlight} = g.SITE.classes + + postEl = $ "#{replySelector}.#{highlight}", thread unless delta - $.rmClass postEl, 'highlight' if postEl + $.rmClass postEl, highlight if postEl return if postEl {height} = postEl.getBoundingClientRect() if Header.getTopOf(postEl) >= -height and Header.getBottomOf(postEl) >= -height # We're at least partially visible - root = postEl.parentNode + {root} = Get.postFromNode(postEl).nodes axis = if delta is +1 'following' else 'preceding' - return unless next = $.x "#{axis}-sibling::div[contains(@class,'replyContainer') and not(@hidden) and not(child::div[@class='stub'])][1]/child::div[contains(@class,'reply')]", root + return unless (next = $.x "#{axis}-sibling::#{g.SITE.xpath.replyContainer}[not(@hidden) and not(child::div[@class='stub'])][1]", root) + next = $ replySelector, next unless next.matches(replySelector) Header.scrollToIfNeeded next, delta is +1 - @focus next - $.rmClass postEl, 'highlight' + $.addClass next, highlight + $.rmClass postEl, highlight return - $.rmClass postEl, 'highlight' + $.rmClass postEl, highlight - replies = $$ '.reply', thread + replies = $$ replySelector, thread replies.reverse() if delta is -1 for reply in replies if delta is +1 and Header.getTopOf(reply) > 0 or delta is -1 and Header.getBottomOf(reply) > 0 - @focus reply + $.addClass reply, highlight return - - focus: (post) -> - $.addClass post, 'highlight' - -return Keybinds + return diff --git a/src/Miscellaneous/ModContact.coffee b/src/Miscellaneous/ModContact.coffee new file mode 100644 index 0000000000..42c65e2574 --- /dev/null +++ b/src/Miscellaneous/ModContact.coffee @@ -0,0 +1,34 @@ +ModContact = + init: -> + return unless g.SITE.software is 'yotsuba' and g.VIEW in ['index', 'thread'] + Callbacks.Post.push + name: 'Mod Contact Links' + cb: @node + + node: -> + return if @isClone or !$.hasOwn(ModContact.specific, @info.capcode) + links = $.el 'span', className: 'contact-links brackets-wrap' + $.extend links, ModContact.template(@info.capcode) + $.after @nodes.capcode, links + if (moved = @info.comment.match /This thread was moved to >>>\/(\w+)\//) and $.hasOwn(ModContact.moveNote, moved[1]) + moveNote = $.el 'div', className: 'move-note' + $.extend moveNote, ModContact.moveNote[moved[1]] + $.add @nodes.post, moveNote + + template: (capcode) -> + `<%= html( + 'feedback&{ModContact.specific[capcode]()}' + ) %>` + + specific: + Mod: -> `<%= html(' IRC') %>` + Manager: -> ModContact.specific.Mod() + Developer: -> `<%= html(' github') %>` + Admin: -> `<%= html(' twitter') %>` + + moveNote: + qa: `<%= html( + 'Moving a thread to /qa/ does not imply mods will read it. If you wish to contact mods, use ' + + 'feedback or ' + + 'IRC.' + ) %>` diff --git a/src/Miscellaneous/Nav.coffee b/src/Miscellaneous/Nav.coffee index 1aaabee283..4db40266f5 100644 --- a/src/Miscellaneous/Nav.coffee +++ b/src/Miscellaneous/Nav.coffee @@ -39,21 +39,24 @@ Nav = Nav.scroll +1 getThread: -> - for threadRoot in $$ '.thread' + return g.threads.get("#{g.BOARD}.#{g.THREADID}").nodes.root if g.VIEW is 'thread' + return if $.hasClass doc, 'catalog-mode' + for threadRoot in $$ g.SITE.selectors.thread thread = Get.threadFromRoot threadRoot continue if thread.isHidden and !thread.stub if Header.getTopOf(threadRoot) >= -threadRoot.getBoundingClientRect().height # not scrolled past return threadRoot - return $ '.board' + return scroll: (delta) -> d.activeElement?.blur() thread = Nav.getThread() + return unless thread axis = if delta is +1 'following' else 'preceding' - if next = $.x "#{axis}-sibling::div[contains(@class,'thread') and not(@hidden)][1]", thread + if next = $.x "#{axis}-sibling::#{g.SITE.xpath.thread}[not(@hidden)][1]", thread # Unless we're not at the beginning of the current thread, # and thus wanting to move to beginning, # or we're above the first thread and don't want to skip it. @@ -74,8 +77,6 @@ Nav = if extra > 0 d.body.style.marginBottom = "#{extra}px" else - d.body.style.marginBottom = null + d.body.style.marginBottom = '' delete Nav.haveExtra $.off d, 'scroll', Nav.removeExtra - -return Nav diff --git a/src/Miscellaneous/NormalizeURL.coffee b/src/Miscellaneous/NormalizeURL.coffee index a0ff418f86..8af912235a 100644 --- a/src/Miscellaneous/NormalizeURL.coffee +++ b/src/Miscellaneous/NormalizeURL.coffee @@ -3,14 +3,13 @@ NormalizeURL = return unless Conf['Normalize URL'] pathname = location.pathname.split /\/+/ - switch g.VIEW - when 'thread' - pathname[2] = 'thread' - pathname = pathname[0...4] - when 'index' - pathname = pathname[0...3] + if g.SITE.software is 'yotsuba' + switch g.VIEW + when 'thread' + pathname[2] = 'thread' + pathname = pathname[0...4] + when 'index' + pathname = pathname[0...3] pathname = pathname.join '/' if location.pathname isnt pathname history.replaceState history.state, '', "#{location.protocol}//#{location.host}#{pathname}#{location.hash}" - -return NormalizeURL diff --git a/src/Miscellaneous/PSA.coffee b/src/Miscellaneous/PSA.coffee new file mode 100644 index 0000000000..5adbcd9138 --- /dev/null +++ b/src/Miscellaneous/PSA.coffee @@ -0,0 +1,14 @@ +PSA = + init: -> + if g.SITE.software is 'yotsuba' and g.BOARD.ID is 'qa' + announcement = <%= html('Stay in touch with your /qa/ friends!') %> + el = $.el 'div', {className: 'fcx-announcement'}, announcement + $.onExists doc, '.boardBanner', (banner) -> + $.after banner, el + if 'samachan.org' of Conf['siteProperties'] and 'samachan' not in Conf['PSAseen'] + el = $.el 'span', + <%= html('Looking for a new home?
              Some former Samachan users are regrouping on SushiChan.

              (a message from 4chan X)') %> + Main.ready -> + new Notice 'info', el + Conf['PSAseen'].push('samachan') + $.set 'PSAseen', Conf['PSAseen'] diff --git a/src/Miscellaneous/PSAHiding.coffee b/src/Miscellaneous/PSAHiding.coffee index 6945e7f6ef..b04058b404 100644 --- a/src/Miscellaneous/PSAHiding.coffee +++ b/src/Miscellaneous/PSAHiding.coffee @@ -1,15 +1,17 @@ PSAHiding = init: -> - return unless Conf['Announcement Hiding'] + return unless Conf['Announcement Hiding'] and g.SITE.selectors.psa $.addClass doc, 'hide-announcement' - $.one d, '4chanXInitFinished', @setup - - setup: -> - unless psa = PSAHiding.psa = $.id 'globalMessage' - $.rmClass doc, 'hide-announcement' - return - if (hr = $.id('globalToggle')?.previousElementSibling) and hr.nodeName is 'HR' + $.onExists doc, g.SITE.selectors.psa, @setup + $.ready -> + $.rmClass doc, 'hide-announcement' if !$(g.SITE.selectors.psa) + + setup: (psa) -> + PSAHiding.psa = psa + PSAHiding.text = psa.dataset.utc ? psa.innerHTML + if g.SITE.selectors.psaTop and (hr = $(g.SITE.selectors.psaTop)?.previousElementSibling) and hr.nodeName is 'HR' PSAHiding.hr = hr + PSAHiding.content = $.el 'div' entry = el: $.el 'a', @@ -17,42 +19,44 @@ PSAHiding = className: 'show-announcement' href: 'javascript:;' order: 50 - open: -> PSAHiding.hidden + open: -> psa.hidden Header.menu.addEntry entry $.on entry.el, 'click', PSAHiding.toggle - PSAHiding.btn = btn = $.el 'span', + PSAHiding.btn = btn = $.el 'a', title: 'Mark announcement as read and hide.' - className: 'hide-announcement' - - $.extend btn, <%= html('[Dismiss]') %> - + className: 'hide-announcement-button fa fa-minus-square' + href: 'javascript:;' $.on btn, 'click', PSAHiding.toggle + if psa.firstChild?.tagName is 'HR' + $.after psa.firstChild, btn + else + $.prepend psa, btn - $.get 'hiddenPSA', 0, ({hiddenPSA}) -> - PSAHiding.sync hiddenPSA - $.add psa, btn - $.rmClass doc, 'hide-announcement' + PSAHiding.sync Conf['hiddenPSAList'] + $.rmClass doc, 'hide-announcement' - $.sync 'hiddenPSA', PSAHiding.sync + $.sync 'hiddenPSAList', PSAHiding.sync toggle: -> - if $.hasClass @, 'hide-announcement' - UTC = +$.id('globalMessage').dataset.utc - $.set 'hiddenPSA', UTC + hide = $.hasClass @, 'hide-announcement-button' + set = (hiddenPSAList) -> + if hide + hiddenPSAList[g.SITE.ID] = PSAHiding.text + else + delete hiddenPSAList[g.SITE.ID] + set Conf['hiddenPSAList'] + PSAHiding.sync Conf['hiddenPSAList'] + $.get 'hiddenPSAList', Conf['hiddenPSAList'], ({hiddenPSAList}) -> + set hiddenPSAList + $.set 'hiddenPSAList', hiddenPSAList + + sync: (hiddenPSAList) -> + {psa, content} = PSAHiding + psa.hidden = (hiddenPSAList[g.SITE.ID] is PSAHiding.text) + # Remove content to prevent autoplaying sounds from hidden announcements + if psa.hidden + $.add content, [psa.childNodes...] else - $.event 'CloseMenu' - $.delete 'hiddenPSA' - PSAHiding.sync UTC - - sync: (UTC) -> - {psa} = PSAHiding - PSAHiding.hidden = PSAHiding.btn.hidden = UTC? and UTC >= +psa.dataset.utc - if PSAHiding.hidden - $.rm psa - else - $.after $.id('globalToggle'), psa - PSAHiding.hr?.hidden = PSAHiding.hidden - return - -return PSAHiding + $.add psa, [content.childNodes...] + PSAHiding.hr?.hidden = psa.hidden diff --git a/src/Miscellaneous/PassMessage.coffee b/src/Miscellaneous/PassMessage.coffee new file mode 100644 index 0000000000..8426e23e85 --- /dev/null +++ b/src/Miscellaneous/PassMessage.coffee @@ -0,0 +1,17 @@ +PassMessage = + init: -> + return if Conf['passMessageClosed'] + msg = $.el 'div', + className: 'box-outer top-box' + , + `<%= readHTML('PassMessage.html') %>` + msg.style.cssText = 'padding-bottom: 0;' + close = $ 'a', msg + $.on close, 'click', -> + $.rm msg + $.set 'passMessageClosed', true + $.ready -> + if (hd = $.id 'hd') + $.after hd, msg + else + $.prepend d.body, msg diff --git a/src/Miscellaneous/PassMessage/PassMessage.html b/src/Miscellaneous/PassMessage/PassMessage.html new file mode 100644 index 0000000000..2c1b02b620 --- /dev/null +++ b/src/Miscellaneous/PassMessage/PassMessage.html @@ -0,0 +1,11 @@ +
              +
              +

              + Trouble buying a 4chan Pass? (a message from 4chan X) + × +

              +
              +
              + Check the 4chan X wiki for alternative solutions. +
              +
              diff --git a/src/Miscellaneous/PostJumper.coffee b/src/Miscellaneous/PostJumper.coffee new file mode 100644 index 0000000000..df61b4be98 --- /dev/null +++ b/src/Miscellaneous/PostJumper.coffee @@ -0,0 +1,65 @@ +PostJumper = + init: -> + return unless Conf['Unique ID and Capcode Navigation'] and g.VIEW in ['index', 'thread'] + + @buttons = @makeButtons() + + Callbacks.Post.push + name: 'Post Jumper' + cb: @node + + node: -> + if @isClone + for buttons in $$ '.postJumper', @nodes.info + PostJumper.addListeners buttons + return + + if @nodes.uniqueIDRoot + PostJumper.addButtons @,'uniqueID' + + if @nodes.capcode + PostJumper.addButtons @,'capcode' + + addButtons: (post,type) -> + value = post.info[type] + buttons = PostJumper.buttons.cloneNode(true) + $.extend buttons.dataset, {type, value} + $.after post.nodes[type+(if type is 'capcode' then '' else 'Root')], buttons + PostJumper.addListeners buttons + + addListeners: (buttons) -> + $.on buttons.firstChild, 'click', PostJumper.buttonClick + $.on buttons.lastChild, 'click', PostJumper.buttonClick + + buttonClick: -> + dir = if $.hasClass(@, 'prev') then -1 else 1 + if (toJumper = PostJumper.find @parentNode, dir) + PostJumper.scroll @parentNode, toJumper + + find: (jumper, dir) -> + {type, value} = jumper.dataset + xpath = "span[contains(@class,\"postJumper\") and @data-value=\"#{value}\" and @data-type=\"#{type}\"]" + axis = if dir < 0 then 'preceding' else 'following' + jumper2 = jumper + while (jumper2 = $.x "#{axis}::#{xpath}", jumper2) + return jumper2 if jumper2.getBoundingClientRect().height + if (jumper2 = $.x "(//#{xpath})[#{if dir < 0 then 'last()' else '1'}]") + return jumper2 if jumper2.getBoundingClientRect().height + while (jumper2 = $.x "#{axis}::#{xpath}", jumper2) and jumper2 isnt jumper + return jumper2 if jumper2.getBoundingClientRect().height + null + + makeButtons: -> + charPrev = '\u23EB' + charNext = '\u23EC' + classPrev = 'prev' + classNext = 'next' + span = $.el 'span', + className: 'postJumper' + $.extend span, `<%= html('${charPrev}${charNext}') %>` + span + + scroll: (fromJumper, toJumper) -> + prevPos = fromJumper.getBoundingClientRect().top + destPos = toJumper.getBoundingClientRect().top + window.scrollBy 0, destPos-prevPos diff --git a/src/Miscellaneous/RelativeDates.coffee b/src/Miscellaneous/RelativeDates.coffee index 89e00d2ac3..66162dc89b 100644 --- a/src/Miscellaneous/RelativeDates.coffee +++ b/src/Miscellaneous/RelativeDates.coffee @@ -1,12 +1,13 @@ RelativeDates = INTERVAL: $.MINUTE / 2 + init: -> if ( - g.VIEW in ['index', 'thread'] and Conf['Relative Post Dates'] and !Conf['Relative Date Title'] or - g.VIEW is 'index' and Conf['JSON Index'] and g.BOARD.ID isnt 'f' + g.VIEW in ['index', 'thread', 'archive'] and Conf['Relative Post Dates'] and !Conf['Relative Date Title'] or + Index.enabled ) @flush() - $.on d, 'visibilitychange ThreadUpdate', @flush + $.on d, 'visibilitychange PostsInserted', @flush if Conf['Relative Post Dates'] Callbacks.Post.push @@ -14,6 +15,7 @@ RelativeDates = cb: @node node: -> + return unless @info.date dateEl = @nodes.date if Conf['Relative Date Title'] $.on dateEl, 'mouseover', => RelativeDates.hover @ @@ -28,9 +30,9 @@ RelativeDates = RelativeDates.update @ # diff is milliseconds from now. - relative: (diff, now, date) -> + relative: (diff, now, date, abbrev) -> unit = if (number = (diff / $.DAY)) >= 1 - years = now.getYear() - date.getYear() + years = now.getFullYear() - date.getFullYear() months = now.getMonth() - date.getMonth() days = now.getDate() - date.getDate() if years > 1 @@ -57,9 +59,13 @@ RelativeDates = 'second' rounded = Math.round number - unit += 's' if rounded isnt 1 # pluralize - "#{rounded} #{unit} ago" + if abbrev + unit = if unit is 'month' then 'mo' else unit[0] + else + unit += 's' if rounded isnt 1 # pluralize + + if abbrev then "#{rounded}#{unit}" else "#{rounded} #{unit} ago" # Changing all relative dates as soon as possible incurs many annoying # redraws and scroll stuttering. Thus, sacrifice accuracy for UX/CPU economy, @@ -92,19 +98,22 @@ RelativeDates = # and re-calls `setOwnTimeout()` to re-add `data` to the stale list later. update: (data, now) -> isPost = data instanceof Post - date = if isPost - data.info.date + if isPost + date = data.info.date + abbrev = false else - new Date +data.dataset.utc + date = new Date +data.dataset.utc + abbrev = !!data.dataset.abbrev now or= new Date() diff = now - date - relative = RelativeDates.relative diff, now, date + relative = RelativeDates.relative diff, now, date, abbrev if isPost for singlePost in [data].concat data.clones singlePost.nodes.date.firstChild.textContent = relative else data.firstChild.textContent = relative RelativeDates.setOwnTimeout diff, data + setOwnTimeout: (diff, data) -> delay = if diff < $.MINUTE $.SECOND - (diff + $.SECOND / 2) % $.SECOND @@ -115,9 +124,9 @@ RelativeDates = else $.DAY - (diff + $.DAY / 2) % $.DAY setTimeout RelativeDates.markStale, delay, data + markStale: (data) -> return if data in RelativeDates.stale # We can call RelativeDates.update() multiple times. - return if data instanceof Post and !g.posts[data.fullID] # collected post. + return if data instanceof Post and !g.posts.get(data.fullID) # collected post. + return if data instanceof Element and !doc.contains(data) # removed catalog reply. RelativeDates.stale.push data - -return RelativeDates diff --git a/src/Miscellaneous/RemoveSpoilers.coffee b/src/Miscellaneous/RemoveSpoilers.coffee index f15c8516fa..a0c501c25e 100644 --- a/src/Miscellaneous/RemoveSpoilers.coffee +++ b/src/Miscellaneous/RemoveSpoilers.coffee @@ -9,10 +9,6 @@ RemoveSpoilers = name: 'Reveal Spoilers' cb: @node - Callbacks.CatalogThread.push - name: 'Reveal Spoilers' - cb: @node - if g.VIEW is 'archive' $.ready -> RemoveSpoilers.unspoiler $.id 'arc-list' @@ -20,11 +16,9 @@ RemoveSpoilers = RemoveSpoilers.unspoiler @nodes.comment unspoiler: (el) -> - spoilers = $$ 's', el + spoilers = $$ g.SITE.selectors.spoiler, el for spoiler in spoilers span = $.el 'span', className: 'removed-spoiler' $.replace spoiler, span $.add span, [spoiler.childNodes...] return - -return RemoveSpoilers diff --git a/src/Miscellaneous/Report.coffee b/src/Miscellaneous/Report.coffee index bdf5012334..c0ca047d27 100644 --- a/src/Miscellaneous/Report.coffee +++ b/src/Miscellaneous/Report.coffee @@ -1,6 +1,6 @@ Report = init: -> - return unless (match = location.search.match /\bno=(\d+)/) + return if not (match = location.search.match /\bno=(\d+)/) Captcha.replace.init() @postID = +match[1] $.ready @ready @@ -8,20 +8,96 @@ Report = ready: -> $.addStyle CSS.report - if not Conf['Use Recaptcha v1 in Reports'] and not Conf['Force Noscript Captcha'] and Main.jsEnabled - new MutationObserver(-> - Report.fit 'iframe[src^="https://www.google.com/recaptcha/api2/frame"]' - Report.fit 'body' - ).observe d.body, - childList: true - attributes: true - subtree: true - else + Report.archive() if Conf['Archive Report'] + + new MutationObserver(-> + Report.fit 'iframe[src^="https://www.google.com/recaptcha/api2/frame"]' Report.fit 'body' + ).observe d.body, + childList: true + attributes: true + subtree: true + Report.fit 'body' fit: (selector) -> - return unless (el = $ selector, doc) and getComputedStyle(el).visibility isnt 'hidden' + return if not ((el = $ selector, doc) and getComputedStyle(el).visibility isnt 'hidden') dy = el.getBoundingClientRect().bottom - doc.clientHeight + 8 window.resizeBy 0, dy if dy > 0 -return Report + archive: -> + return unless (urls = Redirect.report g.BOARD.ID).length + + form = $ 'form' + types = $.id 'reportTypes' + message = $ 'h3' + + fieldset = $.el 'fieldset', + id: 'archive-report' + hidden: true + , + `<%= readHTML('ArchiveReport.html') %>` + enabled = $ '#archive-report-enabled', fieldset + reason = $ '#archive-report-reason', fieldset + submit = $ '#archive-report-submit', fieldset + + $.on enabled, 'change', -> + reason.disabled = !@checked + + if form and types + fieldset.hidden = !$('[value="31"]', types).checked + $.on types, 'change', (e) -> + fieldset.hidden = (e.target.value isnt '31') + Report.fit 'body' + $.after types, fieldset + Report.fit 'body' + $.one form, 'submit', (e) -> + if !fieldset.hidden and enabled.checked + e.preventDefault() + Report.archiveSubmit urls, reason.value, (results) => + @action = '#archiveresults=' + encodeURIComponent JSON.stringify results + @submit() + else if message + fieldset.hidden = /Report submitted!/.test(message.textContent) + $.on enabled, 'change', -> + submit.hidden = !@checked + $.after message, fieldset + $.on submit, 'click', -> + Report.archiveSubmit urls, reason.value, Report.archiveResults + + if (match = location.hash.match /^#archiveresults=(.*)$/) + try + Report.archiveResults JSON.parse decodeURIComponent match[1] + + archiveSubmit: (urls, reason, cb) -> + form = $.formData + board: g.BOARD.ID + num: Report.postID + reason: reason + results = [] + for [name, url] in urls + do (name, url) -> + $.ajax url, { + onloadend: -> + results.push [name, @response or {error: ''}] + if results.length is urls.length + cb results + form + } + return + + archiveResults: (results) -> + fieldset = $.id 'archive-report' + for [name, response] in results + line = $.el 'h3', + className: 'archive-report-response' + if 'success' of response + $.addClass line, 'archive-report-success' + line.textContent = "#{name}: #{response.success}" + else + $.addClass line, 'archive-report-error' + line.textContent = "#{name}: #{response.error or 'Error reporting post.'}" + if fieldset + $.before fieldset, line + else + $.add d.body, line + return diff --git a/src/Miscellaneous/Report/ArchiveReport.html b/src/Miscellaneous/Report/ArchiveReport.html new file mode 100644 index 0000000000..d34db6b74f --- /dev/null +++ b/src/Miscellaneous/Report/ArchiveReport.html @@ -0,0 +1,4 @@ + + + + diff --git a/src/Miscellaneous/ThreadLinks.coffee b/src/Miscellaneous/ThreadLinks.coffee index 30cc92c6d8..2edd4d45de 100644 --- a/src/Miscellaneous/ThreadLinks.coffee +++ b/src/Miscellaneous/ThreadLinks.coffee @@ -18,5 +18,3 @@ ThreadLinks = process: (link) -> link.target = '_blank' - -return ThreadLinks diff --git a/src/Miscellaneous/Time.coffee b/src/Miscellaneous/Time.coffee index 3b195e61cc..e05c296bff 100644 --- a/src/Miscellaneous/Time.coffee +++ b/src/Miscellaneous/Time.coffee @@ -1,17 +1,19 @@ Time = init: -> - return unless g.VIEW in ['index', 'thread'] and Conf['Time Formatting'] + return unless g.VIEW in ['index', 'thread', 'archive'] and Conf['Time Formatting'] Callbacks.Post.push name: 'Time Formatting' cb: @node node: -> - return if @isClone - @nodes.date.textContent = Time.format Conf['time'], @info.date + return if !@info.date or @isClone + {textContent} = @nodes.date + @nodes.date.textContent = textContent.match(/^\s*/)[0] + Time.format(Conf['time'], @info.date) + textContent.match(/\s*$/)[0] + format: (formatString, date) -> formatString.replace /%(.)/g, (s, c) -> - if c of Time.formatters + if $.hasOwn(Time.formatters, c) Time.formatters[c].call(date) else s @@ -41,13 +43,26 @@ Time = 'December' ] + localeFormat: (date, options, defaultValue) -> + if Conf['timeLocale'] + try + return Intl.DateTimeFormat(Conf['timeLocale'], options).format(date) + defaultValue + + localeFormatPart: (date, options, part, defaultValue) -> + if Conf['timeLocale'] + try + parts = Intl.DateTimeFormat(Conf['timeLocale'], options).formatToParts(date) + return parts.map((x) -> if x.type is part then x.value else '').join('') + defaultValue + zeroPad: (n) -> if n < 10 then "0#{n}" else n formatters: - a: -> Time.day[@getDay()][...3] - A: -> Time.day[@getDay()] - b: -> Time.month[@getMonth()][...3] - B: -> Time.month[@getMonth()] + a: -> Time.localeFormat @, {weekday: 'short'}, Time.day[@getDay()][...3] + A: -> Time.localeFormat @, {weekday: 'long'}, Time.day[@getDay()] + b: -> Time.localeFormat @, {month: 'short'}, Time.month[@getMonth()][...3] + B: -> Time.localeFormat @, {month: 'long'}, Time.month[@getMonth()] d: -> Time.zeroPad @getDate() e: -> @getDate() H: -> Time.zeroPad @getHours() @@ -56,11 +71,9 @@ Time = l: -> @getHours() % 12 or 12 m: -> Time.zeroPad @getMonth() + 1 M: -> Time.zeroPad @getMinutes() - p: -> if @getHours() < 12 then 'AM' else 'PM' - P: -> if @getHours() < 12 then 'am' else 'pm' + p: -> Time.localeFormatPart @, {hour: 'numeric', hour12: true}, 'dayperiod', (if @getHours() < 12 then 'AM' else 'PM') + P: -> Time.formatters.p.call(@).toLowerCase() S: -> Time.zeroPad @getSeconds() y: -> @getFullYear().toString()[2..] Y: -> @getFullYear() '%': -> '%' - -return Time diff --git a/src/Miscellaneous/Tinyboard.coffee b/src/Miscellaneous/Tinyboard.coffee new file mode 100644 index 0000000000..c662e92593 --- /dev/null +++ b/src/Miscellaneous/Tinyboard.coffee @@ -0,0 +1,22 @@ +Tinyboard = + init: -> + return unless g.SITE.software is 'tinyboard' + if g.VIEW is 'thread' + Main.ready -> + $.global -> + {boardID, threadID} = document.currentScript.dataset + threadID = +threadID + form = document.querySelector 'form[name="post"]' + window.$(document).ajaxComplete (event, request, settings) -> + return unless settings.url is form.action + return unless (postID = +request.responseJSON?.id) + detail = {boardID, threadID, postID} + try + {redirect, noko} = request.responseJSON + if redirect and originalNoko? and !originalNoko and !noko + detail.redirect = redirect + event = new CustomEvent 'QRPostSuccessful', {bubbles: true, detail: detail} + document.dispatchEvent event + originalNoko = window.tb_settings?.ajax?.always_noko_replies + ((window.tb_settings or= {}).ajax or= {}).always_noko_replies = true + , {boardID: g.BOARD.ID, threadID: g.THREADID} diff --git a/src/Monitoring/Favicon.coffee b/src/Monitoring/Favicon.coffee index 0b5235b49d..be88d2599d 100644 --- a/src/Monitoring/Favicon.coffee +++ b/src/Monitoring/Favicon.coffee @@ -1,65 +1,75 @@ Favicon = init: -> - $.asap (-> d.head and Favicon.el = $ 'link[rel="shortcut icon"]', d.head), Favicon.initAsap - + $.asap (-> d.head and (Favicon.el = $ 'link[rel="shortcut icon"]', d.head)), Favicon.initAsap + + set: (status) -> + Favicon.status = status + if Favicon.el + Favicon.el.href = Favicon[status] + # `favicon.href = href` doesn't work on Firefox. + $.add d.head, Favicon.el + initAsap: -> Favicon.el.type = 'image/x-icon' {href} = Favicon.el - Favicon.SFW = /ws\.ico$/.test href + Favicon.isSFW = /ws\.ico$/.test href Favicon.default = href Favicon.switch() + if Favicon.status + Favicon.set Favicon.status switch: -> items = { ferongr: [ - '<%= readBase64('ferongr.unreadDead.png') %>' - '<%= readBase64('ferongr.unreadDeadY.png') %>' - '<%= readBase64('ferongr.unreadSFW.png') %>' - '<%= readBase64('ferongr.unreadSFWY.png') %>' - '<%= readBase64('ferongr.unreadNSFW.png') %>' - '<%= readBase64('ferongr.unreadNSFWY.png') %>' + '<%= readBase64("ferongr.unreadDead.png") %>' + '<%= readBase64("ferongr.unreadDeadY.png") %>' + '<%= readBase64("ferongr.unreadSFW.png") %>' + '<%= readBase64("ferongr.unreadSFWY.png") %>' + '<%= readBase64("ferongr.unreadNSFW.png") %>' + '<%= readBase64("ferongr.unreadNSFWY.png") %>' ] 'xat-': [ - '<%= readBase64('xat-.unreadDead.png') %>' - '<%= readBase64('xat-.unreadDeadY.png') %>' - '<%= readBase64('xat-.unreadSFW.png') %>' - '<%= readBase64('xat-.unreadSFWY.png') %>' - '<%= readBase64('xat-.unreadNSFW.png') %>' - '<%= readBase64('xat-.unreadNSFWY.png') %>' + '<%= readBase64("xat-.unreadDead.png") %>' + '<%= readBase64("xat-.unreadDeadY.png") %>' + '<%= readBase64("xat-.unreadSFW.png") %>' + '<%= readBase64("xat-.unreadSFWY.png") %>' + '<%= readBase64("xat-.unreadNSFW.png") %>' + '<%= readBase64("xat-.unreadNSFWY.png") %>' ] Mayhem: [ - '<%= readBase64('Mayhem.unreadDead.png') %>' - '<%= readBase64('Mayhem.unreadDeadY.png') %>' - '<%= readBase64('Mayhem.unreadSFW.png') %>' - '<%= readBase64('Mayhem.unreadSFWY.png') %>' - '<%= readBase64('Mayhem.unreadNSFW.png') %>' - '<%= readBase64('Mayhem.unreadNSFWY.png') %>' + '<%= readBase64("Mayhem.unreadDead.png") %>' + '<%= readBase64("Mayhem.unreadDeadY.png") %>' + '<%= readBase64("Mayhem.unreadSFW.png") %>' + '<%= readBase64("Mayhem.unreadSFWY.png") %>' + '<%= readBase64("Mayhem.unreadNSFW.png") %>' + '<%= readBase64("Mayhem.unreadNSFWY.png") %>' ] '4chanJS': [ - '<%= readBase64('4chanJS.unreadDead.png') %>' - '<%= readBase64('4chanJS.unreadDeadY.png') %>' - '<%= readBase64('4chanJS.unreadSFW.png') %>' - '<%= readBase64('4chanJS.unreadSFWY.png') %>' - '<%= readBase64('4chanJS.unreadNSFW.png') %>' - '<%= readBase64('4chanJS.unreadNSFWY.png') %>' + '<%= readBase64("4chanJS.unreadDead.png") %>' + '<%= readBase64("4chanJS.unreadDeadY.png") %>' + '<%= readBase64("4chanJS.unreadSFW.png") %>' + '<%= readBase64("4chanJS.unreadSFWY.png") %>' + '<%= readBase64("4chanJS.unreadNSFW.png") %>' + '<%= readBase64("4chanJS.unreadNSFWY.png") %>' ] Original: [ - '<%= readBase64('Original.unreadDead.png') %>' - '<%= readBase64('Original.unreadDeadY.png') %>' - '<%= readBase64('Original.unreadSFW.png') %>' - '<%= readBase64('Original.unreadSFWY.png') %>' - '<%= readBase64('Original.unreadNSFW.png') %>' - '<%= readBase64('Original.unreadNSFWY.png') %>' + '<%= readBase64("Original.unreadDead.png") %>' + '<%= readBase64("Original.unreadDeadY.png") %>' + '<%= readBase64("Original.unreadSFW.png") %>' + '<%= readBase64("Original.unreadSFWY.png") %>' + '<%= readBase64("Original.unreadNSFW.png") %>' + '<%= readBase64("Original.unreadNSFWY.png") %>' ] 'Metro': [ - '<%= readBase64('Metro.unreadDead.png') %>' - '<%= readBase64('Metro.unreadDeadY.png') %>' - '<%= readBase64('Metro.unreadSFW.png') %>' - '<%= readBase64('Metro.unreadSFWY.png') %>' - '<%= readBase64('Metro.unreadNSFW.png') %>' - '<%= readBase64('Metro.unreadNSFWY.png') %>' + '<%= readBase64("Metro.unreadDead.png") %>' + '<%= readBase64("Metro.unreadDeadY.png") %>' + '<%= readBase64("Metro.unreadSFW.png") %>' + '<%= readBase64("Metro.unreadSFWY.png") %>' + '<%= readBase64("Metro.unreadNSFW.png") %>' + '<%= readBase64("Metro.unreadNSFWY.png") %>' ] - }[Conf['favicon']] + } + items = $.getOwn(items, Conf['favicon']) f = Favicon t = 'data:image/png;base64,' @@ -71,14 +81,14 @@ Favicon = f.update() update: -> - if @SFW + if @isSFW @unread = @unreadSFW @unreadY = @unreadSFWY else @unread = @unreadNSFW @unreadY = @unreadNSFWY - dead: 'data:image/gif;base64,<%= readBase64('dead.gif') %>' - logo: 'data:image/png;base64,<%= readBase64('/src/meta/icon128.png') %>' - -return Favicon + SFW: '//s.4cdn.org/image/favicon-ws.ico' + NSFW: '//s.4cdn.org/image/favicon.ico' + dead: 'data:image/gif;base64,<%= readBase64("dead.gif") %>' + logo: 'data:image/png;base64,<%= readBase64("/src/meta/icon128.png") %>' diff --git a/src/Monitoring/Favicon/4chanJS.unreadDeadY.png b/src/Monitoring/Favicon/4chanJS.unreadDeadY.png index aaefd78b44..fb8f64a34f 100644 Binary files a/src/Monitoring/Favicon/4chanJS.unreadDeadY.png and b/src/Monitoring/Favicon/4chanJS.unreadDeadY.png differ diff --git a/src/Monitoring/Favicon/4chanJS.unreadNSFWY.png b/src/Monitoring/Favicon/4chanJS.unreadNSFWY.png index a03cb6ee0f..cd26683a69 100644 Binary files a/src/Monitoring/Favicon/4chanJS.unreadNSFWY.png and b/src/Monitoring/Favicon/4chanJS.unreadNSFWY.png differ diff --git a/src/Monitoring/Favicon/4chanJS.unreadSFWY.png b/src/Monitoring/Favicon/4chanJS.unreadSFWY.png index 58e69ab96c..ffeca46361 100644 Binary files a/src/Monitoring/Favicon/4chanJS.unreadSFWY.png and b/src/Monitoring/Favicon/4chanJS.unreadSFWY.png differ diff --git a/src/Monitoring/Favicon/Metro.unreadDeadY.png b/src/Monitoring/Favicon/Metro.unreadDeadY.png index 458ec9cdf5..4c8ed3ff49 100644 Binary files a/src/Monitoring/Favicon/Metro.unreadDeadY.png and b/src/Monitoring/Favicon/Metro.unreadDeadY.png differ diff --git a/src/Monitoring/Favicon/Metro.unreadNSFWY.png b/src/Monitoring/Favicon/Metro.unreadNSFWY.png index 5c34ec8a0c..00709b3a1b 100644 Binary files a/src/Monitoring/Favicon/Metro.unreadNSFWY.png and b/src/Monitoring/Favicon/Metro.unreadNSFWY.png differ diff --git a/src/Monitoring/Favicon/Metro.unreadSFWY.png b/src/Monitoring/Favicon/Metro.unreadSFWY.png index 66ed2c844d..5077370d30 100644 Binary files a/src/Monitoring/Favicon/Metro.unreadSFWY.png and b/src/Monitoring/Favicon/Metro.unreadSFWY.png differ diff --git a/src/Monitoring/Favicon/xat-.unreadDead.png b/src/Monitoring/Favicon/xat-.unreadDead.png index 3c6838e708..2a536e9454 100644 Binary files a/src/Monitoring/Favicon/xat-.unreadDead.png and b/src/Monitoring/Favicon/xat-.unreadDead.png differ diff --git a/src/Monitoring/Favicon/xat-.unreadDeadY.png b/src/Monitoring/Favicon/xat-.unreadDeadY.png index 00d9b9b43d..0930e1d7df 100644 Binary files a/src/Monitoring/Favicon/xat-.unreadDeadY.png and b/src/Monitoring/Favicon/xat-.unreadDeadY.png differ diff --git a/src/Monitoring/Favicon/xat-.unreadNSFW.png b/src/Monitoring/Favicon/xat-.unreadNSFW.png index 36fde61aac..93ae220368 100644 Binary files a/src/Monitoring/Favicon/xat-.unreadNSFW.png and b/src/Monitoring/Favicon/xat-.unreadNSFW.png differ diff --git a/src/Monitoring/Favicon/xat-.unreadNSFWY.png b/src/Monitoring/Favicon/xat-.unreadNSFWY.png index 0e4c764460..b55ae75a6c 100644 Binary files a/src/Monitoring/Favicon/xat-.unreadNSFWY.png and b/src/Monitoring/Favicon/xat-.unreadNSFWY.png differ diff --git a/src/Monitoring/Favicon/xat-.unreadSFW.png b/src/Monitoring/Favicon/xat-.unreadSFW.png index b92bf86b20..419fea5163 100644 Binary files a/src/Monitoring/Favicon/xat-.unreadSFW.png and b/src/Monitoring/Favicon/xat-.unreadSFW.png differ diff --git a/src/Monitoring/Favicon/xat-.unreadSFWY.png b/src/Monitoring/Favicon/xat-.unreadSFWY.png index bc0229665a..b12d5e8c13 100644 Binary files a/src/Monitoring/Favicon/xat-.unreadSFWY.png and b/src/Monitoring/Favicon/xat-.unreadSFWY.png differ diff --git a/src/Monitoring/MarkNewIPs.coffee b/src/Monitoring/MarkNewIPs.coffee index 6f6bcdfa2a..a9c3978090 100644 --- a/src/Monitoring/MarkNewIPs.coffee +++ b/src/Monitoring/MarkNewIPs.coffee @@ -1,6 +1,6 @@ MarkNewIPs = init: -> - return if g.VIEW isnt 'thread' or !Conf['Mark New IPs'] + return unless g.SITE.software is 'yotsuba' and g.VIEW is 'thread' and Conf['Mark New IPs'] Callbacks.Thread.push name: 'Mark New IPs' cb: @node @@ -18,10 +18,10 @@ MarkNewIPs = when postCount - MarkNewIPs.postCount + deletedPosts.length i = MarkNewIPs.ipCount for fullID in newPosts - MarkNewIPs.markNew g.posts[fullID], ++i + MarkNewIPs.markNew g.posts.get(fullID), ++i when -deletedPosts.length for fullID in newPosts - MarkNewIPs.markOld g.posts[fullID] + MarkNewIPs.markOld g.posts.get(fullID) MarkNewIPs.ipCount = ipCount MarkNewIPs.postCount = postCount @@ -40,5 +40,3 @@ MarkNewIPs = markOld: (post) -> post.nodes.nameBlock.title = 'Not the first post from this IP.' $.addClass post.nodes.root, 'old-ip' - -return MarkNewIPs diff --git a/src/Monitoring/ReplyPruning.coffee b/src/Monitoring/ReplyPruning.coffee index 3c9524f73d..8be5983b45 100644 --- a/src/Monitoring/ReplyPruning.coffee +++ b/src/Monitoring/ReplyPruning.coffee @@ -2,8 +2,6 @@ ReplyPruning = init: -> return unless g.VIEW is 'thread' and Conf['Reply Pruning'] - @active = not (Conf['Quote Threading'] and Conf['Thread Quotes']) - @container = $.frag() @summary = $.el 'span', @@ -14,17 +12,18 @@ ReplyPruning = @inputs.enabled.checked = !@inputs.enabled.checked $.event 'change', null, @inputs.enabled - label = UI.checkbox 'Prune Replies', 'Show Last', @active + label = UI.checkbox 'Prune Replies', 'Show Last', Conf['Prune All Threads'] el = $.el 'span', title: 'Maximum number of replies to show.' , - <%= html(' ') %> + `<%= html(' ') %>` $.prepend el, label @inputs = enabled: label.firstElementChild replies: el.lastElementChild + @setEnabled.call @inputs.enabled $.on @inputs.enabled, 'change', @setEnabled $.on @inputs.replies, 'change', $.cb.value @@ -57,17 +56,22 @@ ReplyPruning = node: -> ReplyPruning.thread = @ + if @isSticky + ReplyPruning.active = ReplyPruning.inputs.enabled.checked = true + if QuoteThreading.input + # Disable Quote Threading for this thread but don't save the setting. + Conf['Thread Quotes'] = QuoteThreading.input.checked = false + @posts.forEach (post) -> if post.isReply ReplyPruning.total++ - ReplyPruning.totalFiles++ if post.file + (ReplyPruning.totalFiles++ if post.file) # If we're linked to a post that we would hide, don't hide the posts in the first place. - # Also don't hide posts if we open the thread by a link to the OP. if ( ReplyPruning.active and /^#p\d+$/.test(location.hash) and - 0 <= @posts.keys.indexOf(location.hash[2..]) < 1 + Math.max(ReplyPruning.total - +Conf["Max Replies"], 0) + 1 <= @posts.keys.indexOf(location.hash[2..]) < 1 + Math.max(ReplyPruning.total - +Conf["Max Replies"], 0) ) ReplyPruning.active = ReplyPruning.inputs.enabled.checked = false @@ -84,7 +88,7 @@ ReplyPruning = return if e.detail[404] for fullID in e.detail.newPosts ReplyPruning.total++ - ReplyPruning.totalFiles++ if g.posts[fullID].file + ReplyPruning.totalFiles++ if g.posts.get(fullID).file return update: -> @@ -101,8 +105,9 @@ ReplyPruning = if ReplyPruning.hidden < hidden2 while ReplyPruning.hidden < hidden2 and ReplyPruning.position < posts.keys.length - post = posts[posts.keys[ReplyPruning.position++]] + post = posts.get(posts.keys[ReplyPruning.position++]) if post.isReply and not post.isFetchedQuote + $.add ReplyPruning.container, node while (node = ReplyPruning.summary.nextSibling) and node isnt post.nodes.root $.add ReplyPruning.container, post.nodes.root ReplyPruning.hidden++ ReplyPruning.hiddenFiles++ if post.file @@ -110,22 +115,21 @@ ReplyPruning = else if ReplyPruning.hidden > hidden2 frag = $.frag() while ReplyPruning.hidden > hidden2 and ReplyPruning.position > 0 - post = posts[posts.keys[--ReplyPruning.position]] + post = posts.get(posts.keys[--ReplyPruning.position]) if post.isReply and not post.isFetchedQuote + $.prepend frag, node while (node = ReplyPruning.container.lastChild) and node isnt post.nodes.root $.prepend frag, post.nodes.root ReplyPruning.hidden-- ReplyPruning.hiddenFiles-- if post.file $.after ReplyPruning.summary, frag - $.event 'PostsInserted' + $.event 'PostsInserted', null, ReplyPruning.summary.parentNode ReplyPruning.summary.textContent = if ReplyPruning.active - Build.summaryText '+', ReplyPruning.hidden, ReplyPruning.hiddenFiles + g.SITE.Build.summaryText '+', ReplyPruning.hidden, ReplyPruning.hiddenFiles else - Build.summaryText '-', ReplyPruning.total, ReplyPruning.totalFiles + g.SITE.Build.summaryText '-', ReplyPruning.total, ReplyPruning.totalFiles ReplyPruning.summary.hidden = (ReplyPruning.total <= +Conf["Max Replies"]) # Maintain position in thread when posts are added/removed above if hidden1 isnt hidden2 and (boardTop = Header.getTopOf $('.board')) < 0 window.scrollBy 0, Math.max(d.body.clientHeight - oldPos, window.scrollY + boardTop) - window.scrollY - -return ReplyPruning diff --git a/src/Monitoring/ThreadStats.coffee b/src/Monitoring/ThreadStats.coffee index 99f2f89407..4f5f58c44a 100644 --- a/src/Monitoring/ThreadStats.coffee +++ b/src/Monitoring/ThreadStats.coffee @@ -1,15 +1,22 @@ ThreadStats = + postCount: 0 + fileCount: 0 + postIndex: 0 + init: -> return if g.VIEW isnt 'thread' or !Conf['Thread Stats'] - statsHTML = <%= html( + if Conf['Page Count in Stats'] + @[if g.SITE.isPrunedByAge?(g.BOARD) then 'showPurgePos' else 'showPage'] = true + + statsHTML = `<%= html( '? / ?' + - '?{Conf["IP Count in Stats"]}{ / ?}' + + '?{Conf["IP Count in Stats"] && g.SITE.hasIPCount}{ / ?}' + '?{Conf["Page Count in Stats"]}{ / ?}' - ) %> + ) %>` statsTitle = 'Posts / Files' - statsTitle += ' / IPs' if Conf['IP Count in Stats'] - statsTitle += (if g.BOARD.ID is 'f' then ' / Purge Position' else ' / Page') if Conf['Page Count in Stats'] + statsTitle += ' / IPs' if Conf['IP Count in Stats'] and g.SITE.hasIPCount + statsTitle += (if @showPurgePos then ' / Purge Position' else ' / Page') if Conf['Page Count in Stats'] if Conf['Updater and Stats in Header'] @dialog = sc = $.el 'span', @@ -19,8 +26,8 @@ ThreadStats = Header.addShortcut 'stats', sc, 200 else - @dialog = sc = UI.dialog 'thread-stats', 'bottom: 0px; right: 0px;', - <%= html('
              &{statsHTML}
              ') %> + @dialog = sc = UI.dialog 'thread-stats', + `<%= html('
              &{statsHTML}
              ') %>` $.addClass doc, 'float' $.ready -> $.add d.body, sc @@ -37,35 +44,46 @@ ThreadStats = cb: @node node: -> - postCount = 0 - fileCount = 0 - @posts.forEach (post) -> - postCount++ - fileCount++ if post.file - ThreadStats.lastPost = post.info.date if ThreadStats.pageCountEl ThreadStats.thread = @ + ThreadStats.count() + ThreadStats.update() ThreadStats.fetchPage() - ThreadStats.update postCount, fileCount, @ipCount + $.on d, 'PostsInserted', -> $.queueTask ThreadStats.onPostsInserted $.on d, 'ThreadUpdate', ThreadStats.onUpdate + count: -> + {posts} = ThreadStats.thread + n = posts.keys.length + for i in [ThreadStats.postIndex...n] by 1 + post = posts.get(posts.keys[i]) + unless post.isFetchedQuote + ThreadStats.postCount++ + ThreadStats.fileCount += post.files.length + ThreadStats.postIndex = n + onUpdate: (e) -> return if e.detail[404] - {postCount, fileCount, ipCount, newPosts} = e.detail - ThreadStats.update postCount, fileCount, ipCount - return unless ThreadStats.pageCountEl - if newPosts.length - ThreadStats.lastPost = g.posts[newPosts[newPosts.length - 1]].info.date - if g.BOARD.ID isnt 'f' and ThreadStats.pageCountEl?.textContent isnt '1' + {postCount, fileCount} = e.detail + $.extend ThreadStats, {postCount, fileCount} + ThreadStats.postIndex = ThreadStats.thread.posts.keys.length + ThreadStats.update() + if ThreadStats.showPage and ThreadStats.pageCountEl.textContent isnt '1' ThreadStats.fetchPage() - update: (postCount, fileCount, ipCount) -> + onPostsInserted: -> + return unless ThreadStats.thread.posts.keys.length > ThreadStats.postIndex + ThreadStats.count() + ThreadStats.update() + if ThreadStats.showPage and ThreadStats.pageCountEl.textContent isnt '1' + ThreadStats.fetchPage() + + update: -> {thread, postCountEl, fileCountEl, ipCountEl} = ThreadStats - postCountEl.textContent = postCount - fileCountEl.textContent = fileCount - if ipCount? and ipCountEl - ipCountEl.textContent = ipCount - (if thread.postLimit and !thread.isSticky then $.addClass else $.rmClass) postCountEl, 'warning' - (if thread.fileLimit and !thread.isSticky then $.addClass else $.rmClass) fileCountEl, 'warning' + postCountEl.textContent = ThreadStats.postCount + fileCountEl.textContent = ThreadStats.fileCount + ipCountEl?.textContent = thread.ipCount ? '?' + postCountEl.classList.toggle 'warning', (thread.postLimit and !thread.isSticky) + fileCountEl.classList.toggle 'warning', (thread.fileLimit and !thread.isSticky) fetchPage: -> return unless ThreadStats.pageCountEl @@ -75,32 +93,46 @@ ThreadStats = $.addClass ThreadStats.pageCountEl, 'warning' return ThreadStats.timeout = setTimeout ThreadStats.fetchPage, 2 * $.MINUTE - $.ajax "//a.4cdn.org/#{ThreadStats.thread.board}/threads.json", onload: ThreadStats.onThreadsLoad, - whenModified: 'ThreadStats' + $.whenModified( + g.SITE.urls.threadsListJSON(ThreadStats.thread), + 'ThreadStats', + ThreadStats.onThreadsLoad + ) onThreadsLoad: -> if @status is 200 - for page in @response - if g.BOARD.ID is 'f' - purgePos = 1 + if ThreadStats.showPurgePos + purgePos = 1 + for page in @response for thread in page.threads if thread.no < ThreadStats.thread.ID purgePos++ - ThreadStats.pageCountEl.textContent = purgePos - else - for thread in page.threads when thread.no is ThreadStats.thread.ID - ThreadStats.pageCountEl.textContent = page.page - (if page.page is @response.length then $.addClass else $.rmClass) ThreadStats.pageCountEl, 'warning' - ThreadStats.lastPageUpdate = new Date thread.last_modified * $.SECOND - ThreadStats.retry() - return + ThreadStats.pageCountEl.textContent = purgePos + ThreadStats.pageCountEl.classList.toggle 'warning', (purgePos is 1) + else + i = nThreads = 0 + for page in @response + nThreads += page.threads.length + for page, pageNum in @response + for thread in page.threads + if thread.no is ThreadStats.thread.ID + ThreadStats.pageCountEl.textContent = pageNum + 1 + ThreadStats.pageCountEl.classList.toggle 'warning', (i >= nThreads - @response[0].threads.length) + ThreadStats.lastPageUpdate = new Date(thread.last_modified * $.SECOND) + ThreadStats.retry() + return + i++ else if @status is 304 ThreadStats.retry() retry: -> # If thread data is stale (modification date given < time of last post), try again. - if g.BOARD.ID isnt 'f' and ThreadStats.lastPost > ThreadStats.lastPageUpdate and ThreadStats.pageCountEl?.textContent isnt '1' - clearTimeout ThreadStats.timeout - ThreadStats.timeout = setTimeout ThreadStats.fetchPage, 5 * $.SECOND - -return ThreadStats + # Skip this on vichan sites due to sage posts not updating modification time in threads.json. + return unless ( + ThreadStats.showPage and + ThreadStats.pageCountEl.textContent isnt '1' and + !g.SITE.threadModTimeIgnoresSage and + ThreadStats.thread.posts.get(ThreadStats.thread.lastPost).info.date > ThreadStats.lastPageUpdate + ) + clearTimeout ThreadStats.timeout + ThreadStats.timeout = setTimeout ThreadStats.fetchPage, 5 * $.SECOND diff --git a/src/Monitoring/ThreadUpdater.coffee b/src/Monitoring/ThreadUpdater.coffee index a49b2456ee..fcee0a8e6f 100644 --- a/src/Monitoring/ThreadUpdater.coffee +++ b/src/Monitoring/ThreadUpdater.coffee @@ -1,6 +1,7 @@ ThreadUpdater = init: -> return if g.VIEW isnt 'thread' or !Conf['Thread Updater'] + @enabled = true # Chromium won't play audio created in an inactive tab until the tab has been focused, so set it up now. # XXX Sometimes the loading stalls in Firefox, esp. when opening in private browsing window followed by normal window. @@ -11,11 +12,11 @@ ThreadUpdater = if Conf['Updater and Stats in Header'] @dialog = sc = $.el 'span', id: 'updater' - $.extend sc, <%= html('') %> + $.extend sc, `<%= html('') %>` Header.addShortcut 'updater', sc, 100 else - @dialog = sc = UI.dialog 'updater', 'bottom: 0px; left: 0px;', - <%= html('
              ') %> + @dialog = sc = UI.dialog 'updater', + `<%= html('
              ') %>` $.addClass doc, 'float' $.ready -> $.add d.body, sc @@ -30,9 +31,9 @@ ThreadUpdater = updateLink = $.el 'span', className: 'brackets-wrap updatelink' - $.extend updateLink, <%= html('Update') %> + $.extend updateLink, `<%= html('Update') %>` Main.ready -> - $.add navLinksBot, [$.tn(' '), updateLink] if (navLinksBot = $ '.navLinksBot') + ($.add navLinksBot, [$.tn(' '), updateLink] if (navLinksBot = $ '.navLinksBot')) $.on updateLink.firstElementChild, 'click', @update subEntries = [] @@ -49,7 +50,7 @@ ThreadUpdater = subEntries.push el: el @settings = $.el 'span', - <%= html('Interval') %> + `<%= html('Interval') %>` $.on @settings, 'click', @intervalShortcut @@ -67,7 +68,7 @@ ThreadUpdater = node: -> ThreadUpdater.thread = @ - ThreadUpdater.root = @OP.nodes.root.parentNode + ThreadUpdater.root = @nodes.root ThreadUpdater.outdateCount = 0 # We must keep track of our own list of live posts/files @@ -77,7 +78,7 @@ ThreadUpdater = ThreadUpdater.fileIDs = [] @posts.forEach (post) -> ThreadUpdater.postIDs.push post.ID - ThreadUpdater.fileIDs.push post.ID if post.file + (ThreadUpdater.fileIDs.push post.ID if post.file) ThreadUpdater.cb.interval.call $.el 'input', value: Conf['Interval'] @@ -90,7 +91,7 @@ ThreadUpdater = http://freesound.org/people/pierrecartoons1979/sounds/90112/ cc-by-nc-3.0 ### - beep: 'data:audio/wav;base64,<%= readBase64('beep.wav') %>' + beep: 'data:audio/wav;base64,<%= readBase64("beep.wav") %>' playBeep: -> {audio} = ThreadUpdater @@ -128,17 +129,17 @@ ThreadUpdater = $.cb.value.call @ if e load: -> - {req} = ThreadUpdater - switch req.status + return if @ isnt ThreadUpdater.req # aborted + switch @status when 200 - ThreadUpdater.parse req + ThreadUpdater.parse @ if ThreadUpdater.thread.isArchived ThreadUpdater.kill() else ThreadUpdater.setInterval() when 404 # XXX workaround for 4chan sending false 404s - $.ajax "//a.4cdn.org/#{ThreadUpdater.thread.board}/catalog.json", onloadend: -> + $.ajax g.SITE.urls.catalogJSON({boardID: ThreadUpdater.thread.board.ID}), onloadend: -> if @status is 200 confirmed = true for page in @response @@ -151,9 +152,9 @@ ThreadUpdater = if confirmed ThreadUpdater.kill() else - ThreadUpdater.error req + ThreadUpdater.error @ else - ThreadUpdater.error req + ThreadUpdater.error @ kill: -> ThreadUpdater.thread.kill() @@ -230,15 +231,18 @@ ThreadUpdater = update: -> clearTimeout ThreadUpdater.timeoutID ThreadUpdater.set 'timer', '...', 'loading' - ThreadUpdater.req?.abort() - ThreadUpdater.req = $.ajax "//a.4cdn.org/#{ThreadUpdater.thread.board}/thread/#{ThreadUpdater.thread}.json", - onloadend: ThreadUpdater.cb.load - timeout: $.MINUTE - , - whenModified: 'ThreadUpdater' + if (oldReq = ThreadUpdater.req) + delete ThreadUpdater.req + oldReq.abort() + ThreadUpdater.req = $.whenModified( + g.SITE.urls.threadJSON({boardID: ThreadUpdater.thread.board.ID, threadID: ThreadUpdater.thread.ID}), + 'ThreadUpdater', + ThreadUpdater.cb.load, + {timeout: $.MINUTE} + ) updateThreadStatus: (type, status) -> - return unless hasChanged = ThreadUpdater.thread["is#{type}"] isnt status + return if not (hasChanged = ThreadUpdater.thread["is#{type}"] isnt status) ThreadUpdater.thread.setStatus type, status return if type is 'Closed' and ThreadUpdater.thread.isArchived change = if type is 'Sticky' @@ -262,9 +266,9 @@ ThreadUpdater = # XXX Reject updates that falsely delete the last post. return if postObjects[postObjects.length-1].no < lastPost and - new Date(req.getResponseHeader('Last-Modified')) - thread.posts[lastPost].info.date < 30 * $.SECOND + new Date(req.getResponseHeader('Last-Modified')) - thread.posts.get(lastPost).info.date < 30 * $.SECOND - Build.spoilerRange[board] = OP.custom_spoiler + g.SITE.Build.spoilerRange[board] = OP.custom_spoiler thread.setStatus 'Archived', !!OP.archived ThreadUpdater.updateThreadStatus 'Sticky', !!OP.sticky ThreadUpdater.updateThreadStatus 'Closed', !!OP.closed @@ -287,12 +291,12 @@ ThreadUpdater = continue if ID <= lastPost # XXX Resurrect wrongly deleted posts. - if (post = thread.posts[ID]) and not post.isFetchedQuote + if (post = thread.posts.get(ID)) and not post.isFetchedQuote post.resurrect() continue newPosts.push "#{board}.#{ID}" - node = Build.postFromObject postObject, board.ID + node = g.SITE.Build.postFromObject postObject, board.ID posts.push new Post node, thread, board # Fetching your own posts after posting delete ThreadUpdater.postID if ThreadUpdater.postID is ID @@ -300,14 +304,14 @@ ThreadUpdater = # Check for deleted posts. deletedPosts = [] for ID in ThreadUpdater.postIDs when ID not in index - thread.posts[ID].kill() + thread.posts.get(ID).kill() deletedPosts.push "#{board}.#{ID}" ThreadUpdater.postIDs = index # Check for deleted files. deletedFiles = [] for ID in ThreadUpdater.fileIDs when not (ID in files or "#{board}.#{ID}" in deletedPosts) - thread.posts[ID].kill true + thread.posts.get(ID).kill true deletedFiles.push "#{board}.#{ID}" ThreadUpdater.fileIDs = files @@ -337,7 +341,7 @@ ThreadUpdater = unless QuoteThreading.insert post firstPost or= post.nodes.root $.add ThreadUpdater.root, post.nodes.root - $.event 'PostsInserted' + $.event 'PostsInserted', null, ThreadUpdater.root if scroll if Conf['Bottom Scroll'] @@ -346,7 +350,7 @@ ThreadUpdater = Header.scrollTo firstPost if firstPost # Update IP count in original post form. - if OP.unique_ips? and ipCountEl = $.id('unique-ips') + if OP.unique_ips? and (ipCountEl = $.id('unique-ips')) ipCountEl.textContent = OP.unique_ips ipCountEl.previousSibling.textContent = ipCountEl.previousSibling.textContent.replace(/\b(?:is|are)\b/, if OP.unique_ips is 1 then 'is' else 'are') ipCountEl.nextSibling.textContent = ipCountEl.nextSibling.textContent.replace(/\bposters?\b/, if OP.unique_ips is 1 then 'poster' else 'posters') @@ -360,5 +364,3 @@ ThreadUpdater = postCount: OP.replies + 1 fileCount: OP.images + !!OP.fsize ipCount: OP.unique_ips - -return ThreadUpdater diff --git a/src/Monitoring/ThreadWatcher.coffee b/src/Monitoring/ThreadWatcher.coffee index bbef3f42b9..097a1a57b5 100644 --- a/src/Monitoring/ThreadWatcher.coffee +++ b/src/Monitoring/ThreadWatcher.coffee @@ -1,22 +1,22 @@ ThreadWatcher = init: -> - return unless (@enabled = Conf['Thread Watcher']) + return if not (@enabled = Conf['Thread Watcher']) @shortcut = sc = $.el 'a', id: 'watcher-link' textContent: 'Watcher' title: 'Thread Watcher' href: 'javascript:;' - className: 'disabled fa fa-eye' + className: 'fa fa-eye' @db = new DataBoard 'watchedThreads', @refresh, true - @dialog = UI.dialog 'thread-watcher', 'top: 50px; left: 0px;', <%= readHTML('ThreadWatcher.html') %> - + @dbLM = new DataBoard 'watcherLastModified', null, true + @dialog = UI.dialog 'thread-watcher', `<%= readHTML('ThreadWatcher.html') %>` @status = $ '#watcher-status', @dialog @list = @dialog.lastElementChild @refreshButton = $ '.refresh', @dialog @closeButton = $('.move > .close', @dialog) - @unreaddb = Unread.db or new DataBoard 'lastReadPosts' + @unreaddb = Unread.db or UnreadIndex.db or new DataBoard 'lastReadPosts' @unreadEnabled = Conf['Remember Last Read Post'] $.on d, 'QRPostSuccessful', @cb.post @@ -24,29 +24,33 @@ ThreadWatcher = $.on @refreshButton, 'click', @buttonFetchAll $.on @closeButton, 'click', @toggleWatcher - $.on d, '4chanXInitFinished', @ready + @menu.addHeaderMenuEntry() + $.onExists doc, 'body', @addDialog switch g.VIEW when 'index' - $.on d, 'IndexRefresh', @cb.onIndexRefresh + $.on d, 'IndexUpdate', @cb.onIndexUpdate when 'thread' $.on d, 'ThreadUpdate', @cb.onThreadRefresh if Conf['Fixed Thread Watcher'] $.addClass doc, 'fixed-watcher' - if Conf['Toggleable Thread Watcher'] + if !Conf['Persistent Thread Watcher'] + $.addClass ThreadWatcher.shortcut, 'disabled' @dialog.hidden = true - Header.addShortcut 'watcher', sc, 510 - $.addClass doc, 'toggleable-watcher' + Header.addShortcut 'watcher', sc, 510 + + ThreadWatcher.initLastModified() ThreadWatcher.fetchAuto() + $.on window, 'visibilitychange focus', -> $.queueTask ThreadWatcher.fetchAuto - if g.VIEW is 'index' and Conf['JSON Index'] and Conf['Menu'] and g.BOARD.ID isnt 'f' + if Conf['Menu'] and Index.enabled Menu.menu.addEntry el: $.el 'a', href: 'javascript:;' className: 'has-shortcut-text' - , <%= html('Alt+click') %> + , `<%= html('Alt+click') %>` order: 6 open: ({thread}) -> return false if Conf['Index Mode'] isnt 'catalog' @@ -57,10 +61,12 @@ ThreadWatcher = $.off @el, 'click', @cb if @cb @cb = -> $.event 'CloseMenu' - ThreadWatcher.toggle thread + ThreadWatcher.toggle thread, true $.on @el, 'click', @cb true + return unless g.VIEW in ['index', 'thread'] + Callbacks.Post.push name: 'Thread Watcher' cb: @node @@ -69,40 +75,46 @@ ThreadWatcher = cb: @catalogNode isWatched: (thread) -> - ThreadWatcher.db?.get {boardID: thread.board.ID, threadID: thread.ID} + !!ThreadWatcher.db?.get {boardID: thread.board.ID, threadID: thread.ID} + + isWatchedRaw: (boardID, threadID) -> + !!ThreadWatcher.db?.get {boardID, threadID} + + setToggler: (toggler, isWatched) -> + toggler.classList.toggle 'watched', isWatched + toggler.title = "#{if isWatched then 'Unwatch' else 'Watch'} Thread" node: -> return if @isReply if @isClone - toggler = $ '.watch-thread-link', @nodes.post + toggler = $ '.watch-thread-link', @nodes.info else toggler = $.el 'a', href: 'javascript:;' className: 'watch-thread-link' - $.before $('input', @nodes.post), toggler + $.before $('input', @nodes.info), toggler + siteID = g.SITE.ID + boardID = @board.ID + threadID = @thread.ID + data = ThreadWatcher.db.get {siteID, boardID, threadID} + ThreadWatcher.setToggler toggler, !!data $.on toggler, 'click', ThreadWatcher.cb.toggle + # Add missing excerpt for threads added by Auto Watch + if data and not data.excerpt? + $.queueTask => + ThreadWatcher.update siteID, boardID, threadID, {excerpt: Get.threadExcerpt @thread} catalogNode: -> $.addClass @nodes.root, 'watched' if ThreadWatcher.isWatched @thread - $.on @nodes.thumb.parentNode, 'click', (e) => + $.on @nodes.root, 'mousedown click', (e) => return unless e.button is 0 and e.altKey - ThreadWatcher.toggle @thread - e.preventDefault() - $.on @nodes.thumb.parentNode, 'mousedown', (e) -> - # Prevent highlighting thumbnail in Firefox. - e.preventDefault() if e.button is 0 and e.altKey - - ready: -> - $.off d, '4chanXInitFinished', ThreadWatcher.ready - return unless Main.isThisPageLegit() - ThreadWatcher.refresh() - $.add d.body, ThreadWatcher.dialog + ThreadWatcher.toggle @thread, true if e.type is 'click' + e.preventDefault() # Also on mousedown to prevent highlighting thumbnail in Firefox. - return unless Conf['Auto Watch'] - $.get 'AutoWatch', 0, ({AutoWatch}) -> - return unless thread = g.BOARD.threads[AutoWatch] - ThreadWatcher.add thread - $.delete 'AutoWatch' + addDialog: -> + return unless Main.isThisPageLegit() + ThreadWatcher.build() + $.prepend d.body, ThreadWatcher.dialog toggleWatcher: -> $.toggleClass ThreadWatcher.shortcut, 'disabled' @@ -111,55 +123,97 @@ ThreadWatcher = cb: openAll: -> return if $.hasClass @, 'disabled' - for a in $$ 'a[title]', ThreadWatcher.list + for a in $$ 'a.watcher-link', ThreadWatcher.list + $.open a.href + $.event 'CloseMenu' + openUnread: -> + return if $.hasClass @, 'disabled' + for a in $$ '.replies-unread > a.watcher-link', ThreadWatcher.list $.open a.href $.event 'CloseMenu' + openDeads: -> + return if $.hasClass @, 'disabled' + for a in $$ '.dead-thread > a.watcher-link', ThreadWatcher.list + $.open a.href + $.event 'CloseMenu' + clear: -> + return unless confirm "Delete ALL threads from watcher?" + for {siteID, boardID, threadID} in ThreadWatcher.getAll() + ThreadWatcher.db.delete {siteID, boardID, threadID} + ThreadWatcher.refresh true + $.event 'CloseMenu' pruneDeads: -> return if $.hasClass @, 'disabled' - ThreadWatcher.db.forceSync() - for {boardID, threadID, data} in ThreadWatcher.getAll() when data.isDead - delete ThreadWatcher.db.data.boards[boardID][threadID] - ThreadWatcher.db.deleteIfEmpty {boardID} - ThreadWatcher.db.save() - ThreadWatcher.refresh() + for {siteID, boardID, threadID, data} in ThreadWatcher.getAll() when data.isDead + ThreadWatcher.db.delete {siteID, boardID, threadID} + ThreadWatcher.refresh true + $.event 'CloseMenu' + dismiss: -> + for {siteID, boardID, threadID, data} in ThreadWatcher.getAll() when data.quotingYou + ThreadWatcher.update siteID, boardID, threadID, {dismiss: data.quotingYou or 0} $.event 'CloseMenu' toggle: -> {thread} = Get.postFromNode @ - Index.followedThreadID = thread.ID - ThreadWatcher.toggle thread - delete Index.followedThreadID + ThreadWatcher.toggle thread, true rm: -> + {siteID} = @parentNode.dataset [boardID, threadID] = @parentNode.dataset.fullID.split '.' - ThreadWatcher.rm boardID, +threadID + ThreadWatcher.rm siteID, boardID, +threadID, undefined, true post: (e) -> {boardID, threadID, postID} = e.detail + cb = PostRedirect.delay() if postID is threadID if Conf['Auto Watch'] - $.set 'AutoWatch', threadID + ThreadWatcher.addRaw boardID, threadID, {}, cb, true else if Conf['Auto Watch Reply'] - ThreadWatcher.add g.threads[boardID + '.' + threadID] - onIndexRefresh: -> + ThreadWatcher.add (g.threads.get(boardID + '.' + threadID) or new Thread(threadID, g.boards[boardID] or new Board(boardID))), cb, true + onIndexUpdate: (e) -> {db} = ThreadWatcher + siteID = g.SITE.ID boardID = g.BOARD.ID - db.forceSync() - for threadID, data of db.data.boards[boardID] when not data?.isDead and threadID not of g.BOARD.threads + nKilled = 0 + 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. + continue unless e.detail.threads.some (fullID) -> +fullID.split('.')[1] > threadID if Conf['Auto Prune'] or not (data and typeof data is 'object') # corrupt data db.delete {boardID, threadID} + nKilled++ else - if ThreadWatcher.unreadEnabled and Conf['Show Unread Count'] - ThreadWatcher.fetchStatus {boardID, threadID, data} - data.isDead = true - db.set {boardID, threadID, val: data} - ThreadWatcher.refresh() + ThreadWatcher.fetchStatus {siteID, boardID, threadID, data} + ThreadWatcher.refresh() if nKilled onThreadRefresh: (e) -> - thread = g.threads[e.detail.threadID] - return unless e.detail[404] and ThreadWatcher.db.get {boardID: thread.board.ID, threadID: thread.ID} + thread = g.threads.get(e.detail.threadID) + return unless e.detail[404] and ThreadWatcher.isWatched thread # Update dead status. ThreadWatcher.add thread requests: [] 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 g.SITE.ID then $.ajax else CrossOrigin.ajax + if force + delete $.lastModified.ThreadWatcher?[url] + req = $.whenModified( + url, + 'ThreadWatcher', + onloadend, + {timeout: $.MINUTE, ajax} + ) + ThreadWatcher.requests.push req + clearRequests: -> ThreadWatcher.requests = [] ThreadWatcher.fetched = 0 @@ -167,131 +221,220 @@ ThreadWatcher = $.rmClass ThreadWatcher.refreshButton, 'fa-spin' 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() ThreadWatcher.clearRequests() + initLastModified: -> + lm = ($.lastModified['ThreadWatcher'] or= $.dict()) + 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: -> clearTimeout ThreadWatcher.timeout return unless Conf['Auto Update Thread Watcher'] {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() - if now >= (db.data.lastChecked or 0) + interval - db.data.lastChecked = now - ThreadWatcher.fetchAllStatus() - db.save() + unless now - interval < (db.data.lastChecked or 0) <= now or d.hidden or not d.hasFocus() + ThreadWatcher.fetchAllStatus interval ThreadWatcher.timeout = setTimeout ThreadWatcher.fetchAuto, interval buttonFetchAll: -> - if ThreadWatcher.requests.length + if ThreadWatcher.syncing or ThreadWatcher.requests.length ThreadWatcher.abort() else ThreadWatcher.fetchAllStatus() - fetchAllStatus: -> - ThreadWatcher.db.forceSync() - ThreadWatcher.unreaddb.forceSync() - QuoteYou.db?.forceSync() - return unless (threads = ThreadWatcher.getAll()).length - for thread in threads - ThreadWatcher.fetchStatus thread + fetchAllStatus: (interval=0) -> + ThreadWatcher.status.textContent = '...' + $.addClass ThreadWatcher.refreshButton, 'fa-spin' + ThreadWatcher.syncing = true + dbs = [ThreadWatcher.db, ThreadWatcher.unreaddb, QuoteYou.db].filter((x) -> x) + n = 0 + for dbi in dbs + dbi.forceSync -> + if (++n) is dbs.length + return if !ThreadWatcher.syncing # aborted + delete ThreadWatcher.syncing + unless 0 <= Date.now() - (ThreadWatcher.db.data.lastChecked or 0) < interval # not checked in another tab + # XXX On vichan boards, last_modified field of threads.json does not account for sage posts. + # 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() + + fetchBoard: (board, deep) -> + return unless board.some (thread) -> !thread.data.isDead + force = false + for thread in board + {data} = thread + if !data.isDead and data.last isnt -1 + force = true if Conf['Show Page'] and !data.page? + force = thread.force = true if !data.modified? + {siteID, boardID} = board[0] + site = g.sites[siteID] + return unless site + urlF = if deep and site.threadModTimeIgnoresSage then 'catalogJSON' else 'threadsListJSON' + url = site.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] + lmDate = @getResponseHeader('Last-Modified') + ThreadWatcher.dbLM.extend {siteID, boardID, val: $.item(url, lmDate)} + threads = $.dict() + 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 g.sites[siteID].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) + (thread.newData or= {}).modified = modified + ThreadWatcher.fetchStatus thread + else + ThreadWatcher.fetchStatus thread return - fetchStatus: (thread, force) -> - {boardID, threadID, data} = thread + fetchStatus: (thread) -> + {siteID, boardID, threadID, data, force} = thread + url = g.sites[siteID]?.urls.threadJSON?({siteID, boardID, threadID}) + return unless url return if data.isDead and not force - if ThreadWatcher.requests.length is 0 - ThreadWatcher.status.textContent = '...' - $.addClass ThreadWatcher.refreshButton, 'fa-spin' - req = $.ajax "//a.4cdn.org/#{boardID}/thread/#{threadID}.json", - onloadend: -> - ThreadWatcher.parseStatus.call @, thread - timeout: $.MINUTE - , - whenModified: if force then false else 'ThreadWatcher' - ThreadWatcher.requests.push req - - parseStatus: ({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)}%" + return if data.last is -1 # 404 or no JSON API + ThreadWatcher.fetch url, {siteID, force}, [thread], ThreadWatcher.parseStatus + parseStatus: (thread, isArchiveURL) -> + {siteID, boardID, threadID, data, newData, force} = thread + site = g.sites[siteID] if @status is 200 and @response - isDead = !!@response.posts[0].archived + last = @response.posts[@response.posts.length-1].no + replies = @response.posts.length-1 + isDead = isArchived = !!(@response.posts[0].archived or isArchiveURL) if isDead and Conf['Auto Prune'] - ThreadWatcher.db.delete {boardID, threadID} - ThreadWatcher.refresh() + ThreadWatcher.rm siteID, boardID, threadID return - lastReadPost = ThreadWatcher.unreaddb.get - boardID: boardID - threadID: threadID - defaultValue: 0 + return if last is data.last and isDead is data.isDead and isArchived is data.isArchived - unread = quotingYou = 0 + lastReadPost = ThreadWatcher.unreaddb.get {siteID, boardID, threadID, defaultValue: 0} + unread = data.unread or 0 + quotingYou = data.quotingYou or 0 + youOP = !!QuoteYou.db?.get {siteID, boardID, threadID, postID: threadID} for postObj in @response.posts - continue unless postObj.no > lastReadPost - continue if QuoteYou.db?.get {boardID, threadID, postID: postObj.no} + continue unless postObj.no > (data.last or 0) and postObj.no > lastReadPost + continue if QuoteYou.db?.get {siteID, boardID, threadID, postID: postObj.no} - unread++ + quotesYou = false + if !Conf['Require OP Quote Link'] and youOP + quotesYou = true + else if QuoteYou.db and postObj.com + regexp = site.regexp.quotelinkHTML + regexp.lastIndex = 0 + while (match = regexp.exec postObj.com) + if QuoteYou.db.get { + siteID + boardID: if match[1] then encodeURIComponent(match[1]) else boardID + threadID: match[2] or threadID + postID: match[3] or match[2] or threadID + } + quotesYou = true + break + + if !unread or (!quotingYou and quotesYou) + continue if Filter.isHidden(site.Build.parseJSON postObj, {siteID, boardID}) - continue unless QuoteYou.db and postObj.com + unread++ + quotingYou = postObj.no if quotesYou - quotesYou = false - regexp = /]*\bhref="(?:\/([^\/]+)\/thread\/)?(\d+)?(?:#p(\d+))?"/g - while match = regexp.exec postObj.com - if QuoteYou.db.get { - boardID: match[1] or boardID - threadID: match[2] or threadID - postID: match[3] or match[2] or threadID - } - quotesYou = true - break - if quotesYou and not Filter.isHidden(Build.parseJSON postObj, boardID) - quotingYou++ - - if isDead isnt data.isDead or unread isnt data.unread or quotingYou isnt data.quotingYou - data.isDead = isDead - data.unread = unread - data.quotingYou = quotingYou - ThreadWatcher.db.set {boardID, threadID, val: data} - ThreadWatcher.refresh() + newData or= {} + $.extend newData, {last, replies, isDead, isArchived, unread, quotingYou} + ThreadWatcher.update siteID, boardID, threadID, newData else if @status is 404 - if Conf['Auto Prune'] - ThreadWatcher.db.delete {boardID, threadID} + archiveURL = g.sites[siteID]?.urls.archivedThreadJSON?({siteID, boardID, threadID}) + if !isArchiveURL and archiveURL + ThreadWatcher.fetch archiveURL, {siteID, force}, [thread, true], ThreadWatcher.parseStatus + else if site.mayLackJSON and !data.last? + ThreadWatcher.update siteID, boardID, threadID, {last: -1} else - data.isDead = true - delete data.unread - delete data.quotingYou - ThreadWatcher.db.set {boardID, threadID, val: data} - - ThreadWatcher.refresh() + ThreadWatcher.update siteID, boardID, threadID, {isDead: true} - getAll: -> + getAll: (groupByBoard) -> all = [] - for boardID, threads of ThreadWatcher.db.data.boards - if Conf['Current Board'] and boardID isnt g.BOARD.ID - continue - for threadID, data of threads when data and typeof data is 'object' - all.push {boardID, threadID, data} + for siteID, boards of ThreadWatcher.db.data + for boardID, threads of boards.boards + if Conf['Current Board'] and (siteID isnt g.SITE.ID or boardID isnt g.BOARD.ID) + continue + if groupByBoard + all.push (cont = []) + for threadID, data of threads when data and typeof data is 'object' + (if groupByBoard then cont else all).push {siteID, boardID, threadID, data} all - makeLine: (boardID, threadID, data) -> + makeLine: (siteID, boardID, threadID, data) -> x = $.el 'a', className: 'fa fa-times' href: 'javascript:;' $.on x, 'click', ThreadWatcher.cb.rm + {excerpt, isArchived} = data + excerpt or= "/#{boardID}/ - No.#{threadID}" + excerpt = ThreadWatcher.prefixes[siteID] + excerpt if Conf['Show Site Prefix'] + link = $.el 'a', - href: "/#{boardID}/thread/#{threadID}" - title: data.excerpt + href: g.sites[siteID]?.urls.thread({siteID, boardID, threadID}, isArchived) or '' + title: excerpt 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? count = $.el 'span', textContent: "(#{data.unread})" @@ -299,120 +442,154 @@ ThreadWatcher = $.add link, count title = $.el 'span', - textContent: data.excerpt + textContent: excerpt className: 'watcher-title' $.add link, title div = $.el 'div' fullID = "#{boardID}.#{threadID}" div.dataset.fullID = fullID + div.dataset.siteID = siteID $.addClass div, 'current' if g.VIEW is 'thread' and fullID is "#{g.BOARD}.#{g.THREADID}" $.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'] $.addClass div, 'replies-read' if data.unread is 0 $.addClass div, 'replies-unread' if data.unread - $.addClass div, 'replies-quoting-you' if data.quotingYou + $.addClass div, 'replies-quoting-you' if (data.quotingYou or 0) > (data.dismiss or 0) $.add div, [x, $.tn(' '), link] div - refresh: -> + setPrefixes: (threads) -> + prefixes = $.dict() + for {siteID} in threads + continue if siteID of prefixes + len = 0 + prefix = '' + conflicts = Object.keys(prefixes) + while conflicts.length > 0 + len++ + prefix = siteID[...len] + conflicts2 = [] + for siteID2 in conflicts + if siteID2[...len] is prefix + conflicts2.push siteID2 + else if prefixes[siteID2].length < len + prefixes[siteID2] = siteID2[...len] + conflicts = conflicts2 + prefixes[siteID] = prefix + ThreadWatcher.prefixes = prefixes + + build: -> nodes = [] - for {boardID, threadID, data} in ThreadWatcher.getAll() - nodes.push ThreadWatcher.makeLine boardID, threadID, data - + threads = ThreadWatcher.getAll() + ThreadWatcher.setPrefixes threads + for {siteID, boardID, threadID, data} in threads + # Add missing excerpt for threads added by Auto Watch + if not data.excerpt? and siteID is g.SITE.ID and (thread = g.threads.get("#{boardID}.#{threadID}")) and thread.OP + ThreadWatcher.db.extend {boardID, threadID, val: {excerpt: Get.threadExcerpt thread}} + nodes.push ThreadWatcher.makeLine siteID, boardID, threadID, data {list} = ThreadWatcher $.rmAll list $.add list, nodes + ThreadWatcher.refreshIcon() + + refresh: (manual) -> + ThreadWatcher.build() + g.threads.forEach (thread) -> - helper = if ThreadWatcher.isWatched thread then ['addClass', 'Unwatch'] else ['rmClass', 'Watch'] + isWatched = ThreadWatcher.isWatched thread if thread.OP for post in [thread.OP, thread.OP.clones...] - toggler = $ '.watch-thread-link', post.nodes.post - $[helper[0]] toggler, 'watched' - toggler.title = "#{helper[1]} Thread" - $[helper[0]] thread.catalogView.nodes.root, 'watched' if thread.catalogView - - ThreadWatcher.refreshIcon() + if (toggler = $ '.watch-thread-link', post.nodes.info) + ThreadWatcher.setToggler toggler, isWatched + (thread.catalogView.nodes.root.classList.toggle 'watched', isWatched if thread.catalogView) - for refresher in ThreadWatcher.menu.refreshers - refresher() - - if Index.nodes and Conf['Pin Watched Threads'] - Index.sort() - Index.buildIndex() + if Conf['Pin Watched Threads'] + $.event 'SortIndex', {deferred: not (manual and Conf['Index Mode'] is 'catalog')} refreshIcon: -> for className in ['replies-unread', 'replies-quoting-you'] ThreadWatcher.shortcut.classList.toggle className, !!$(".#{className}", ThreadWatcher.dialog) return - update: (boardID, threadID, newData) -> - return unless data = ThreadWatcher.db?.get {boardID, threadID} + update: (siteID, boardID, threadID, newData) -> + return if not (data = ThreadWatcher.db?.get {siteID, boardID, threadID}) if newData.isDead and Conf['Auto Prune'] - ThreadWatcher.db.delete {boardID, threadID} - ThreadWatcher.refresh() + ThreadWatcher.rm siteID, boardID, threadID return + if newData.isDead or newData.last is -1 + for key in ['isArchived', 'page', 'lastPage', 'unread', 'quotingyou'] when key not of newData + newData[key] = undefined + if newData.last? and newData.last < data.last + newData.modified = undefined n = 0 n++ for key, val of newData when data[key] isnt val return unless n - ThreadWatcher.db.forceSync() - return unless data = ThreadWatcher.db.get {boardID, threadID} - $.extend data, newData - ThreadWatcher.db.set {boardID, threadID, val: data} - if line = $ "#watched-threads > [data-full-i-d='#{boardID}.#{threadID}']", ThreadWatcher.dialog - newLine = ThreadWatcher.makeLine boardID, threadID, data + ThreadWatcher.db.extend {siteID, boardID, threadID, val: newData} + if (line = $ "#watched-threads > [data-site-i-d='#{siteID}'][data-full-i-d='#{boardID}.#{threadID}']", ThreadWatcher.dialog) + newLine = ThreadWatcher.makeLine siteID, boardID, threadID, data $.replace line, newLine ThreadWatcher.refreshIcon() else ThreadWatcher.refresh() set404: (boardID, threadID, cb) -> - return cb() unless data = ThreadWatcher.db?.get {boardID, threadID} + return cb() if not (data = ThreadWatcher.db?.get {boardID, threadID}) if Conf['Auto Prune'] ThreadWatcher.db.delete {boardID, threadID} return cb() - return cb() if data.isDead and not (data.unread? or data.quotingYou?) - data.isDead = true - delete data.unread - delete data.quotingYou - ThreadWatcher.db.set {boardID, threadID, val: data}, cb + return cb() if data.isDead and not (data.isArchived? or data.page? or data.lastPage? or data.unread? or data.quotingYou?) + ThreadWatcher.db.extend {boardID, threadID, val: {isDead: true, isArchived: undefined, page: undefined, lastPage: undefined, unread: undefined, quotingYou: undefined}}, cb - toggle: (thread) -> + toggle: (thread, manual) -> + siteID = g.SITE.ID boardID = thread.board.ID threadID = thread.ID if ThreadWatcher.db.get {boardID, threadID} - ThreadWatcher.rm boardID, threadID + ThreadWatcher.rm siteID, boardID, threadID, undefined, manual else - ThreadWatcher.add thread + ThreadWatcher.add thread, undefined, manual - add: (thread) -> + add: (thread, cb, manual) -> data = {} + siteID = g.SITE.ID boardID = thread.board.ID threadID = thread.ID if thread.isDead if Conf['Auto Prune'] and ThreadWatcher.db.get {boardID, threadID} - ThreadWatcher.rm boardID, threadID + ThreadWatcher.rm siteID, boardID, threadID, cb return data.isDead = true - data.excerpt = Get.threadExcerpt thread - ThreadWatcher.db.set {boardID, threadID, val: data} - ThreadWatcher.refresh() - if ThreadWatcher.unreadEnabled and Conf['Show Unread Count'] - ThreadWatcher.fetchStatus {boardID, threadID, data}, true + data.excerpt = Get.threadExcerpt thread if thread.OP + ThreadWatcher.addRaw boardID, threadID, data, cb, manual + + addRaw: (boardID, threadID, data, cb, manual) -> + oldData = ThreadWatcher.db.get {boardID, threadID, defaultValue: $.dict()} + delete oldData.last + delete oldData.modified + $.extend oldData, data + ThreadWatcher.db.set {boardID, threadID, val: oldData}, cb + ThreadWatcher.refresh manual + thread = {siteID: g.SITE.ID, boardID, threadID, data, force: true} + if Conf['Show Page'] and !data.isDead + ThreadWatcher.fetchBoard [thread] + else if ThreadWatcher.unreadEnabled and Conf['Show Unread Count'] + ThreadWatcher.fetchStatus thread - rm: (boardID, threadID) -> - ThreadWatcher.db.delete {boardID, threadID} - ThreadWatcher.refresh() + rm: (siteID, boardID, threadID, cb, manual) -> + ThreadWatcher.db.delete {siteID, boardID, threadID}, cb + ThreadWatcher.refresh manual menu: - refreshers: [] init: -> return if !Conf['Thread Watcher'] menu = @menu = new UI.Menu 'thread watcher' $.on $('.menu-button', ThreadWatcher.dialog), 'click', (e) -> menu.toggle e, @, ThreadWatcher - @addHeaderMenuEntry() @addMenuEntries() addHeaderMenuEntry: -> @@ -422,53 +599,85 @@ ThreadWatcher = Header.menu.addEntry el: entryEl order: 60 - $.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 + 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.get("#{g.BOARD}.#{g.THREADID}"), true addMenuEntries: -> entries = [] # `Open all` entry entries.push + text: 'Open all threads' cb: ThreadWatcher.cb.openAll - entry: - el: $.el 'a', - textContent: 'Open all threads' - refresh: -> (if ThreadWatcher.list.firstElementChild then $.rmClass else $.addClass) @el, 'disabled' + open: -> + @el.classList.toggle 'disabled', !ThreadWatcher.list.firstElementChild + true + + # `Open Unread` entry + entries.push + text: 'Open unread threads' + cb: ThreadWatcher.cb.openUnread + open: -> + @el.classList.toggle 'disabled', !$('.replies-unread', ThreadWatcher.list) + true + + # `Open dead threads` entry + entries.push + text: 'Open dead threads' + cb: ThreadWatcher.cb.openDeads + open: -> + @el.classList.toggle 'disabled', !$('.dead-thread', ThreadWatcher.list) + true + + entries.push + text: 'Clear all threads' + cb: ThreadWatcher.cb.clear + open: -> + @el.classList.toggle 'disabled', !ThreadWatcher.list.firstElementChild + true # `Prune dead threads` entry entries.push + text: 'Prune dead threads' cb: ThreadWatcher.cb.pruneDeads - entry: - el: $.el 'a', - textContent: 'Prune dead threads' - refresh: -> (if $('.dead-thread', ThreadWatcher.list) then $.rmClass else $.addClass) @el, 'disabled' + open: -> + @el.classList.toggle 'disabled', !$('.dead-thread', ThreadWatcher.list) + true - # `Settings` entries: - subEntries = [] - for name, conf of Config.threadWatcher - subEntries.push @createSubEntry name, conf[1] + # `Dismiss posts quoting you` entry 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 + text: 'Dismiss posts quoting you' + title: 'Unhighlight the thread watcher icon and threads until there are new replies quoting you.' + cb: ThreadWatcher.cb.dismiss + open: -> + @el.classList.toggle 'disabled', !$.hasClass(ThreadWatcher.shortcut, 'replies-quoting-you') + true + + for {text, title, cb, open} in entries + entry = + el: $.el 'a', + textContent: text + href: 'javascript:;' + entry.el.title = title if title + $.on entry.el, 'click', cb + entry.open = open.bind(entry) @menu.addEntry entry + + # Settings checkbox entries: + for name, conf of Config.threadWatcher + @addCheckbox name, conf[1] + return - createSubEntry: (name, desc) -> + addCheckbox: (name, desc) -> entry = type: 'thread watcher' el: UI.checkbox name, name.replace(' Thread Watcher', '') @@ -479,8 +688,6 @@ ThreadWatcher = $.addClass entry.el, 'disabled' entry.el.title += '\n[Remember Last Read Post is disabled.]' $.on input, 'change', $.cb.checked - $.on input, 'change', ThreadWatcher.refresh if name in ['Current Board', 'Show Unread Count'] - $.on input, 'change', ThreadWatcher.fetchAuto if name in ['Show Unread Count', 'Auto Update Thread Watcher'] - entry - -return ThreadWatcher + $.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 Page', 'Show Unread Count', 'Auto Update Thread Watcher'] + @menu.addEntry entry diff --git a/src/Monitoring/Unread.coffee b/src/Monitoring/Unread.coffee index f2f3f9c573..d71174eaf1 100644 --- a/src/Monitoring/Unread.coffee +++ b/src/Monitoring/Unread.coffee @@ -15,6 +15,7 @@ Unread = @hr = $.el 'hr', id: 'unread-line' + className: 'unread-line' @posts = new Set() @postsQuotingYou = new Set() @order = new RandomAccessList() @@ -28,27 +29,6 @@ Unread = name: 'Unread' cb: @addPost - <% if (readJSON('/.tests_enabled')) { %> - testLink = $.el 'a', - textContent: 'Test Post Order' - $.on testLink, 'click', -> - list1 = (x.ID for x in Unread.order.order()) - list2 = (+x.id[2..] for x in $$ '.postContainer') - pass = do -> - return false unless list1.length is list2.length - for i in [0...list1.length] by 1 - return false if list1[i] isnt list2[i] - true - if pass - new Notice 'success', "Orders same (#{list1.length} posts)", 5 - else - new Notice 'warning', 'Orders differ.', 30 - c.log list1 - c.log list2 - Header.menu.addEntry - el: testLink - <% } %> - node: -> Unread.thread = @ Unread.title = d.title @@ -59,7 +39,16 @@ Unread = Unread.readCount = 0 Unread.readCount++ for ID in @posts.keys when +ID <= Unread.lastReadPost $.one d, '4chanXInitFinished', Unread.ready - $.on d, 'ThreadUpdate', Unread.onUpdate + $.on d, 'PostsInserted', Unread.onUpdate + $.on d, 'ThreadUpdate', (e) -> Unread.update() if e.detail[404] + resetLink = $.el 'a', + href: 'javascript:;' + className: 'unread-reset' + textContent: 'Mark all unread' + $.on resetLink, 'click', Unread.reset + Header.menu.addEntry + el: resetLink + order: 70 ready: -> Unread.scroll() if Conf['Remember Last Read Post'] and Conf['Scroll to Last Read Post'] @@ -76,19 +65,39 @@ Unread = # Let the header's onload callback handle it. return if (hash = location.hash.match /\d+/) and hash[0] of Unread.thread.posts - ReplyPruning.showIfHidden Unread.position?.data.nodes.root.id - position = Unread.positionPrev() while position - {root} = position.data.nodes - if !root.getBoundingClientRect().height + {bottom} = position.data.nodes + if !bottom.getBoundingClientRect().height # Don't try to scroll to posts with display: none position = position.prev else - Header.scrollToIfNeeded root, true + Header.scrollToIfNeeded bottom, true break return + reset: -> + return unless Unread.lastReadPost? + + Unread.posts = new Set() + Unread.postsQuotingYou = new Set() + Unread.order = new RandomAccessList() + Unread.position = null + Unread.lastReadPost = 0 + Unread.readCount = 0 + Unread.thread.posts.forEach (post) -> Unread.addPost.call post + + $.forceSync 'Remember Last Read Post' + if Conf['Remember Last Read Post'] and (!Unread.thread.isDead or Unread.thread.isArchived) + Unread.db.set + boardID: Unread.thread.board.ID + threadID: Unread.thread.ID + val: 0 + + Unread.updatePosition() + Unread.setLine() + Unread.update() + sync: -> return unless Unread.lastReadPost? lastReadPost = Unread.db.get @@ -101,7 +110,7 @@ Unread = postIDs = Unread.thread.posts.keys for i in [Unread.readCount...postIDs.length] by 1 ID = +postIDs[i] - unless Unread.thread.posts[ID].isFetchedQuote + unless Unread.thread.posts.get(ID).isFetchedQuote break if ID > Unread.lastReadPost Unread.posts.delete ID Unread.postsQuotingYou.delete ID @@ -114,39 +123,35 @@ Unread = addPost: -> return if @isFetchedQuote or @isClone Unread.order.push @ - return if @ID <= Unread.lastReadPost or @isHidden or QuoteYou.db?.get { - boardID: @board.ID - threadID: @thread.ID - postID: @ID - } - Unread.posts.add @ID + return if @ID <= Unread.lastReadPost or @isHidden or QuoteYou.isYou(@) + Unread.posts.add (Unread.posts.last = @ID) Unread.addPostQuotingYou @ Unread.position ?= Unread.order[@ID] addPostQuotingYou: (post) -> for quotelink in post.nodes.quotelinks when QuoteYou.db?.get Get.postDataFromLink quotelink - Unread.postsQuotingYou.add post.ID + Unread.postsQuotingYou.add (Unread.postsQuotingYou.last = post.ID) Unread.openNotification post return - openNotification: (post) -> + openNotification: (post, predicate=' replied to you') -> return unless Header.areNotificationsEnabled - notif = new Notification "#{post.info.nameBlock} replied to you", - body: post.info.commentDisplay + notif = new Notification "#{post.info.nameBlock}#{predicate}", + body: post.commentDisplay() icon: Favicon.logo notif.onclick = -> - Header.scrollToIfNeeded post.nodes.root, true + Header.scrollToIfNeeded post.nodes.bottom, true window.focus() notif.onshow = -> setTimeout -> notif.close() , 7 * $.SECOND - onUpdate: (e) -> - if !e.detail[404] + onUpdate: -> + $.queueTask -> # ThreadUpdater may scroll immediately after inserting posts Unread.setLine() Unread.read() - Unread.update() + Unread.update() readSinglePost: (post) -> {ID} = post @@ -167,25 +172,18 @@ Unread = count = 0 while Unread.position {ID, data} = Unread.position - {root} = data.nodes - break unless !root.getBoundingClientRect().height or # post has been hidden - Header.getBottomOf(root) > -1 # post is completely read + {bottom} = data.nodes + break unless !bottom.getBoundingClientRect().height or # post has been hidden + Header.getBottomOf(bottom) > -1 # post is completely read count++ Unread.posts.delete ID Unread.postsQuotingYou.delete ID - - if QuoteYou.db?.get { - boardID: data.board.ID - threadID: data.thread.ID - postID: ID - } - QuoteYou.lastRead = root Unread.position = Unread.position.next return unless count Unread.updatePosition() Unread.saveLastReadPost() - Unread.update() if e + (Unread.update() if e) updatePosition: -> while Unread.position and !Unread.posts.has Unread.position.ID @@ -198,12 +196,11 @@ Unread = postIDs = Unread.thread.posts.keys for i in [Unread.readCount...postIDs.length] by 1 ID = +postIDs[i] - unless Unread.thread.posts[ID].isFetchedQuote + unless Unread.thread.posts.get(ID).isFetchedQuote break if Unread.posts.has ID Unread.lastReadPost = ID Unread.readCount++ return if Unread.thread.isDead and !Unread.thread.isArchived - Unread.db.forceSync() Unread.db.set boardID: Unread.thread.board.ID threadID: Unread.thread.ID @@ -212,8 +209,12 @@ Unread = setLine: (force) -> return unless Conf['Unread Line'] if Unread.hr.hidden or d.hidden or (force is true) + oldPosition = Unread.linePosition if (Unread.linePosition = Unread.positionPrev()) - $.after Unread.linePosition.data.nodes.root, Unread.hr + if Unread.linePosition isnt oldPosition + node = Unread.linePosition.data.nodes.bottom + node = node.nextSibling if node.nextSibling?.tagName is 'BR' + $.after node, Unread.hr else $.rm Unread.hr Unread.hr.hidden = Unread.linePosition is Unread.order.last @@ -231,23 +232,35 @@ Unread = Unread.title d.title = "#{titleQuotingYou}#{titleCount}#{titleDead}" - $.forceSync 'Remember Last Read Post' - if Conf['Remember Last Read Post'] and (!Unread.thread.isDead or Unread.thread.isArchived) - ThreadWatcher.update Unread.thread.board.ID, Unread.thread.ID, - isDead: Unread.thread.isDead - unread: count - quotingYou: countQuotingYou + Unread.saveThreadWatcherCount() - if Conf['Unread Favicon'] + if Conf['Unread Favicon'] and g.SITE.software is 'yotsuba' {isDead} = Unread.thread - Favicon.el.href = + Favicon.set ( if countQuotingYou - Favicon[if isDead then 'unreadDeadY' else 'unreadY'] + (if isDead then 'unreadDeadY' else 'unreadY') else if count - Favicon[if isDead then 'unreadDead' else 'unread'] + (if isDead then 'unreadDead' else 'unread') else - Favicon[if isDead then 'dead' else 'default'] - # `favicon.href = href` doesn't work on Firefox. - $.add d.head, Favicon.el + (if isDead then 'dead' else 'default') + ) -return Unread + saveThreadWatcherCount: $.debounce 2 * $.SECOND, -> + $.forceSync 'Remember Last Read Post' + if Conf['Remember Last Read Post'] and (!Unread.thread.isDead or Unread.thread.isArchived) + quotingYou = if !Conf['Require OP Quote Link'] and QuoteYou.isYou(Unread.thread.OP) then Unread.posts else Unread.postsQuotingYou + if !quotingYou.size + quotingYou.last = 0 + else if !quotingYou.has(quotingYou.last) + quotingYou.last = 0 + posts = Unread.thread.posts.keys + for i in [posts.length - 1 .. 0] by -1 + if quotingYou.has(+posts[i]) + quotingYou.last = posts[i] + break + ThreadWatcher.update g.SITE.ID, Unread.thread.board.ID, Unread.thread.ID, + last: Unread.thread.lastPost + isDead: Unread.thread.isDead + isArchived: Unread.thread.isArchived + unread: Unread.posts.size + quotingYou: (quotingYou.last or 0) diff --git a/src/Monitoring/UnreadIndex.coffee b/src/Monitoring/UnreadIndex.coffee new file mode 100644 index 0000000000..b46b8dad02 --- /dev/null +++ b/src/Monitoring/UnreadIndex.coffee @@ -0,0 +1,107 @@ +UnreadIndex = + lastReadPost: $.dict() + hr: $.dict() + markReadLink: $.dict() + + init: -> + return unless g.VIEW is 'index' and Conf['Remember Last Read Post'] and Conf['Unread Line in Index'] + + @enabled = true + @db = new DataBoard 'lastReadPosts', @sync + + Callbacks.Thread.push + name: 'Unread Line in Index' + cb: @node + + $.on d, 'IndexRefreshInternal', @onIndexRefresh + $.on d, 'PostsInserted PostsRemoved', @onPostsInserted + + node: -> + UnreadIndex.lastReadPost[@fullID] = UnreadIndex.db.get( + boardID: @board.ID + threadID: @ID + ) or 0 + if !Index.enabled # let onIndexRefresh handle JSON Index + UnreadIndex.update @ + + onIndexRefresh: (e) -> + return if e.detail.isCatalog + for threadID in e.detail.threadIDs + thread = g.threads.get(threadID) + UnreadIndex.update thread + + onPostsInserted: (e) -> + return if e.target is Index.root # onIndexRefresh handles this case + thread = Get.threadFromNode e.target + return if !thread or thread.nodes.root isnt e.target + wasVisible = !!UnreadIndex.hr[thread.fullID]?.parentNode + UnreadIndex.update thread + if Conf['Scroll to Last Read Post'] and e.type is 'PostsInserted' and !wasVisible and !!UnreadIndex.hr[thread.fullID]?.parentNode + Header.scrollToIfNeeded UnreadIndex.hr[thread.fullID], true + + sync: -> + g.threads.forEach (thread) -> + lastReadPost = UnreadIndex.db.get( + boardID: thread.board.ID + threadID: thread.ID + ) or 0 + if lastReadPost isnt UnreadIndex.lastReadPost[thread.fullID] + UnreadIndex.lastReadPost[thread.fullID] = lastReadPost + if thread.nodes.root?.parentNode + UnreadIndex.update thread + + update: (thread) -> + lastReadPost = UnreadIndex.lastReadPost[thread.fullID] + repliesShown = 0 + repliesRead = 0 + firstUnread = null + thread.posts.forEach (post) -> + if post.isReply and thread.nodes.root.contains(post.nodes.root) + repliesShown++ + if post.ID <= lastReadPost + repliesRead++ + else if (!firstUnread or post.ID < firstUnread.ID) and !post.isHidden and !QuoteYou.isYou(post) + firstUnread = post + + hr = UnreadIndex.hr[thread.fullID] + if firstUnread and (repliesRead or (lastReadPost is thread.OP.ID and (!$(g.SITE.selectors.summary, thread.nodes.root) or thread.ID of ExpandThread.statuses))) + if !hr + hr = UnreadIndex.hr[thread.fullID] = $.el 'hr', + className: 'unread-line' + $.before firstUnread.nodes.root, hr + else + $.rm hr + + hasUnread = if repliesShown + firstUnread or !repliesRead + else if Index.enabled + thread.lastPost > lastReadPost + else + thread.OP.ID > lastReadPost + thread.nodes.root.classList.toggle 'unread-thread', hasUnread + + link = UnreadIndex.markReadLink[thread.fullID] + if !link + link = UnreadIndex.markReadLink[thread.fullID] = $.el 'a', + className: 'unread-mark-read brackets-wrap' + href: 'javascript:;' + textContent: 'Mark Read' + $.on link, 'click', UnreadIndex.markRead + if (divider = $ g.SITE.selectors.threadDivider, thread.nodes.root) # divider inside thread as in Tinyboard + $.before divider, link + else + $.add thread.nodes.root, link + + markRead: -> + thread = Get.threadFromNode @ + UnreadIndex.lastReadPost[thread.fullID] = thread.lastPost + UnreadIndex.db.set + boardID: thread.board.ID + threadID: thread.ID + val: thread.lastPost + $.rm UnreadIndex.hr[thread.fullID] + thread.nodes.root.classList.remove 'unread-thread' + ThreadWatcher.update g.SITE.ID, thread.board.ID, thread.ID, + last: thread.lastPost + unread: 0 + quotingYou: 0 diff --git a/src/Posting/Captcha.cache.coffee b/src/Posting/Captcha.cache.coffee new file mode 100644 index 0000000000..b8517d0a54 --- /dev/null +++ b/src/Posting/Captcha.cache.coffee @@ -0,0 +1,110 @@ +Captcha.cache = + init: -> + $.on d, 'SaveCaptcha', (e) => + @saveAPI e.detail + $.on d, 'NoCaptcha', (e) => + @noCaptcha e.detail + + captchas: [] + + getCount: -> + @captchas.length + + neededRaw: -> + not ( + @haveCookie() or @captchas.length or QR.req or @submitCB + ) and ( + QR.posts.length > 1 or Conf['Auto-load captcha'] or !QR.posts[0].isOnlyQuotes() or QR.posts[0].file + ) + + needed: -> + @neededRaw() and $.event('LoadCaptcha') + + prerequest: -> + return unless Conf['Prerequest Captcha'] + # Post count temporarily off by 1 when called from QR.post.rm, QR.close, or QR.submit + $.queueTask => + if ( + !@prerequested and + @neededRaw() and + !$.event('LoadCaptcha') and + !QR.captcha.occupied() and + QR.cooldown.seconds <= 60 and + QR.selected is QR.posts[QR.posts.length - 1] and + !QR.selected.isOnlyQuotes() + ) + isReply = (QR.selected.thread isnt 'new') + if !$.event('RequestCaptcha', {isReply}) + @prerequested = true + @submitCB = (captcha) => + @save captcha if captcha + @updateCount() + + haveCookie: -> + /\b_ct=/.test(d.cookie) and QR.posts[0].thread isnt 'new' + + getOne: -> + delete @prerequested + @clear() + if (captcha = @captchas.shift()) + @count() + captcha + else + null + + request: (isReply) -> + if !@submitCB + return if $.event('RequestCaptcha', {isReply}) + (cb) => + @submitCB = cb + @updateCount() + + abort: -> + if @submitCB + delete @submitCB + $.event 'AbortCaptcha' + @updateCount() + + saveAPI: (captcha) -> + if (cb = @submitCB) + delete @submitCB + cb captcha + @updateCount() + else + @save captcha + + noCaptcha: (detail) -> + if (cb = @submitCB) + if !@haveCookie() or detail?.error + QR.error(detail?.error or 'Failed to retrieve captcha.') + QR.captcha.setup(d.activeElement is QR.nodes.status) + delete @submitCB + cb() + @updateCount() + + save: (captcha) -> + if (cb = @submitCB) + @abort() + cb captcha + return + @captchas.push captcha + @captchas.sort (a, b) -> a.timeout - b.timeout + @count() + + clear: -> + if @captchas.length + now = Date.now() + for captcha, i in @captchas + break if captcha.timeout > now + if i + @captchas = @captchas[i..] + @count() + + count: -> + clearTimeout @timer + if @captchas.length + @timer = setTimeout @clear.bind(@), @captchas[0].timeout - Date.now() + @updateCount() + + updateCount: -> + $.event 'CaptchaCount', @captchas.length diff --git a/src/Posting/Captcha.fixes.coffee b/src/Posting/Captcha.fixes.coffee deleted file mode 100644 index 60e52f5ab7..0000000000 --- a/src/Posting/Captcha.fixes.coffee +++ /dev/null @@ -1,156 +0,0 @@ -Captcha.fixes = - imageKeys: '789456123uiojklm'.split('').concat(['Comma', 'Period']) - imageKeys16: '7890uiopjkl'.split('').concat(['Semicolon', 'm', 'Comma', 'Period', 'Slash']) - - css: ''' - .rc-imageselect-target > div:focus, .rc-image-tile-target:focus { - outline: 2px solid #4a90e2; - } - .rc-imageselect-target td:focus { - box-shadow: inset 0 0 0 2px #4a90e2; - outline: none; - } - .rc-button-default:focus { - box-shadow: inset 0 0 0 2px #0063d6; - } - ''' - - cssNoscript: ''' - .fbc-payload-imageselect { - position: relative; - } - .fbc-payload-imageselect > label { - position: absolute; - display: block; - height: 93.3px; - width: 93.3px; - } - label[data-row="0"] {top: 0px;} - label[data-row="1"] {top: 93.3px;} - label[data-row="2"] {top: 186.6px;} - label[data-col="0"] {left: 0px;} - label[data-col="1"] {left: 93.3px;} - label[data-col="2"] {left: 186.6px;} - .fbc-payload-imageselect > input:focus + label { - outline: 2px solid #4a90e2; - } - .fbc-button-verify input:focus { - box-shadow: inset 0 0 0 2px #0063d6; - } - body.focus .fbc { - box-shadow: inset 0 0 0 2px #4a90e2; - } - ''' - - init: -> - switch location.pathname.split('/')[3] - when 'anchor' then @initMain() - when 'frame' then @initPopup() - when 'fallback' then @initNoscript() - - initMain: -> - $.onExists d.body, '#recaptcha-anchor', (checkbox) -> - focus = -> - if d.hasFocus() and d.activeElement in [d.documentElement, d.body] - checkbox.focus() - focus() - $.on window, 'focus', -> - $.queueTask focus - - # Remove Privacy and Terms links from tab order. - for a in $$ '.rc-anchor-pt a' - a.tabIndex = -1 - return - - initPopup: -> - $.addStyle @css - @fixImages() - new MutationObserver(=> @fixImages()).observe d.body, {childList: true, subtree: true} - $.on d, 'keydown', @keybinds.bind(@) - - initNoscript: -> - @noscript = true - data = if (token = $('.fbc-verification-token > textarea')?.value) then {token} else {working: true} - new Connection(window.parent, '*').send data - d.body.classList.toggle 'focus', d.hasFocus() - $.on window, 'focus blur', -> d.body.classList.toggle 'focus', d.hasFocus() - - @images = $$ '.fbc-payload-imageselect > input' - @width = 3 - return unless @images.length is 9 - - $.addStyle @cssNoscript - @addLabels() - $.on d, 'keydown', @keybinds.bind(@) - $.on $('.fbc-imageselect-challenge > form'), 'submit', @checkForm.bind(@) - - fixImages: -> - @images = $$ '.rc-image-tile-target' - @images = $$ '.rc-imageselect-target > div, .rc-imageselect-target td' unless @images.length - @width = $$('.rc-imageselect-target tr:first-of-type td').length or Math.round(Math.sqrt @images.length) - for img in @images - img.tabIndex = 0 - if @images.length is 9 - @addTooltips @images - else - @addTooltips16 @images - - addLabels: -> - imageSelect = $ '.fbc-payload-imageselect' - labels = for checkbox, i in @images - checkbox.id = "checkbox-#{i}" - label = $.el 'label', - htmlFor: checkbox.id - label.dataset.row = i // 3 - label.dataset.col = i % 3 - $.after checkbox, label - label - @addTooltips labels - - addTooltips: (nodes) -> - for node, i in nodes - node.title = "#{@imageKeys[i]} or #{@imageKeys[i+9][0].toUpperCase()}#{@imageKeys[i+9][1..]}" - return - - addTooltips16: (nodes) -> - for key, i in @imageKeys16 - if i % 4 < @width and (node = nodes[nodes.length - (4 - i//4)*@width + (i % 4)]) - node.title = "#{key[0].toUpperCase()}#{key[1..]}" - return - - checkForm: (e) -> - n = 0 - n++ for checkbox in @images when checkbox.checked - e.preventDefault() if n is 0 - - keybinds: (e) -> - return unless @images and doc.contains(@images[0]) - n = @images.length - w = @width - last = n + w - 1 - - reload = $ '#recaptcha-reload-button, .fbc-button-reload' - verify = $ '#recaptcha-verify-button, .fbc-button-verify > input' - x = @images.indexOf d.activeElement - if x < 0 - x = if d.activeElement is verify then last else n - key = Keybinds.keyCode e - - if !@noscript and key is 'Space' and x < n - @images[x].click() - else if n is 9 and (i = @imageKeys.indexOf key) >= 0 - @images[i % 9].click() - verify.focus() - else if n isnt 9 and (i = @imageKeys16.indexOf key) >= 0 and i % 4 < w and (img = @images[n - (4 - i//4)*w + (i % 4)]) - img.click() - verify.focus() - else if dx = {'Up': n, 'Down': w, 'Left': last, 'Right': 1}[key] - x = (x + dx) % (n + w) - if n < x < last - x = if dx is last then n else last - (@images[x] or (if x is n then reload) or (if x is last then verify)).focus() - else - return - - e.preventDefault() - e.stopPropagation() diff --git a/src/Posting/Captcha.replace.coffee b/src/Posting/Captcha.replace.coffee index db691b7b4d..3c27b48816 100644 --- a/src/Posting/Captcha.replace.coffee +++ b/src/Posting/Captcha.replace.coffee @@ -1,55 +1,31 @@ Captcha.replace = init: -> - return unless d.cookie.indexOf('pass_enabled=1') < 0 - - if location.hostname is 'sys.4chan.org' and /[?&]altc\b/.test(location.search) and Main.jsEnabled - $.onExists doc, 'script[src="//www.google.com/recaptcha/api/js/recaptcha_ajax.js"]', -> - $.global -> window.el.onload = null - Captcha.v1.create() - return - - if ( - (Conf['Use Recaptcha v1'] and location.hostname is 'boards.4chan.org') or - (Conf['Use Recaptcha v1 in Reports'] and location.hostname is 'sys.4chan.org') - ) and Main.jsEnabled - $.ready Captcha.replace.v1 - return + return unless g.SITE.software is 'yotsuba' and d.cookie.indexOf('pass_enabled=1') < 0 if Conf['Force Noscript Captcha'] and Main.jsEnabled $.ready Captcha.replace.noscript return - if Conf['captchaLanguage'].trim() or Conf['Captcha Fixes'] - if location.hostname is 'boards.4chan.org' - $.onExists doc, '#captchaFormPart', (node) -> $.onExists node, 'iframe', Captcha.replace.iframe + if Conf['captchaLanguage'].trim() + if location.hostname in ['boards.4chan.org', 'boards.4channel.org'] + $.onExists doc, '#captchaFormPart', (node) -> $.onExists node, 'iframe[src^="https://www.google.com/recaptcha/"]', Captcha.replace.iframe else - $.onExists doc, 'iframe', Captcha.replace.iframe + $.onExists doc, 'iframe[src^="https://www.google.com/recaptcha/"]', Captcha.replace.iframe noscript: -> - return unless (original = $ '#g-recaptcha, #captchaContainerAlt') and (noscript = $ 'noscript') + return if not ((original = $ '#g-recaptcha') and (noscript = $ 'noscript', original.parentNode)) span = $.el 'span', id: 'captcha-forced-noscript' $.replace noscript, span $.rm original insert = -> span.innerHTML = noscript.textContent - Captcha.replace.iframe $('iframe', span) + Captcha.replace.iframe $('iframe[src^="https://www.google.com/recaptcha/"]', span) if (toggle = $ '#togglePostFormLink a, #form-link') $.on toggle, 'click', insert else insert() - v1: -> - return unless $.id 'g-recaptcha' - Captcha.v1.replace() - if (link = $.id 'form-link') - $.on link, 'click', -> Captcha.v1.create() - else if location.hostname is 'boards.4chan.org' - form = $.id 'postForm' - form.addEventListener 'focus', (-> Captcha.v1.create()), true - else - Captcha.v1.create() - iframe: (iframe) -> if (lang = Conf['captchaLanguage'].trim()) src = if /[?&]hl=/.test iframe.src @@ -57,17 +33,4 @@ Captcha.replace = else iframe.src + "&hl=#{encodeURIComponent lang}" iframe.src = src unless iframe.src is src - Captcha.replace.autocopy iframe - - autocopy: (iframe) -> - return unless Conf['Captcha Fixes'] and /^https:\/\/www\.google\.com\/recaptcha\/api\/fallback\?/.test(iframe.src) - new Connection iframe, 'https://www.google.com', - working: -> - if $.id('qr')?.contains iframe - $('#qr .captcha-container textarea')?.parentNode.hidden = true - token: (token) -> - node = iframe - while (node = node.parentNode) - break if (textarea = $ 'textarea', node) - textarea.value = token - $.event 'input', null, textarea + return diff --git a/src/Posting/Captcha.t.coffee b/src/Posting/Captcha.t.coffee new file mode 100644 index 0000000000..823f9ece5e --- /dev/null +++ b/src/Posting/Captcha.t.coffee @@ -0,0 +1,74 @@ +Captcha.t = + init: -> + return if d.cookie.indexOf('pass_enabled=1') >= 0 + return if not (@isEnabled = !!$('#t-root') or !$.id('postForm')) + + root = $.el 'div', className: 'captcha-root' + @nodes = {root} + + $.addClass QR.nodes.el, 'has-captcha', 'captcha-t' + $.after QR.nodes.com.parentNode, root + + moreNeeded: -> + return + + getThread: -> + boardID = g.BOARD.ID + if QR.posts[0].thread is 'new' + threadID = '0' + else + threadID = '' + QR.posts[0].thread + {boardID, threadID} + + setup: (focus) -> + return unless @isEnabled + + if !@nodes.container + @nodes.container = $.el 'div', className: 'captcha-container' + $.prepend @nodes.root, @nodes.container + Captcha.t.currentThread = Captcha.t.getThread() + $.global -> + el = document.querySelector '#qr .captcha-container' + window.TCaptcha.init el, @boardID, +@threadID + window.TCaptcha.setErrorCb (err) -> + window.dispatchEvent new CustomEvent('CreateNotification', {detail: { + type: 'warning', + content: '' + err + }}) + , Captcha.t.currentThread + + if focus + $('#t-resp').focus() + + destroy: -> + return unless @isEnabled and @nodes.container + $.global -> + window.TCaptcha.destroy() + $.rm @nodes.container + delete @nodes.container + + updateThread: -> + return unless @isEnabled + {boardID, threadID} = (Captcha.t.currentThread or {}) + newThread = Captcha.t.getThread() + unless newThread.boardID is boardID and newThread.threadID is threadID + Captcha.t.destroy() + Captcha.t.setup() + + getOne: -> + response = {} + if @nodes.container + for key in ['t-response', 't-challenge'] + response[key] = $("[name='#{key}']", @nodes.container).value + if !response['t-response'] and !((el = $('#t-msg')) and /Verification not required/i.test(el.textContent)) + response = null + response + + setUsed: -> + return unless @isEnabled + if @nodes.container + $.global -> + window.TCaptcha.clearChallenge() + + occupied: -> + !!@nodes.container diff --git a/src/Posting/Captcha.v1.coffee b/src/Posting/Captcha.v1.coffee deleted file mode 100644 index 577ebd0310..0000000000 --- a/src/Posting/Captcha.v1.coffee +++ /dev/null @@ -1,237 +0,0 @@ -Captcha.v1 = - blank: "data:image/svg+xml," - - init: -> - return if d.cookie.indexOf('pass_enabled=1') >= 0 - return unless @isEnabled = !!$ '#g-recaptcha, #captchaContainerAlt' - - imgContainer = $.el 'div', - className: 'captcha-img' - title: 'Reload reCAPTCHA' - $.extend imgContainer, <%= html('') %> - input = $.el 'input', - className: 'captcha-input field' - title: 'Verification' - autocomplete: 'off' - spellcheck: false - @nodes = - img: imgContainer.firstChild - input: input - - $.on input, 'blur', QR.focusout - $.on input, 'focus', QR.focusin - $.on input, 'keydown', QR.captcha.keydown.bind QR.captcha - $.on @nodes.img.parentNode, 'click', QR.captcha.reload.bind QR.captcha - - $.addClass QR.nodes.el, 'has-captcha', 'captcha-v1' - $.after QR.nodes.com.parentNode, [imgContainer, input] - - @captchas = [] - $.get 'captchas', [], ({captchas}) -> - QR.captcha.sync captchas - QR.captcha.clear() - $.sync 'captchas', @sync - - @replace() - @beforeSetup() - @setup() if Conf['Auto-load captcha'] - new MutationObserver(@afterSetup).observe $.id('captchaContainerAlt'), childList: true - @afterSetup() # reCAPTCHA might have loaded before the QR. - - replace: -> - return if @script - unless @script = $ 'script[src="//www.google.com/recaptcha/api/js/recaptcha_ajax.js"]', d.head - @script = $.el 'script', - src: '//www.google.com/recaptcha/api/js/recaptcha_ajax.js' - $.add d.head, @script - if old = $.id 'g-recaptcha' - container = $.el 'div', - id: 'captchaContainerAlt' - $.replace old, container - - create: -> - cont = $.id 'captchaContainerAlt' - return if @occupied - - @occupied = true - - if (lang = Conf['captchaLanguage'].trim()) - cont.dataset.lang = lang - - $.onExists cont, '#recaptcha_image', (image) -> - $.on image, 'click', -> - if $.id 'recaptcha_challenge_image' - $.global -> window.Recaptcha.reload() - $.onExists cont, '#recaptcha_response_field', (field) -> - $.on field, 'keydown', (e) -> - if e.keyCode is 8 and not field.value - $.global -> window.Recaptcha.reload() - field.focus() if location.hostname is 'sys.4chan.org' - - $.global -> - container = document.getElementById 'captchaContainerAlt' - options = - theme: 'clean' - tabindex: {"boards.4chan.org": 5}[location.hostname] - lang: container.dataset.lang - if window.Recaptcha - window.Recaptcha.create '<%= meta.recaptchaKey %>', container, options - else - script = document.head.querySelector 'script[src="//www.google.com/recaptcha/api/js/recaptcha_ajax.js"]' - script.addEventListener 'load', -> - window.Recaptcha.create '<%= meta.recaptchaKey %>', container, options - , false - - cb: - focus: -> QR.captcha.setup false, true - - beforeSetup: -> - {img, input} = @nodes - img.parentNode.hidden = true - img.src = @blank - input.value = '' - input.placeholder = 'Focus to load reCAPTCHA' - @count() - $.on input, 'focus click', @cb.focus - - needed: -> - captchaCount = @captchas.length - captchaCount++ if QR.req - postsCount = QR.posts.length - postsCount = 0 if postsCount is 1 and !Conf['Auto-load captcha'] and !QR.posts[0].com and !QR.posts[0].file - captchaCount < postsCount - - onNewPost: -> - - onPostChange: -> - - setup: (focus, force) -> - return unless @isEnabled and (force or @needed()) - @create() - if focus - $.addClass QR.nodes.el, 'focus' - @nodes.input.focus() - - afterSetup: -> - return unless challenge = $.id 'recaptcha_challenge_field_holder' - return if challenge is QR.captcha.nodes.challenge - - setLifetime = (e) -> QR.captcha.lifetime = e.detail - $.on window, 'captcha:timeout', setLifetime - $.global -> window.dispatchEvent new CustomEvent 'captcha:timeout', {detail: window.RecaptchaState.timeout} - $.off window, 'captcha:timeout', setLifetime - - {img, input} = QR.captcha.nodes - img.parentNode.hidden = false - input.placeholder = 'Verification' - QR.captcha.count() - $.off input, 'focus click', QR.captcha.cb.focus - - QR.captcha.nodes.challenge = challenge - new MutationObserver(QR.captcha.load.bind QR.captcha).observe challenge, - childList: true - subtree: true - attributes: true - QR.captcha.load() - - if QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight - QR.nodes.el.style.top = null - QR.nodes.el.style.bottom = '0px' - - destroy: -> - return unless @script - $.global -> window.Recaptcha.destroy() - delete @occupied - @beforeSetup() if @nodes - - sync: (captchas=[]) -> - QR.captcha.captchas = captchas - QR.captcha.count() - - getOne: -> - @clear() - if captcha = @captchas.shift() - @count() - $.set 'captchas', @captchas - captcha - else - challenge = @nodes.img.alt - timeout = @timeout - if /\S/.test(response = @nodes.input.value) - @destroy() - {challenge, response, timeout} - else - null - - save: -> - return unless /\S/.test(response = @nodes.input.value) - @nodes.input.value = '' - @captchas.push - challenge: @nodes.img.alt - response: response - timeout: @timeout - @captchas.sort (a, b) -> a.timeout - b.timeout - @count() - @destroy() - @setup false, true - $.set 'captchas', @captchas - - clear: -> - return unless @captchas.length - $.forceSync 'captchas' - now = Date.now() - for captcha, i in @captchas - break if captcha.timeout > now - return unless i - @captchas = @captchas[i..] - @count() - $.set 'captchas', @captchas - - load: -> - if $('#captchaContainerAlt[class~="recaptcha_is_showing_audio"]') - @nodes.img.src = @blank - return - return unless @nodes.challenge.firstChild - return unless challenge_image = $.id 'recaptcha_challenge_image' - # -1 minute to give upload some time. - @timeout = Date.now() + @lifetime * $.SECOND - $.MINUTE - challenge = @nodes.challenge.firstChild.value - @nodes.img.alt = challenge - @nodes.img.src = challenge_image.src - @nodes.input.value = '' - @clear() - - count: -> - count = if @captchas then @captchas.length else 0 - placeholder = @nodes.input.placeholder.replace /\ \(.*\)$/, '' - placeholder += switch count - when 0 - if placeholder is 'Verification' then ' (Shift + Enter to cache)' else '' - when 1 - ' (1 cached captcha)' - else - " (#{count} cached captchas)" - @nodes.input.placeholder = placeholder - @nodes.input.alt = count # For XTRM RICE. - clearTimeout @timer - if count - @timer = setTimeout @clear.bind(@), @captchas[0].timeout - Date.now() - - reload: (focus) -> - # Recaptcha.should_focus = false: Hack to prevent the input from being focused - $.global -> - if window.Recaptcha.type is 'image' - window.Recaptcha.reload() - else - window.Recaptcha.switch_type 'image' - window.Recaptcha.should_focus = false - @nodes.input.focus() if focus - - keydown: (e) -> - if e.keyCode is 8 and not @nodes.input.value - @reload() - else if e.keyCode is 13 and e.shiftKey - @save() - else - return - e.preventDefault() diff --git a/src/Posting/Captcha.v2.coffee b/src/Posting/Captcha.v2.coffee index aea247525d..de27a3d532 100644 --- a/src/Posting/Captcha.v2.coffee +++ b/src/Posting/Captcha.v2.coffee @@ -3,20 +3,18 @@ Captcha.v2 = init: -> return if d.cookie.indexOf('pass_enabled=1') >= 0 - return unless (@isEnabled = !!$ '#g-recaptcha, #captchaContainerAlt, #captcha-forced-noscript') + return if not (@isEnabled = !!$('#g-recaptcha, #captcha-forced-noscript') or !$.id('postForm')) if (@noscript = Conf['Force Noscript Captcha'] or not Main.jsEnabled) $.addClass QR.nodes.el, 'noscript-captcha' - @captchas = [] - $.get 'captchas', [], ({captchas}) -> - QR.captcha.sync captchas - $.sync 'captchas', @sync.bind @ + Captcha.cache.init() + $.on d, 'CaptchaCount', @count.bind(@) root = $.el 'div', className: 'captcha-root' - $.extend root, <%= html( + $.extend root, `<%= html( '
              ' - ) %> + ) %>` counter = $ '.captcha-counter > a', root @nodes = {root, counter} @count() @@ -34,7 +32,7 @@ Captcha.v2 = $.queueTask => @save false timeouts: {} - postsCount: 0 + prevNeeded: 0 noscriptURL: -> url = 'https://www.google.com/recaptcha/api/fallback?k=<%= meta.recaptchaKey %>' @@ -42,19 +40,13 @@ Captcha.v2 = url += "&hl=#{encodeURIComponent lang}" url - needed: -> - captchaCount = @captchas.length - captchaCount++ if QR.req - @postsCount = QR.posts.length - @postsCount = 0 if @postsCount is 1 and !Conf['Auto-load captcha'] and !QR.posts[0].com and !QR.posts[0].file - captchaCount < @postsCount - - onNewPost: -> - @setup() - - onPostChange: -> - @setup() if @postsCount is 0 - @postsCount = 0 if QR.posts.length is 1 and !Conf['Auto-load captcha'] and !QR.posts[0].com and !QR.posts[0].file + moreNeeded: -> + # Post count temporarily off by 1 when called from QR.post.rm, QR.close, or QR.submit + $.queueTask => + needed = Captcha.cache.needed() + if needed and not @prevNeeded + @setup(QR.cooldown.auto and d.activeElement is QR.nodes.status) + @prevNeeded = needed toggle: -> if @nodes.container and !@timeouts.destroy @@ -63,7 +55,7 @@ Captcha.v2 = @setup true, true setup: (focus, force) -> - return unless @isEnabled and (@needed() or force) + return unless @isEnabled and (Captcha.cache.needed() or force) if focus $.addClass QR.nodes.el, 'focus' @@ -77,7 +69,7 @@ Captcha.v2 = if @nodes.container # XXX https://bugzilla.mozilla.org/show_bug.cgi?id=1226835 $.queueTask => - if @nodes.container and d.activeElement is @nodes.counter and (iframe = $ 'iframe', @nodes.container) + if @nodes.container and d.activeElement is @nodes.counter and (iframe = $ 'iframe[src^="https://www.google.com/recaptcha/"]', @nodes.container) iframe.focus() QR.focus() # Event handler not fired in Firefox return @@ -96,6 +88,7 @@ Captcha.v2 = setupNoscript: -> iframe = $.el 'iframe', id: 'qr-captcha-iframe' + scrolling: 'no' src: @noscriptURL() div = $.el 'div' textarea = $.el 'textarea' @@ -109,7 +102,7 @@ Captcha.v2 = container = document.querySelector '#qr .captcha-container' container.dataset.widgetID = window.grecaptcha.render container, sitekey: '<%= meta.recaptchaKey %>' - theme: if classList.contains('tomorrow') or classList.contains('dark-captcha') then 'dark' else 'light' + theme: if classList.contains('tomorrow') or classList.contains('spooky') or classList.contains('dark-captcha') then 'dark' else 'light' callback: (response) -> window.dispatchEvent new CustomEvent('captcha:success', {detail: response}) if window.grecaptcha @@ -119,11 +112,15 @@ Captcha.v2 = window.onRecaptchaLoaded = -> render() cbNative() + unless document.head.querySelector 'script[src^="https://www.google.com/recaptcha/api.js"]' + script = document.createElement 'script' + script.src = 'https://www.google.com/recaptcha/api.js?onload=onRecaptchaLoaded&render=explicit' + document.head.appendChild script afterSetup: (mutations) -> for mutation in mutations for node in mutation.addedNodes - @setupIFrame iframe if (iframe = $.x './descendant-or-self::iframe', node) + @setupIFrame iframe if (iframe = $.x './descendant-or-self::iframe[starts-with(@src, "https://www.google.com/recaptcha/")]', node) @setupTextArea textarea if (textarea = $.x './descendant-or-self::textarea', node) return @@ -134,14 +131,13 @@ Captcha.v2 = @fixQRPosition() $.on iframe, 'load', @fixQRPosition iframe.focus() if d.activeElement is @nodes.counter - # XXX Stop Recaptcha from changing focus from iframe -> body -> iframe on submit. - $.global -> - f = document.querySelector('#qr iframe') - f.focus = f.blur = -> + # XXX Make sure scroll on space prevention (see src/css/style.css) doesn't cause scrolling of div + if $.engine in ['blink', 'edge'] and iframe.parentNode in $$('#qr .captcha-container > div > div:first-of-type') + $.on iframe.parentNode, 'scroll', -> @scrollTop = 0 fixQRPosition: -> if QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight - QR.nodes.el.style.top = null + QR.nodes.el.style.top = '' QR.nodes.el.style.bottom = '0px' setupTextArea: (textarea) -> @@ -151,41 +147,23 @@ Captcha.v2 = return unless @isEnabled delete @timeouts.destroy $.rmClass QR.nodes.el, 'captcha-open' - $.rm @nodes.container if @nodes.container - delete @nodes.container - # Clean up abandoned iframes. - garbage = $.X '//iframe[starts-with(@src, "https://www.google.com/recaptcha/api2/frame")]/ancestor-or-self::*[parent::body]' - i = 0 - while node = garbage.snapshotItem i++ - $.rm ins if (ins = node.nextSibling)?.nodeName is 'INS' - $.rm node - return - - sync: (captchas=[]) -> - @captchas = captchas - @clear() - @count() + if @nodes.container + $.global -> + container = document.querySelector '#qr .captcha-container' + window.grecaptcha.reset container.dataset.widgetID + $.rm @nodes.container + delete @nodes.container - getOne: -> - @clear() - if (captcha = @captchas.shift()) - $.set 'captchas', @captchas - @count() - captcha - else - null + getOne: (isReply) -> + Captcha.cache.getOne isReply save: (pasted, token) -> - $.forceSync 'captchas' - @captchas.push + Captcha.cache.save response: token or $('textarea', @nodes.container).value timeout: Date.now() + @lifetime - @captchas.sort (a, b) -> a.timeout - b.timeout - $.set 'captchas', @captchas - @count() focus = d.activeElement?.nodeName is 'IFRAME' and /https?:\/\/www\.google\.com\/recaptcha\//.test(d.activeElement.src) - if @needed() + if Captcha.cache.needed() if focus if QR.cooldown.auto or Conf['Post on Captcha Completion'] @nodes.counter.focus() @@ -201,23 +179,11 @@ Captcha.v2 = QR.submit() if Conf['Post on Captcha Completion'] and !QR.cooldown.auto - clear: -> - return unless @captchas.length - $.forceSync 'captchas' - now = Date.now() - for captcha, i in @captchas - break if captcha.timeout > now - return unless i - @captchas = @captchas[i..] - @count() - $.set 'captchas', @captchas - @setup(d.activeElement is QR.nodes.status) - count: -> - @nodes.counter.textContent = "Captchas: #{@captchas.length}" - clearTimeout @timeouts.clear - if @captchas.length - @timeouts.clear = setTimeout @clear.bind(@), @captchas[0].timeout - Date.now() + count = Captcha.cache.getCount() + loading = if Captcha.cache.submitCB then '...' else '' + @nodes.counter.textContent = "Captchas: #{count}#{loading}" + @moreNeeded() reload: -> if $ 'iframe[src^="https://www.google.com/recaptcha/api/fallback?"]', @nodes.container @@ -227,3 +193,6 @@ Captcha.v2 = $.global -> container = document.querySelector '#qr .captcha-container' window.grecaptcha.reset container.dataset.widgetID + + occupied: -> + !!@nodes.container and !@timeouts.destroy diff --git a/src/Posting/PassLink.coffee b/src/Posting/PassLink.coffee index 7f5bbe8bcb..3111a6d56c 100644 --- a/src/Posting/PassLink.coffee +++ b/src/Posting/PassLink.coffee @@ -1,18 +1,16 @@ PassLink = init: -> - return unless Conf['Pass Link'] + return unless g.SITE.software is 'yotsuba' and Conf['Pass Link'] Main.ready @ready ready: -> - return unless (styleSelector = $.id 'styleSelector') + return if not (styleSelector = $.id 'styleSelector') passLink = $.el 'span', className: 'brackets-wrap pass-link-container' - $.extend passLink, <%= html('4chan Pass') %> + $.extend passLink, `<%= html('4chan Pass') %>` $.on passLink.firstElementChild, 'click', -> - window.open '//sys.4chan.org/auth', + window.open "//sys.#{location.hostname.split('.')[1]}.org/auth", Date.now() 'width=500,height=280,toolbar=0' $.before styleSelector.previousSibling, [passLink, $.tn('\u00A0\u00A0')] - -return PassLink diff --git a/src/Posting/PostRedirect.coffee b/src/Posting/PostRedirect.coffee new file mode 100644 index 0000000000..9d4c7fc991 --- /dev/null +++ b/src/Posting/PostRedirect.coffee @@ -0,0 +1,21 @@ +PostRedirect = + init: -> + $.on d, 'QRPostSuccessful', (e) => + return unless e.detail.redirect + @event = e + @delays = 0 + $.queueTask => + if e is @event and @delays is 0 + location.href = e.detail.redirect + + delays: 0 + + delay: -> + return null unless @event + e = @event + @delays++ + () => + return unless e is @event + @delays-- + if @delays is 0 + location.href = e.detail.redirect diff --git a/src/Posting/PostSuccessful.coffee b/src/Posting/PostSuccessful.coffee index 982341c832..ca791b7e5c 100644 --- a/src/Posting/PostSuccessful.coffee +++ b/src/Posting/PostSuccessful.coffee @@ -16,5 +16,3 @@ PostSuccessful = threadID: threadID postID: postID val: true - -return PostSuccessful diff --git a/src/Posting/QR.coffee b/src/Posting/QR.coffee index d14905b59f..dc24acc0ae 100644 --- a/src/Posting/QR.coffee +++ b/src/Posting/QR.coffee @@ -1,7 +1,7 @@ QR = - mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/vnd.adobe.flash.movie', 'application/x-shockwave-flash', 'video/webm'] + mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/vnd.adobe.flash.movie', 'application/x-shockwave-flash', 'video/webm', 'video/mp4'] - validExtension: /\.(jpe?g|png|gif|pdf|swf|webm)$/i + validExtension: /\.(jpe?g|png|gif|pdf|swf|webm|mp4)$/i typeFromExtension: 'jpg': 'image/jpeg' @@ -10,7 +10,8 @@ QR = 'gif': 'image/gif' 'pdf': 'application/pdf' 'swf': 'application/vnd.adobe.flash.movie' - 'webm': 'video/webm' + 'webm': 'video/webm', + 'mp4': 'video/mp4' extensionFromType: 'image/jpeg': 'jpg' @@ -19,19 +20,15 @@ QR = 'application/pdf': 'pdf' 'application/vnd.adobe.flash.movie': 'swf' 'application/x-shockwave-flash': 'swf' - 'video/webm': 'webm' + 'video/webm': 'webm', + 'video/mp4': 'mp4' init: -> return unless Conf['Quick Reply'] @posts = [] - return if g.VIEW is 'archive' - - version = if Conf['Use Recaptcha v1'] and Main.jsEnabled then 'v1' else 'v2' - @captcha = Captcha[version] - - $.on d, '4chanXInitFinished', @initReady + $.on d, '4chanXInitFinished', -> BoardConfig.ready QR.initReady Callbacks.Post.push name: 'Quick Reply' @@ -53,23 +50,44 @@ QR = Header.addShortcut 'qr', sc, 540 initReady: -> - $.off d, '4chanXInitFinished', @initReady - QR.postingIsEnabled = !!$.id 'postForm' - return unless QR.postingIsEnabled + captchaVersion = if $('#g-recaptcha, #captcha-forced-noscript') then 'v2' else 't' + QR.captcha = Captcha[captchaVersion] + QR.postingIsEnabled = true - link = $.el 'h1', - className: "qr-link-container" - $.extend link, <%= html('?{g.VIEW === "thread"}{Reply to Thread}{Start a Thread}') %> + {config} = g.BOARD + prop = (key, def) -> +(config[key] ? def) - QR.link = link.firstElementChild - $.on link.firstChild, 'click', -> - QR.open() - QR.nodes.com.focus() + QR.min_width = prop 'min_image_width', 1 + QR.min_height = prop 'min_image_height', 1 + QR.max_width = QR.max_height = 10000 + + QR.max_size = prop 'max_filesize', 4194304 + QR.max_size_video = prop 'max_webm_filesize', QR.max_size + QR.max_comment = prop 'max_comment_chars', 2000 + + QR.max_width_video = QR.max_height_video = 2048 + QR.max_duration_video = prop 'max_webm_duration', 120 + + QR.forcedAnon = !!config.forced_anon + QR.spoiler = !!config.spoilers + + if (origToggle = $.id 'togglePostFormLink') + link = $.el 'h1', + className: "qr-link-container" + $.extend link, `<%= html('?{g.VIEW === "thread"}{Reply to Thread}{Start a Thread}') %>` + + QR.link = link.firstElementChild + $.on link.firstChild, 'click', -> + QR.open() + QR.nodes.com.focus() + + $.before origToggle, link + origToggle.firstElementChild.textContent = 'Original Form' if g.VIEW is 'thread' linkBot = $.el 'div', className: "brackets-wrap qr-link-container-bottom" - $.extend linkBot, <%= html('Reply to Thread') %> + $.extend linkBot, `<%= html('Reply to Thread') %>` $.on linkBot.firstElementChild, 'click', -> QR.open() @@ -77,11 +95,8 @@ QR = $.prepend navLinksBot, linkBot if (navLinksBot = $ '.navLinksBot') - origToggle = $.id 'togglePostFormLink' - $.before origToggle, link - origToggle.firstElementChild.textContent = 'Original Form' - $.on d, 'QRGetFile', QR.getFile + $.on d, 'QRDrawFile', QR.drawFile $.on d, 'QRSetFile', QR.setFile $.on d, 'paste', QR.paste @@ -89,7 +104,7 @@ QR = $.on d, 'drop', QR.dropFile $.on d, 'dragstart dragend', QR.drag - $.on d, 'IndexRefresh', QR.generatePostableThreadsList + $.on d, 'IndexRefreshInternal', QR.generatePostableThreadsList $.on d, 'ThreadUpdate', QR.statusCheck return if !Conf['Persistent QR'] @@ -99,7 +114,7 @@ QR = statusCheck: -> return unless QR.nodes {thread} = QR.posts[0] - if thread isnt 'new' and g.threads["#{g.BOARD}.#{thread}"].isDead + if thread isnt 'new' and g.threads.get("#{g.BOARD}.#{thread}").isDead QR.abort() else QR.status() @@ -130,7 +145,7 @@ QR = return QR.nodes.el.hidden = true QR.cleanNotifications() - d.activeElement.blur() + QR.blur() $.rmClass QR.nodes.el, 'dump' $.addClass QR.shortcut, 'disabled' new QR.post true @@ -152,7 +167,7 @@ QR = getComputedStyle(el).visibility isnt 'hidden' and el.getBoundingClientRect().bottom > 0 hide: -> - d.activeElement.blur() + QR.blur() $.addClass QR.nodes.el, 'autohide' QR.nodes.autohide.checked = true @@ -166,6 +181,9 @@ QR = else QR.unhide() + blur: -> + d.activeElement.blur() if QR.nodes.el.contains(d.activeElement) + toggleSJIS: (e) -> e.preventDefault() Conf['sjisPreview'] = !Conf['sjisPreview'] @@ -181,13 +199,21 @@ QR = texPreviewHide: -> $.rmClass QR.nodes.el, 'tex-preview' + addPost: -> + wasOpen = (QR.nodes and !QR.nodes.el.hidden) + QR.open() + if wasOpen + $.addClass QR.nodes.el, 'dump' + new QR.post true + QR.nodes.com.focus() + setCustomCooldown: (enabled) -> Conf['customCooldownEnabled'] = enabled QR.cooldown.customCooldown = enabled QR.nodes.customCooldown.classList.toggle 'disabled', !enabled toggleCustomCooldown: -> - enabled = $.hasClass @, 'disabled' + enabled = $.hasClass QR.nodes.customCooldown, 'disabled' QR.setCustomCooldown enabled $.set 'customCooldownEnabled', enabled @@ -217,6 +243,13 @@ QR = notif.close() , 7 * $.SECOND + connectionError: -> + $.el 'span', + `<%= html( + 'Connection error while posting. ' + + '[More info]' + ) %>` + notifications: [] cleanNotifications: -> @@ -227,7 +260,7 @@ QR = status: -> return unless QR.nodes {thread} = QR.posts[0] - if thread isnt 'new' and g.threads["#{g.BOARD}.#{thread}"].isDead + if thread isnt 'new' and g.threads.get("#{g.BOARD}.#{thread}").isDead value = 'Dead' disabled = true QR.cooldown.auto = false @@ -259,37 +292,48 @@ QR = return unless QR.postingIsEnabled sel = d.getSelection() post = Get.postFromNode @ + {root} = post.nodes + postRange = new Range() + postRange.selectNode root text = if post.board.ID is g.BOARD.ID then ">>#{post}\n" else ">>>/#{post.board}/#{post}\n" - if sel.toString().trim() and post is Get.postFromNode sel.anchorNode - range = sel.getRangeAt 0 - frag = range.cloneContents() - ancestor = range.commonAncestorContainer - # Quoting the insides of a spoiler/code tag. - if $.x 'ancestor-or-self::*[self::s or contains(@class,"removed-spoiler")]', ancestor - $.prepend frag, $.tn '[spoiler]' - $.add frag, $.tn '[/spoiler]' - if insideCode = $.x 'ancestor-or-self::pre[contains(@class,"prettyprint")]', ancestor - $.prepend frag, $.tn '[code]' - $.add frag, $.tn '[/code]' - for node in $$ (if insideCode then 'br' else '.prettyprint br'), frag - $.replace node, $.tn '\n' - for node in $$ 'br', frag - $.replace node, $.tn '\n>' unless node is frag.lastChild - for node in $$ 's, .removed-spoiler', frag - $.replace node, [$.tn('[spoiler]'), node.childNodes..., $.tn '[/spoiler]'] - for node in $$ '.prettyprint', frag - $.replace node, [$.tn('[code]'), node.childNodes..., $.tn '[/code]'] - for node in $$ '.linkify[data-original]', frag - $.replace node, $.tn node.dataset.original - for node in $$ '.embedder', frag - $.rm node.previousSibling if node.previousSibling?.nodeValue is ' ' - $.rm node - text += ">#{frag.textContent.trim()}\n" + for i in [0...sel.rangeCount] + try + range = sel.getRangeAt i + # Trim range to be fully inside post + if range.compareBoundaryPoints(Range.START_TO_START, postRange) < 0 + range.setStartBefore root + if range.compareBoundaryPoints(Range.END_TO_END, postRange) > 0 + range.setEndAfter root + + continue unless range.toString().trim() + + frag = range.cloneContents() + ancestor = range.commonAncestorContainer + # Quoting the insides of a spoiler/code tag. + if $.x 'ancestor-or-self::*[self::s or contains(@class,"removed-spoiler")]', ancestor + $.prepend frag, $.tn '[spoiler]' + $.add frag, $.tn '[/spoiler]' + if insideCode = $.x 'ancestor-or-self::pre[contains(@class,"prettyprint")]', ancestor + $.prepend frag, $.tn '[code]' + $.add frag, $.tn '[/code]' + for node in $$ (if insideCode then 'br' else '.prettyprint br'), frag + $.replace node, $.tn '\n' + for node in $$ 'br', frag + $.replace node, $.tn '\n>' unless node is frag.lastChild + g.SITE.insertTags?(frag) + for node in $$ '.linkify[data-original]', frag + $.replace node, $.tn node.dataset.original + for node in $$ '.embedder', frag + $.rm node.previousSibling if node.previousSibling?.nodeValue is ' ' + $.rm node + text += ">#{frag.textContent.trim()}\n" QR.openPost() {com, thread} = QR.nodes thread.value = Get.threadFromNode @ unless com.value + wasOnlyQuotes = QR.selected.isOnlyQuotes() + caretPos = com.selectionStart # Replace selection for text. com.value = com.value[...caretPos] + text + com.value[com.selectionEnd..] @@ -298,6 +342,9 @@ QR = com.setSelectionRange range, range com.focus() + # This allows us to determine if any text other than quotes has been typed. + QR.selected.quotedText = com.value if wasOnlyQuotes + QR.selected.save com QR.selected.save thread @@ -308,9 +355,50 @@ QR = counter.hidden = count < QR.max_comment/2 (if count > QR.max_comment then $.addClass else $.rmClass) counter, 'warning' + splitPost = QR.nodes.splitPost + splitPost.hidden = count < QR.max_comment + + splitPost: -> + count = QR.nodes.com.value.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '_').length + text = QR.nodes.com.value + return if count < QR.max_comment or QR.selected.isLocked + lastPostLength = 0 + QR.selected.setComment(""); + + for line in text.split("\n") + currentLength = line.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '_').length + 1 # 1 for newline + if (currentLength + lastPostLength) > QR.max_comment + post = new QR.post true + post.setComment(line) + lastPostLength = currentLength + else + currentPost = QR.selected + newComment = [currentPost.com, line].filter((el) -> el != null).join("\n") + currentPost.setComment(newComment) + lastPostLength += currentLength + + QR.nodes.el.classList.add 'dump' + getFile: -> $.event 'QRFile', QR.selected?.file + drawFile: (e) -> + file = QR.selected?.file + return unless file and /^(image|video)\//.test(file.type) + isVideo = /^video\//.test file + el = $.el (if isVideo then 'video' else 'img') + $.on el, 'error', -> QR.openError() + $.on el, (if isVideo then 'loadeddata' else 'load'), -> + e.target.getContext('2d').drawImage el, 0, 0 + URL.revokeObjectURL el.src + $.event 'QRImageDrawn', null, e.target + el.src = URL.createObjectURL file + + openError: -> + div = $.el 'div' + $.extend div, `<%= html('Could not open file. [More info]') %>` + QR.error div + setFile: (e) -> {file, name, source} = e.detail file.name = name if name? @@ -337,16 +425,21 @@ QR = paste: (e) -> return unless e.clipboardData.items - files = [] - for item in e.clipboardData.items when item.kind is 'file' - blob = item.getAsFile() - blob.name = 'file' - blob.name += '.' + blob.type.split('/')[1] if blob.type - files.push blob - return unless files.length - QR.open() - QR.handleFiles files - $.addClass QR.nodes.el, 'dump' + file = null + score = -1 + for item in e.clipboardData.items when item.kind is 'file' and (file2 = item.getAsFile()) + score2 = 2*(file2.size <= QR.max_size) + (file2.type is 'image/png') + if score2 > score + file = file2 + score = score2 + if file + {type} = file + blob = new Blob [file], {type} + blob.name = "#{Conf['pastedname']}.#{$.getOwn(QR.extensionFromType, type) or 'jpg'}" + QR.open() + QR.handleFiles [blob] + $.addClass QR.nodes.el, 'dump' + return pasteFF: -> {pasteArea} = QR.nodes @@ -361,21 +454,24 @@ QR = for i in [0...bstr.length] arr[i] = bstr.charCodeAt(i) blob = new Blob [arr], {type: m[1]} - blob.name = "file.#{m[2]}" + blob.name = "#{Conf['pastedname']}.#{m[2]}" QR.handleFiles [blob] else if /^https?:\/\//.test src QR.handleUrl src return handleUrl: (urlDefault) -> - url = prompt 'Enter a URL:', urlDefault - return if url is null - QR.nodes.fileButton.focus() - CrossOrigin.file url, (blob) -> - if blob and not /^text\//.test blob.type - QR.handleFiles [blob] - else - QR.error "Can't load file." + QR.open() + QR.selected.preventAutoPost() + CrossOrigin.permission -> + url = prompt 'Enter a URL:', urlDefault + return if url is null + QR.nodes.fileButton.focus() + CrossOrigin.file url, (blob) -> + if blob and not /^text\//.test blob.type + QR.handleFiles [blob] + else + QR.error "Can't load file." handleFiles: (files) -> if @ isnt QR # file input @@ -426,8 +522,8 @@ QR = dialog: -> QR.nodes = nodes = - el: dialog = UI.dialog 'qr', 'top: 50px; right: 0px;', - <%= readHTML('QuickReply.html') %> + el: dialog = UI.dialog 'qr', + `<%= readHTML('QuickReply.html') %>` setNode = (name, query) -> nodes[name] = $ query, dialog @@ -444,6 +540,7 @@ QR = setNode 'sub', '[data-name=sub]' setNode 'com', '[data-name=com]' setNode 'charCount', '#char-count' + setNode 'splitPost', '#split-post' setNode 'texPreview', '#tex-preview' setNode 'dumpList', '#dump-list' setNode 'addPost', '#add-post' @@ -464,35 +561,14 @@ QR = setNode 'flashTag', '[name=filetag]' setNode 'fileInput', '[type=file]' - rules = $('ul.rules').textContent.trim() - match_min = rules.match(/.+smaller than (\d+)x(\d+).+/) - match_max = rules.match(/.+greater than (\d+)x(\d+).+/) - QR.min_width = +match_min?[1] or 1 - QR.min_height = +match_min?[2] or 1 - QR.max_width = +match_max?[1] or 10000 - QR.max_height = +match_max?[2] or 10000 - - scriptData = Get.scriptData() - QR.max_size = if (m = scriptData.match /\bmaxFilesize *= *(\d+)\b/) then +m[1] else 4194304 - QR.max_size_video = if (m = scriptData.match /\bmaxWebmFilesize *= *(\d+)\b/) then +m[1] else QR.max_size - QR.max_comment = if (m = scriptData.match /\bcomlen *= *(\d+)\b/) then +m[1] else 2000 - - QR.max_width_video = QR.max_height_video = 2048 - QR.max_duration_video = if g.BOARD.ID in ['gif', 'wsg'] then 300 else 120 - - if Conf['Show New Thread Option in Threads'] - $.addClass QR.nodes.el, 'show-new-thread-option' - - QR.forcedAnon = !!$ 'form[name="post"] input[name="name"][type="hidden"]' - if QR.forcedAnon - $.addClass QR.nodes.el, 'forced-anon' - - QR.spoiler = !!$ '.postForm input[name=spoiler]' - if QR.spoiler - $.addClass QR.nodes.el, 'has-spoiler' - - if g.BOARD.ID is 'jp' and Conf['sjisPreview'] - $.addClass QR.nodes.el, 'sjis-preview' + {config} = g.BOARD + {classList} = QR.nodes.el + classList.toggle 'forced-anon', QR.forcedAnon + classList.toggle 'has-spoiler', QR.spoiler + classList.toggle 'has-sjis', !!config.sjis_tags + classList.toggle 'has-math', !!config.math_tags + classList.toggle 'sjis-preview', !!config.sjis_tags and Conf['sjisPreview'] + classList.toggle 'show-new-thread-option', Conf['Show New Thread Option in Threads'] if parseInt(Conf['customCooldown'], 10) > 0 $.addClass QR.nodes.fileSubmit, 'custom-cooldown' @@ -500,12 +576,16 @@ QR = QR.setCustomCooldown customCooldownEnabled $.sync 'customCooldownEnabled', QR.setCustomCooldown + QR.flagsInput() + $.on nodes.autohide, 'change', QR.toggleHide $.on nodes.close, 'click', QR.close + $.on nodes.status, 'click', QR.submit $.on nodes.form, 'submit', QR.submit $.on nodes.sjisToggle, 'click', QR.toggleSJIS $.on nodes.texButton, 'mousedown', QR.texPreviewShow $.on nodes.texButton, 'mouseup', QR.texPreviewHide + $.on nodes.splitPost, 'click', QR.splitPost $.on nodes.addPost, 'click', -> new QR.post true $.on nodes.drawButton, 'click', QR.oekaki.draw $.on nodes.fileButton, 'click', QR.openFileInput @@ -525,23 +605,25 @@ QR = # We don't receive blur events from captcha iframe. $.on d, 'click', QR.focus - if $.engine is 'gecko' + # XXX Workaround for image pasting in Firefox, obsolete as of v50. + # https://bugzilla.mozilla.org/show_bug.cgi?id=906420 + if $.engine is 'gecko' and not window.DataTransferItemList nodes.pasteArea.hidden = false - new MutationObserver(QR.pasteFF).observe nodes.pasteArea, {childList: true} + new MutationObserver(QR.pasteFF).observe nodes.pasteArea, {childList: true} # save selected post's data - items = ['thread', 'name', 'email', 'sub', 'com', 'filename'] + items = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'flag'] i = 0 save = -> QR.selected.save @ while name = items[i++] - continue unless node = nodes[name] + continue if not (node = nodes[name]) event = if node.nodeName is 'SELECT' then 'change' else 'input' $.on nodes[name], event, save # XXX Blink and WebKit treat width and height of +
              @@ -42,7 +43,7 @@ - + diff --git a/src/Quotelinks/QuoteBacklink.coffee b/src/Quotelinks/QuoteBacklink.coffee index ed5e3c73c2..d7be85ac17 100644 --- a/src/Quotelinks/QuoteBacklink.coffee +++ b/src/Quotelinks/QuoteBacklink.coffee @@ -10,10 +10,15 @@ QuoteBacklink = # Second callback adds relevant containers into posts. # This is is so that fetched posts can get their backlinks, # and that as much backlinks are appended in the background as possible. - containers: {} + containers: $.dict() init: -> return if g.VIEW not in ['index', 'thread'] or !Conf['Quote Backlinks'] + # Add a class to differentiate when backlinks are at + # the top (default) or bottom of a post + if (@bottomBacklinks = Conf['Bottom Backlinks']) + $.addClass doc, 'bottom-backlinks' + Callbacks.Post.push name: 'Quote Backlinking Part 1' cb: @firstNode @@ -22,14 +27,15 @@ QuoteBacklink = cb: @secondNode firstNode: -> return if @isClone or !@quotes.length or @isRebuilt - markYours = Conf['Mark Quotes of You'] and QuoteYou.db?.get {boardID: @board.ID, threadID: @thread.ID, postID: @ID} + markYours = Conf['Mark Quotes of You'] and QuoteYou.isYou(@) a = $.el 'a', - href: Build.postURL @board.ID, @thread.ID, @ID + href: g.SITE.Build.postURL @board.ID, @thread.ID, @ID className: if @isHidden then 'filtered backlink' else 'backlink' - textContent: Conf['backlink'].replace(/%(?:id|%)/g, (x) => {'%id': @ID, '%%': '%'}[x]) + (if markYours then '\u00A0(You)' else '') + textContent: Conf['backlink'].replace(/%(?:id|%)/g, (x) => ({'%id': @ID, '%%': '%'})[x]) + $.add a, QuoteYou.mark.cloneNode(true) if markYours for quote in @quotes containers = [QuoteBacklink.getContainer quote] - if (post = g.posts[quote]) and post.nodes.backlinkContainer + if (post = g.posts.get(quote)) and post.nodes.backlinkContainer # Don't add OP clones when OP Backlinks is disabled, # as the clones won't have the backlink containers. for clone in post.clones @@ -48,15 +54,16 @@ QuoteBacklink = return secondNode: -> if @isClone and (@origin.isReply or Conf['OP Backlinks']) - @nodes.backlinkContainer = $ '.container', @nodes.info + @nodes.backlinkContainer = $ '.container', @nodes.post return # Don't backlink the OP. return unless @isReply or Conf['OP Backlinks'] container = QuoteBacklink.getContainer @fullID @nodes.backlinkContainer = container - $.add @nodes.info, container + if QuoteBacklink.bottomBacklinks + $.add @nodes.post, container + else + $.add @nodes.info, container getContainer: (id) -> @containers[id] or= $.el 'span', className: 'container' - -return QuoteBacklink diff --git a/src/Quotelinks/QuoteCT.coffee b/src/Quotelinks/QuoteCT.coffee index 076865fc7a..fa7104ccfe 100644 --- a/src/Quotelinks/QuoteCT.coffee +++ b/src/Quotelinks/QuoteCT.coffee @@ -6,7 +6,9 @@ QuoteCT = ExpandComment.callbacks.push @node # \u00A0 is nbsp - @text = '\u00A0(Cross-thread)' + @mark = $.el 'span', + textContent: '\u00A0(Cross-thread)' + className: 'qmark-ct' Callbacks.Post.push name: 'Mark Cross-thread Quotes' cb: @node @@ -19,9 +21,7 @@ QuoteCT = {boardID, threadID} = Get.postDataFromLink quotelink continue unless threadID # deadlink if @isClone - quotelink.textContent = quotelink.textContent.replace QuoteCT.text, '' + $.rm $('.qmark-ct', quotelink) if boardID is board.ID and threadID isnt thread.ID - $.add quotelink, $.tn QuoteCT.text + $.add quotelink, QuoteCT.mark.cloneNode(true) return - -return QuoteCT diff --git a/src/Quotelinks/QuoteInline.coffee b/src/Quotelinks/QuoteInline.coffee index 7e2f5a554e..b31bf35356 100644 --- a/src/Quotelinks/QuoteInline.coffee +++ b/src/Quotelinks/QuoteInline.coffee @@ -30,10 +30,11 @@ QuoteInline = href: link.href toggle: (e) -> - return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0 + return if $.modifiedClick e {boardID, threadID, postID} = Get.postDataFromLink @ - return if Conf['Inline Cross-thread Quotes Only'] and g.VIEW is 'thread' and g.posts["#{boardID}.#{postID}"]?.nodes.root.offsetParent # exists and not hidden + return if Conf['Inline Cross-thread Quotes Only'] and g.VIEW is 'thread' and g.posts.get("#{boardID}.#{postID}")?.nodes.root.offsetParent # exists and not hidden + return if $.hasClass(doc, 'catalog-mode') e.preventDefault() quoter = Get.postFromNode @ @@ -47,7 +48,7 @@ QuoteInline = findRoot: (quotelink, isBacklink) -> if isBacklink - quotelink.parentNode.parentNode + $.x 'ancestor::*[parent::*[contains(@class,"post")]][1]', quotelink else $.x 'ancestor-or-self::*[parent::blockquote][1]', quotelink @@ -64,14 +65,16 @@ QuoteInline = $.addClass qroot, 'hasInline' new Fetcher boardID, threadID, postID, inline, quoter - return unless (post = g.posts["#{boardID}.#{postID}"]) and + return if not ( + (post = g.posts.get("#{boardID}.#{postID}")) and context.thread is post.thread + ) # Hide forward post if it's a backlink of a post in this thread. # Will only unhide if there's no inlined backlinks of it anymore. if isBacklink and Conf['Forward Hiding'] $.addClass post.nodes.root, 'forwarded' - post.forwarded++ or post.forwarded = 1 + post.forwarded++ or (post.forwarded = 1) # Decrease the unread count if this post # is in the array of unread posts. @@ -84,22 +87,24 @@ QuoteInline = root = QuoteInline.findRoot quotelink, isBacklink root = $.x "following-sibling::div[@data-full-i-d='#{boardID}.#{postID}'][1]", root qroot = $.x 'ancestor::*[contains(@class,"postContainer")][1]', root + {parentNode} = root $.rm root + $.event 'PostsRemoved', null, parentNode unless $ '.inline', qroot $.rmClass qroot, 'hasInline' # Stop if it only contains text. - return unless el = root.firstElementChild + return if not (el = root.firstElementChild) # Dereference clone. - post = g.posts["#{boardID}.#{postID}"] + post = g.posts.get("#{boardID}.#{postID}") post.rmClone el.dataset.clone # Decrease forward count and unhide. if Conf['Forward Hiding'] and isBacklink and - context.thread is g.threads["#{boardID}.#{threadID}"] and + context.thread is g.threads.get("#{boardID}.#{threadID}") and not --post.forwarded delete post.forwarded $.rmClass post.nodes.root, 'forwarded' @@ -110,5 +115,3 @@ QuoteInline = QuoteInline.rm inlined, boardID, threadID, postID, context $.rmClass inlined, 'inlined' return - -return QuoteInline diff --git a/src/Quotelinks/QuoteOP.coffee b/src/Quotelinks/QuoteOP.coffee index 482993ab9f..d7464083af 100644 --- a/src/Quotelinks/QuoteOP.coffee +++ b/src/Quotelinks/QuoteOP.coffee @@ -6,7 +6,9 @@ QuoteOP = ExpandComment.callbacks.push @node # \u00A0 is nbsp - @text = '\u00A0(OP)' + @mark = $.el 'span', + textContent: '\u00A0(OP)' + className: 'qmark-op' Callbacks.Post.push name: 'Mark OP Quotes' cb: @node @@ -22,7 +24,7 @@ QuoteOP = if @isClone and @thread.fullID in quotes i = 0 while quotelink = quotelinks[i++] - quotelink.textContent = quotelink.textContent.replace QuoteOP.text, '' + $.rm $('.qmark-op', quotelink) {fullID} = @context.thread # add (OP) to quotes quoting this context's OP. @@ -32,7 +34,5 @@ QuoteOP = while quotelink = quotelinks[i++] {boardID, postID} = Get.postDataFromLink quotelink if "#{boardID}.#{postID}" is fullID - $.add quotelink, $.tn QuoteOP.text + $.add quotelink, QuoteOP.mark.cloneNode(true) return - -return QuoteOP diff --git a/src/Quotelinks/QuotePreview.coffee b/src/Quotelinks/QuotePreview.coffee index 123f864064..3f0b544d9b 100644 --- a/src/Quotelinks/QuotePreview.coffee +++ b/src/Quotelinks/QuotePreview.coffee @@ -1,6 +1,13 @@ QuotePreview = init: -> - return unless g.VIEW in ['index', 'thread'] and Conf['Quote Previewing'] + return unless Conf['Quote Previewing'] + + if g.VIEW is 'archive' + $.on d, 'mouseover', (e) -> + if e.target.nodeName is 'A' and $.hasClass(e.target, 'quotelink') + QuotePreview.mouseover.call e.target, e + + return unless g.VIEW in ['index', 'thread'] if Conf['Comment Expansion'] ExpandComment.callbacks.push @node @@ -15,7 +22,7 @@ QuotePreview = return mouseover: (e) -> - return if $.hasClass(@, 'inlined') or !d.contains(@) + return if ($.hasClass(@, 'inlined') and not $.hasClass(doc, 'catalog-mode')) or not d.contains(@) {boardID, threadID, postID} = Get.postDataFromLink @ @@ -33,7 +40,7 @@ QuotePreview = endEvents: 'mouseout click' cb: QuotePreview.mouseout - if Conf['Quote Highlighting'] and (origin = g.posts["#{boardID}.#{postID}"]) + if Conf['Quote Highlighting'] and (origin = g.posts.get("#{boardID}.#{postID}")) posts = [origin].concat origin.clones # Remove the clone that's in the qp from the array. posts.pop() @@ -43,7 +50,9 @@ QuotePreview = mouseout: -> # Stop if it only contains text. - return unless root = @el.firstElementChild + return if not (root = @el.firstElementChild) + + $.event 'PostsRemoved', null, Header.hover clone = Get.postFromRoot root post = clone.origin @@ -53,5 +62,3 @@ QuotePreview = for post in [post].concat post.clones $.rmClass post.nodes.post, 'qphl' return - -return QuotePreview diff --git a/src/Quotelinks/QuoteStrikeThrough.coffee b/src/Quotelinks/QuoteStrikeThrough.coffee index a22afbb869..77816c661a 100644 --- a/src/Quotelinks/QuoteStrikeThrough.coffee +++ b/src/Quotelinks/QuoteStrikeThrough.coffee @@ -11,8 +11,6 @@ QuoteStrikeThrough = return if @isClone for quotelink in @nodes.quotelinks {boardID, postID} = Get.postDataFromLink quotelink - if g.posts["#{boardID}.#{postID}"]?.isHidden + if g.posts.get("#{boardID}.#{postID}")?.isHidden $.addClass quotelink, 'filtered' return - -return QuoteStrikeThrough diff --git a/src/Quotelinks/QuoteThreading.coffee b/src/Quotelinks/QuoteThreading.coffee index a1da9d1d4d..52352d07f3 100644 --- a/src/Quotelinks/QuoteThreading.coffee +++ b/src/Quotelinks/QuoteThreading.coffee @@ -7,12 +7,12 @@ QuoteThreading = return unless Conf['Quote Threading'] and g.VIEW is 'thread' @controls = $.el 'label', - <%= html(' Threading') %> + `<%= html(' Threading') %>` @threadNewLink = $.el 'span', className: 'brackets-wrap threadnewlink' hidden: true - $.extend @threadNewLink, <%= html('Thread New Posts') %> + $.extend @threadNewLink, `<%= html('Thread New Posts') %>` @input = $('input', @controls) @input.checked = Conf['Thread Quotes'] @@ -34,28 +34,38 @@ QuoteThreading = name: 'Quote Threading' cb: @node - parent: {} - children: {} - inserted: {} + parent: $.dict() + children: $.dict() + inserted: $.dict() + + toggleThreading: -> + @setThreadingState !Conf['Thread Quotes'] + + setThreadingState: (enabled) -> + @input.checked = enabled + @setEnabled.call @input + @rethread.call @input setEnabled: -> - other = ReplyPruning.inputs?.enabled - if @checked and other?.checked - other.checked = false - $.event 'change', null, other + if @checked + $.set 'Prune All Threads', false + other = ReplyPruning.inputs?.enabled + if other?.checked + other.checked = false + $.event 'change', null, other $.cb.checked.call @ setThread: -> QuoteThreading.thread = @ $.asap (-> !Conf['Thread Updater'] or $ '.navLinksBot > .updatelink'), -> - $.add navLinksBot, [$.tn(' '), QuoteThreading.threadNewLink] if (navLinksBot = $ '.navLinksBot') + ($.add navLinksBot, [$.tn(' '), QuoteThreading.threadNewLink] if (navLinksBot = $ '.navLinksBot')) node: -> return if @isFetchedQuote or @isClone or !@isReply parents = new Set() lastParent = null - for quote in @quotes when parent = g.posts[quote] + for quote in @quotes when parent = g.posts.get(quote) if not parent.isFetchedQuote and parent.isReply and parent.ID < @ID parents.add parent.ID lastParent = parent if not lastParent or parent.ID > lastParent.ID @@ -77,9 +87,11 @@ QuoteThreading = posts insert: (post) -> - return false unless Conf['Thread Quotes'] and + return false if not ( + Conf['Thread Quotes'] and (parent = QuoteThreading.parent[post.fullID]) and !QuoteThreading.inserted[post.fullID] + ) descendants = QuoteThreading.descendants post if !Unread.posts.has(parent.ID) @@ -129,7 +141,7 @@ QuoteThreading = else nodes = [] Unread.order = new RandomAccessList() - QuoteThreading.inserted = {} + QuoteThreading.inserted = $.dict() posts.forEach (post) -> return if post.isFetchedQuote Unread.order.push post @@ -139,12 +151,10 @@ QuoteThreading = $.rmClass post.nodes.root, 'threadOP' $.rm post.nodes.threadContainer delete post.nodes.threadContainer - $.add thread.OP.nodes.root.parentNode, nodes + $.add thread.nodes.root, nodes Unread.position = Unread.order.first Unread.updatePosition() Unread.setLine true Unread.read() Unread.update() - -return QuoteThreading diff --git a/src/Quotelinks/QuoteYou.coffee b/src/Quotelinks/QuoteYou.coffee index 989cad04ae..e0d9836b8c 100644 --- a/src/Quotelinks/QuoteYou.coffee +++ b/src/Quotelinks/QuoteYou.coffee @@ -5,12 +5,13 @@ QuoteYou = @db = new DataBoard 'yourPosts' $.sync 'Remember Your Posts', (enabled) -> Conf['Remember Your Posts'] = enabled $.on d, 'QRPostSuccessful', (e) -> - $.forceSync 'Remember Your Posts' - if Conf['Remember Your Posts'] + cb = PostRedirect.delay() + $.get 'Remember Your Posts', Conf['Remember Your Posts'], (items) -> + return unless items['Remember Your Posts'] {boardID, threadID, postID} = e.detail - QuoteYou.db.set {boardID, threadID, postID, val: true} + QuoteYou.db.set {boardID, threadID, postID, val: true}, cb - return unless g.VIEW in ['index', 'thread'] + return unless g.VIEW in ['index', 'thread', 'archive'] if Conf['Highlight Own Posts'] $.addClass doc, 'highlight-own' @@ -22,32 +23,80 @@ QuoteYou = ExpandComment.callbacks.push @node # \u00A0 is nbsp - @text = '\u00A0(You)' + @mark = $.el 'span', + textContent: '\u00A0(You)' + className: 'qmark-you' Callbacks.Post.push name: 'Mark Quotes of You' cb: @node + QuoteYou.menu.init() + + isYou: (post) -> + !!QuoteYou.db?.get { + boardID: post.boardID + threadID: post.threadID + postID: post.ID + } + node: -> return if @isClone - if QuoteYou.db.get {boardID: @board.ID, threadID: @thread.ID, postID: @ID} + if QuoteYou.isYou @ $.addClass @nodes.root, 'yourPost' # Stop there if there's no quotes in that post. return unless @quotes.length for quotelink in @nodes.quotelinks when QuoteYou.db.get Get.postDataFromLink quotelink - $.add quotelink, $.tn QuoteYou.text if Conf['Mark Quotes of You'] + $.add quotelink, QuoteYou.mark.cloneNode(true) if Conf['Mark Quotes of You'] $.addClass quotelink, 'you' $.addClass @nodes.root, 'quotesYou' return + menu: + init: -> + label = $.el 'label', + className: 'toggle-you' + , + `<%= html(' You') %>` + input = $ 'input', label + $.on input, 'change', QuoteYou.menu.toggle + Menu.menu?.addEntry + el: label + order: 80 + open: (post) -> + QuoteYou.menu.post = (post.origin or post) + input.checked = QuoteYou.isYou post + true + + toggle: -> + {post} = QuoteYou.menu + data = {boardID: post.board.ID, threadID: post.thread.ID, postID: post.ID, val: true} + if @checked + QuoteYou.db.set data + else + QuoteYou.db.delete data + for clone in [post].concat post.clones + clone.nodes.root.classList.toggle 'yourPost', @checked + for quotelink in Get.allQuotelinksLinkingTo post + if @checked + $.add quotelink, QuoteYou.mark.cloneNode(true) if Conf['Mark Quotes of You'] + else + $.rm $('.qmark-you', quotelink) + quotelink.classList.toggle 'you', @checked + if $.hasClass quotelink, 'quotelink' + quoter = Get.postFromNode(quotelink).nodes.root + quoter.classList.toggle 'quotesYou', !!$('.quotelink.you', quoter) + return + cb: seek: (type) -> - $.rmClass highlight, 'highlight' if highlight = $ '.highlight' + {highlight} = g.SITE.classes + $.rmClass highlighted, highlight if (highlighted = $ ".#{highlight}") unless QuoteYou.lastRead and doc.contains(QuoteYou.lastRead) and $.hasClass(QuoteYou.lastRead, 'quotesYou') - unless (post = QuoteYou.lastRead = $ '.quotesYou') + if not (post = QuoteYou.lastRead = $ '.quotesYou') new Notice 'warning', 'No posts are currently quoting you, loser.', 20 return return if QuoteYou.cb.scroll post @@ -63,14 +112,16 @@ QuoteYou = QuoteYou.cb.scroll posts[if type is 'following' then 0 else posts.length - 1] scroll: (root) -> - post = $ '.post', root - if !post.getBoundingClientRect().height + post = Get.postFromRoot root + if !post.nodes.post.getBoundingClientRect().height return false else QuoteYou.lastRead = root - window.location = "##{post.id}" - Header.scrollTo post - $.addClass post, 'highlight' + location.href = Get.url('post', post) + Header.scrollTo post.nodes.post + if post.isReply + sel = "#{g.SITE.selectors.postContainer}#{g.SITE.selectors.highlightable.reply}" + node = post.nodes.root + node = $ sel, node unless node.matches(sel) + $.addClass node, g.SITE.classes.highlight return true - -return QuoteYou diff --git a/src/Quotelinks/Quotify.coffee b/src/Quotelinks/Quotify.coffee index 301d32b7e8..8ea79956f6 100644 --- a/src/Quotelinks/Quotify.coffee +++ b/src/Quotelinks/Quotify.coffee @@ -2,6 +2,8 @@ Quotify = init: -> return if g.VIEW not in ['index', 'thread'] or !Conf['Resurrect Quotes'] + $.addClass doc, 'resurrect-quotes' + if Conf['Comment Expansion'] ExpandComment.callbacks.push @node @@ -20,11 +22,11 @@ Quotify = return parseArchivelink: (link) -> - return unless (m = link.pathname.match /^\/([^/]+)\/thread\/S?(\d+)\/?$/) - return if link.hostname is 'boards.4chan.org' + return if not (m = link.pathname.match /^\/([^/]+)\/thread\/S?(\d+)\/?$/) + return if link.hostname in ['boards.4chan.org', 'boards.4channel.org'] boardID = m[1] threadID = m[2] - postID = link.hash.match(/^#p?(\d+)$|$/)[1] or threadID + postID = link.hash.match(/^#[pq]?(\d+)$|$/)[1] or threadID if Redirect.to 'post', {boardID, postID} $.addClass link, 'quotelink' $.extend link.dataset, {boardID, threadID, postID} @@ -41,7 +43,7 @@ Quotify = return quote = deadlink.textContent - return unless postID = quote.match(/\d+$/)?[0] + return if not (postID = quote.match(/\d+$/)?[0]) if postID[0] is '0' # Fix quotelinks that start with a `0`. Quotify.fixDeadlink deadlink @@ -52,20 +54,21 @@ Quotify = @board.ID quoteID = "#{boardID}.#{postID}" - if post = g.posts[quoteID] + if post = g.posts.get(quoteID) unless post.isDead # Don't (Dead) when quotifying in an archived post, # and we know the post still exists. a = $.el 'a', - href: Build.postURL boardID, post.thread.ID, postID + href: g.SITE.Build.postURL boardID, post.thread.ID, postID className: 'quotelink' textContent: quote else # Replace the .deadlink span if we can redirect. a = $.el 'a', - href: Build.postURL boardID, post.thread.ID, postID + href: g.SITE.Build.postURL boardID, post.thread.ID, postID className: 'quotelink deadlink' - textContent: "#{quote}\u00A0(Dead)" + textContent: quote + $.add a, Post.deadMark.cloneNode(true) $.extend a.dataset, {boardID, threadID: post.thread.ID, postID} else @@ -76,7 +79,8 @@ Quotify = a = $.el 'a', href: redirect or 'javascript:;' className: 'deadlink' - textContent: "#{quote}\u00A0(Dead)" + textContent: quote + $.add a, Post.deadMark.cloneNode(true) if fetchable # Make it function as a normal quote if we can fetch the post. $.addClass a, 'quotelink' @@ -85,7 +89,7 @@ Quotify = @quotes.push quoteID unless quoteID in @quotes unless a - deadlink.textContent = "#{quote}\u00A0(Dead)" + $.add deadlink, Post.deadMark.cloneNode(true) return $.replace deadlink, a @@ -99,5 +103,3 @@ Quotify = $.before deadlink, green $.add green, deadlink $.replace deadlink, [deadlink.childNodes...] - -return Quotify diff --git a/src/classes/Board.coffee b/src/classes/Board.coffee index 2701715336..5eb7bc6971 100644 --- a/src/classes/Board.coffee +++ b/src/classes/Board.coffee @@ -2,9 +2,23 @@ class Board toString: -> @ID constructor: (@ID) -> + @boardID = @ID + @siteID = g.SITE.ID @threads = new SimpleDict() @posts = new SimpleDict() + @config = BoardConfig.boards?[@ID] or {} g.boards[@] = @ -return Board + cooldowns: -> + c2 = (@config or {}).cooldowns or {} + c = + thread: c2.threads or 0 + reply: c2.replies or 0 + image: c2.images or 0 + thread_global: 300 # inter-board thread cooldown + # Pass users have reduced cooldowns. + if d.cookie.indexOf('pass_enabled=1') >= 0 + for key in ['reply', 'image'] + c[key] = Math.ceil(c[key] / 2) + c diff --git a/src/classes/Callbacks.coffee b/src/classes/Callbacks.coffee index db84a8451c..89aa2f5522 100644 --- a/src/classes/Callbacks.coffee +++ b/src/classes/Callbacks.coffee @@ -2,6 +2,7 @@ class Callbacks @Post = new Callbacks 'Post' @Thread = new Callbacks 'Thread' @CatalogThread = new Callbacks 'Catalog Thread' + @CatalogThreadNative = new Callbacks 'Catalog Thread' constructor: (@type) -> @keys = [] @@ -10,7 +11,9 @@ class Callbacks @keys.push name unless @[name] @[name] = cb - execute: (node, keys=@keys) -> + execute: (node, keys=@keys, force=false) -> + return if node.callbacksExecuted and !force + node.callbacksExecuted = true for name in keys try @[name]?.call node @@ -22,5 +25,3 @@ class Callbacks html: node.nodes?.root?.outerHTML Main.handleErrors errors if errors - -return Callbacks diff --git a/src/classes/CatalogThread.coffee b/src/classes/CatalogThread.coffee index ba2c3ce6f9..f3e771c3e2 100644 --- a/src/classes/CatalogThread.coffee +++ b/src/classes/CatalogThread.coffee @@ -4,14 +4,13 @@ class CatalogThread constructor: (root, @thread) -> @ID = @thread.ID @board = @thread.board + {post} = @thread.OP.nodes @nodes = - root: root - thumb: $ '.catalog-thumb', root - icons: $ '.catalog-icons', root - postCount: $ '.post-count', root - fileCount: $ '.file-count', root - pageCount: $ '.page-count', root - comment: $ '.comment', root + root: root + thumb: $ '.catalog-thumb', post + icons: $ '.catalog-icons', post + postCount: $ '.post-count', post + fileCount: $ '.file-count', post + pageCount: $ '.page-count', post + replies: null @thread.catalogView = @ - -return CatalogThread diff --git a/src/classes/CatalogThreadNative.coffee b/src/classes/CatalogThreadNative.coffee new file mode 100644 index 0000000000..5a96dd7260 --- /dev/null +++ b/src/classes/CatalogThreadNative.coffee @@ -0,0 +1,12 @@ +class CatalogThreadNative + toString: -> @ID + + constructor: (root) -> + @nodes = + root: root + thumb: $(g.SITE.selectors.catalog.thumb, root) + @siteID = g.SITE.ID + @boardID = @nodes.thumb.parentNode.pathname.split(/\/+/)[1] + @board = g.boards[@boardID] or new Board(@boardID) + @ID = @threadID = +(root.dataset.id or root.id).match(/\d*$/)[0] + @thread = @board.threads.get(@ID) or new Thread(@ID, @board) diff --git a/src/classes/Connection.coffee b/src/classes/Connection.coffee index 810f3341e6..843e6aebf6 100644 --- a/src/classes/Connection.coffee +++ b/src/classes/Connection.coffee @@ -17,8 +17,6 @@ class Connection typeof e.data is 'string' and e.data[...g.NAMESPACE.length] is g.NAMESPACE data = JSON.parse e.data[g.NAMESPACE.length..] - for type, value of data - @cb[type]? value + for type, value of data when $.hasOwn(@cb, type) + @cb[type] value return - -return Connection diff --git a/src/classes/DataBoard.coffee b/src/classes/DataBoard.coffee index 1890a42345..3cecd069c9 100644 --- a/src/classes/DataBoard.coffee +++ b/src/classes/DataBoard.coffee @@ -1,8 +1,8 @@ class DataBoard - @keys = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads', 'customTitles'] + @keys = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads', 'watcherLastModified', 'customTitles'] constructor: (@key, sync, dontClean) -> - @data = Conf[@key] + @initData Conf[@key] $.sync @key, @onSync @clean() unless dontClean return unless sync @@ -13,43 +13,97 @@ class DataBoard @sync = sync $.on d, '4chanXInitFinished', init - save: (cb) -> $.set @key, @data, cb - - delete: ({boardID, threadID, postID}) -> - $.forceSync @key - if postID - return unless @data.boards[boardID]?[threadID] - delete @data.boards[boardID][threadID][postID] - @deleteIfEmpty {boardID, threadID} - else if threadID - return unless @data.boards[boardID] - delete @data.boards[boardID][threadID] - @deleteIfEmpty {boardID} - else - delete @data.boards[boardID] - @save() + initData: (@data) -> + if @data.boards + {boards, lastChecked} = @data + @data['4chan.org'] = {boards, lastChecked} + delete @data.boards + delete @data.lastChecked + @data[g.SITE.ID] or= boards: $.dict() + + changes: [] + + save: (change, cb) -> + change() + @changes.push change + $.get @key, {boards: $.dict()}, (items) => + return unless @changes.length + needSync = ((items[@key].version or 0) > (@data.version or 0)) + if needSync + @initData items[@key] + change() for change in @changes + @changes = [] + @data.version = (@data.version or 0) + 1 + $.set @key, @data, => + @sync?() if needSync + cb?() + + forceSync: (cb) -> + $.get @key, {boards: $.dict()}, (items) => + if (items[@key].version or 0) > (@data.version or 0) + @initData items[@key] + change() for change in @changes + @sync?() + cb?() - deleteIfEmpty: ({boardID, threadID}) -> - $.forceSync @key + delete: ({siteID, boardID, threadID, postID}, cb) -> + siteID or= g.SITE.ID + return unless @data[siteID] + @save => + if postID + return unless @data[siteID].boards[boardID]?[threadID] + delete @data[siteID].boards[boardID][threadID][postID] + @deleteIfEmpty {siteID, boardID, threadID} + else if threadID + return unless @data[siteID].boards[boardID] + delete @data[siteID].boards[boardID][threadID] + @deleteIfEmpty {siteID, boardID} + else + delete @data[siteID].boards[boardID] + , cb + + deleteIfEmpty: ({siteID, boardID, threadID}) -> + return unless @data[siteID] if threadID - unless Object.keys(@data.boards[boardID][threadID]).length - delete @data.boards[boardID][threadID] - @deleteIfEmpty {boardID} - else unless Object.keys(@data.boards[boardID]).length - delete @data.boards[boardID] - - set: ({boardID, threadID, postID, val}, cb) -> - $.forceSync @key + unless Object.keys(@data[siteID].boards[boardID][threadID]).length + delete @data[siteID].boards[boardID][threadID] + @deleteIfEmpty {siteID, boardID} + else unless Object.keys(@data[siteID].boards[boardID]).length + delete @data[siteID].boards[boardID] + + set: (data, cb) -> + @save => + @setUnsafe data + , cb + + setUnsafe: ({siteID, boardID, threadID, postID, val}) -> + siteID or= g.SITE.ID + @data[siteID] or= boards: $.dict() if postID isnt undefined - ((@data.boards[boardID] or= {})[threadID] or= {})[postID] = val + ((@data[siteID].boards[boardID] or= $.dict())[threadID] or= $.dict())[postID] = val else if threadID isnt undefined - (@data.boards[boardID] or= {})[threadID] = val + (@data[siteID].boards[boardID] or= $.dict())[threadID] = val else - @data.boards[boardID] = val - @save cb + @data[siteID].boards[boardID] = val - get: ({boardID, threadID, postID, defaultValue}) -> - if board = @data.boards[boardID] + extend: ({siteID, boardID, threadID, postID, val}, cb) -> + @save => + oldVal = @get {siteID, boardID, threadID, postID, defaultValue: $.dict()} + for key, subVal of val + if typeof subVal is 'undefined' + delete oldVal[key] + else + oldVal[key] = subVal + @setUnsafe {siteID, boardID, threadID, postID, val: oldVal} + , cb + + setLastChecked: (key='lastChecked') -> + @save => + @data[key] = Date.now() + + get: ({siteID, boardID, threadID, postID, defaultValue}) -> + siteID or= g.SITE.ID + if board = @data[siteID]?.boards[boardID] unless threadID? if postID? for ID, thread in board @@ -65,31 +119,35 @@ class DataBoard thread val or defaultValue - forceSync: -> - $.forceSync @key - clean: -> - $.forceSync @key - for boardID, val of @data.boards - @deleteIfEmpty {boardID} - + siteID = g.SITE.ID + for boardID, val of @data[siteID].boards + @deleteIfEmpty {siteID, boardID} now = Date.now() - if (@data.lastChecked or 0) < now - 2 * $.HOUR - @data.lastChecked = now - for boardID of @data.boards + unless now - 2 * $.HOUR < (@data[siteID].lastChecked or 0) <= now + @data[siteID].lastChecked = now + for boardID of @data[siteID].boards @ajaxClean boardID return ajaxClean: (boardID) -> - $.cache "//a.4cdn.org/#{boardID}/threads.json", (e1) => - return unless e1.target.status in [200, 404] - $.cache "//a.4cdn.org/#{boardID}/archive.json", (e2) => - return unless e2.target.status in [200, 404] - @ajaxCleanParse boardID, e1.target.response, e2.target.response + that = @ + siteID = g.SITE.ID + threadsList = g.SITE.urls.threadsListJSON?({siteID, boardID}) + return unless threadsList + $.cache threadsList, -> + return unless @status is 200 + archiveList = g.SITE.urls.archiveListJSON?({siteID, boardID}) + return that.ajaxCleanParse(boardID, @response) unless archiveList + response1 = @response + $.cache archiveList, -> + return unless @status is 200 or (!g.SITE.archivedBoardsKnown and @status is 404) + that.ajaxCleanParse(boardID, response1, @response) ajaxCleanParse: (boardID, response1, response2) -> - return unless (board = @data.boards[boardID]) - threads = {} + siteID = g.SITE.ID + return if not (board = @data[siteID].boards[boardID]) + threads = $.dict() if response1 for page in response1 for thread in page.threads @@ -98,12 +156,11 @@ class DataBoard if response2 for ID in response2 threads[ID] = board[ID] if ID of board - @data.boards[boardID] = threads - @deleteIfEmpty {boardID} - @save() + @data[siteID].boards[boardID] = threads + @deleteIfEmpty {siteID, boardID} + $.set @key, @data onSync: (data) => - @data = data or boards: {} + return unless (data.version or 0) > (@data.version or 0) + @initData data @sync?() - -return DataBoard diff --git a/src/classes/Fetcher.coffee b/src/classes/Fetcher.coffee index 1125934ded..1031fb8565 100644 --- a/src/classes/Fetcher.coffee +++ b/src/classes/Fetcher.coffee @@ -1,19 +1,29 @@ class Fetcher constructor: (@boardID, @threadID, @postID, @root, @quoter) -> - if post = g.posts["#{@boardID}.#{@postID}"] + if post = g.posts.get("#{@boardID}.#{@postID}") + @insert post + return + + # 4chan X catalog data + if (post = Index.replyData?["#{@boardID}.#{@postID}"]) and (thread = g.threads.get("#{@boardID}.#{@threadID}")) + board = g.boards[@boardID] + post = new Post g.SITE.Build.postFromObject(post, @boardID), thread, board, {isFetchedQuote: true} + Main.callbackNodes 'Post', [post] @insert post return @root.textContent = "Loading post No.#{@postID}..." if @threadID - $.cache "//a.4cdn.org/#{@boardID}/thread/#{@threadID}.json", (e, isCached) => - @fetchedPost e.target, isCached + that = @ + $.cache g.SITE.urls.threadJSON({boardID: @boardID, threadID: @threadID}), ({isCached}) -> + that.fetchedPost @, isCached else @archivedPost() insert: (post) -> # Stop here if the container has been removed while loading. return unless @root.parentNode + @quoter or= post clone = post.addClone @quoter.context, ($.hasClass @root, 'dialog') Main.callbackNodes 'Post', [clone] @@ -38,40 +48,43 @@ class Fetcher $.rmAll @root $.add @root, nodes.root - $.event 'PostsInserted' + $.event 'PostsInserted', null, @root fetchedPost: (req, isCached) -> # In case of multiple callbacks for the same request, # don't parse the same original post more than once. - if post = g.posts["#{@boardID}.#{@postID}"] + if post = g.posts.get("#{@boardID}.#{@postID}") @insert post return {status} = req unless status is 200 # The thread can die by the time we check a quote. - return if @archivedPost() + return if status and @archivedPost() $.addClass @root, 'warning' @root.textContent = if status is 404 "Thread No.#{@threadID} 404'd." + else if !status + 'Connection Error' else "Error #{req.statusText} (#{req.status})." return {posts} = req.response - Build.spoilerRange[@boardID] = posts[0].custom_spoiler + g.SITE.Build.spoilerRange[@boardID] = posts[0].custom_spoiler for post in posts break if post.no is @postID # we found it! if post.no isnt @postID # Cached requests can be stale and must be rechecked. if isCached - api = "//a.4cdn.org/#{@boardID}/thread/#{@threadID}.json" + api = g.SITE.urls.threadJSON({boardID: @boardID, threadID: @threadID}) $.cleanCache (url) -> url is api - $.cache api, (e) => - @fetchedPost e.target, false + that = @ + $.cache api, -> + that.fetchedPost @, false return # The post can be deleted by the time we check a quote. @@ -83,39 +96,34 @@ class Fetcher board = g.boards[@boardID] or new Board @boardID - thread = g.threads["#{@boardID}.#{@threadID}"] or + thread = g.threads.get("#{@boardID}.#{@threadID}") or new Thread @threadID, board - post = new Post Build.postFromObject(post, @boardID), thread, board - post.isFetchedQuote = true + post = new Post g.SITE.Build.postFromObject(post, @boardID), thread, board, {isFetchedQuote: true} Main.callbackNodes 'Post', [post] @insert post archivedPost: -> return false unless Conf['Resurrect Quotes'] - return false unless url = Redirect.to 'post', {@boardID, @postID} + return false if not (url = Redirect.to 'post', {@boardID, @postID}) archive = Redirect.data.post[@boardID] - if /^https:\/\//.test(url) or location.protocol is 'http:' - $.cache url, (e) => - @parseArchivedPost e.target.response, url, archive - , - responseType: 'json' - withCredentials: archive.withCredentials - return true - else if Conf['Exempt Archives from Encryption'] - CrossOrigin.json url, (response) => - {media} = response - if media then for key of media when /_link$/.test key - # Image/thumbnail URLs loaded over HTTP can be modified in transit. - # Require them to be from an HTTP host so that no referrer is sent to them from an HTTPS page. - delete media[key] unless media[key]?.match /^http:\/\// - @parseArchivedPost response, url, archive + encryptionOK = /^https:\/\//.test(url) or location.protocol is 'http:' + if encryptionOK or Conf['Exempt Archives from Encryption'] + that = @ + CrossOrigin.cache url, -> + if !encryptionOK and @response?.media + {media} = @response + for key of media when /_link$/.test key + # Image/thumbnail URLs loaded over HTTP can be modified in transit. + # Require them to be from an HTTP host so that no referrer is sent to them from an HTTPS page. + delete media[key] unless $.getOwn(media, key)?.match /^http:\/\// + that.parseArchivedPost @response, url, archive return true return false parseArchivedPost: (data, url, archive) -> # In case of multiple callbacks for the same request, # don't parse the same original post more than once. - if post = g.posts["#{@boardID}.#{@postID}"] + if post = g.posts.get("#{@boardID}.#{@postID}") @insert post return @@ -141,14 +149,14 @@ class Fetcher greentext = text[0] is '>' text = text.replace /(\[\/?[a-z]+):lit(\])/g, '$1$2' text = for text2, j in text.split /(>>(?:>\/[a-z\d]+\/)?\d+)/g - <%= html('?{j % 2}{${text2}}{${text2}}') %> - text = <%= html('?{greentext}{@{text}}{@{text}}') %> + `<%= html('?{j % 2}{${text2}}{${text2}}') %>` + text = `<%= html('?{greentext}{@{text}}{@{text}}') %>` text - comment = <%= html('@{comment}') %> + comment = `<%= html('@{comment}') %>` @threadID = +data.thread_num o = - postID: @postID + ID: @postID threadID: @threadID boardID: @boardID isReply: @postID isnt @threadID @@ -158,67 +166,78 @@ class Fetcher name: data.name or '' tripcode: data.trip capcode: switch data.capcode + # https://github.com/pleebe/FoolFuuka/blob/bf4224eed04637a4d0bd4411c2bf5f9945dfec0b/assets/themes/foolz/foolfuuka-theme-fuuka/src/Partial/Board.php#L77 when 'M' then 'Mod' when 'A' then 'Admin' when 'D' then 'Developer' + when 'V' then 'Verified' + when 'F' then 'Founder' + when 'G' then 'Manager' uniqueID: data.poster_hash flagCode: data.poster_country - flag: data.poster_country_name + flagCodeTroll: data.troll_country_code + flag: data.poster_country_name or data.troll_country_name dateUTC: data.timestamp dateText: data.fourchan_date commentHTML: comment delete o.info.uniqueID if o.info.capcode - if data.media?.media_filename + if data.media and !!+data.media.banned + o.fileDeleted = true + else if data.media?.media_filename + {thumb_link} = data.media # Fix URLs missing origin - for key, val of data.media when /_link$/.test(key) and val?[0] is '/' - data.media[key] = url.split('/', 3).join('/') + val + thumb_link = url.split('/', 3).join('/') + thumb_link if thumb_link?[0] is '/' + thumb_link = '' unless Redirect.securityCheck thumb_link + media_link = Redirect.to('file', {boardID: @boardID, filename: data.media.media_orig}) + media_link = '' unless Redirect.securityCheck media_link o.file = name: data.media.media_filename - url: data.media.media_link or data.media.remote_media_link or - "#{location.protocol}//i.4cdn.org/#{@boardID}/#{encodeURIComponent data.media[if @boardID is 'f' then 'media_filename' else 'media_orig']}" + url: media_link or + if @boardID is 'f' + "#{location.protocol}//#{ImageHost.flashHost()}/#{@boardID}/#{encodeURIComponent E data.media.media_filename}" + else + "#{location.protocol}//#{ImageHost.host()}/#{@boardID}/#{data.media.media_orig}" height: data.media.media_h width: data.media.media_w MD5: data.media.media_hash size: $.bytesToString data.media.media_size - thumbURL: data.media.thumb_link or "#{location.protocol}//i.4cdn.org/#{@boardID}/#{data.media.preview_orig}" + thumbURL: thumb_link or "#{location.protocol}//#{ImageHost.thumbHost()}/#{@boardID}/#{data.media.preview_orig}" theight: data.media.preview_h twidth: data.media.preview_w isSpoiler: data.media.spoiler is '1' o.file.dimensions = "#{o.file.width}x#{o.file.height}" unless /\.pdf$/.test o.file.url o.file.tag = JSON.parse(data.media.exif).Tag if @boardID is 'f' and data.media.exif + o.extra = $.dict() board = g.boards[@boardID] or new Board @boardID - thread = g.threads["#{@boardID}.#{@threadID}"] or + thread = g.threads.get("#{@boardID}.#{@threadID}") or new Thread @threadID, board - post = new Post Build.post(o), thread, board + post = new Post g.SITE.Build.post(o), thread, board, {isFetchedQuote: true} post.kill() post.file.thumbURL = o.file.thumbURL if post.file - post.isFetchedQuote = true Main.callbackNodes 'Post', [post] @insert post archiveTags: - '\n': <%= html('
              ') %> - '[b]': <%= html('') %> - '[/b]': <%= html('') %> - '[spoiler]': <%= html('') %> - '[/spoiler]': <%= html('') %> - '[code]': <%= html('
              ') %>
              -    '[/code]':    <%= html('
              ') %> - '[moot]': <%= html('
              ') %> - '[/moot]': <%= html('
              ') %> - '[banned]': <%= html('') %> - '[/banned]': <%= html('') %> - '[fortune]': (text) -> <%= html('') %> - '[/fortune]': <%= html('') %> - '[i]': <%= html('') %> - '[/i]': <%= html('') %> - '[red]': <%= html('') %> - '[/red]': <%= html('') %> - '[green]': <%= html('') %> - '[/green]': <%= html('') %> - '[blue]': <%= html('') %> - '[/blue]': <%= html('') %> - -return Fetcher + '\n': `<%= html('
              ') %>` + '[b]': `<%= html('') %>` + '[/b]': `<%= html('') %>` + '[spoiler]': `<%= html('') %>` + '[/spoiler]': `<%= html('') %>` + '[code]': `<%= html('
              ') %>`
              +    '[/code]':    `<%= html('
              ') %>` + '[moot]': `<%= html('
              ') %>` + '[/moot]': `<%= html('
              ') %>` + '[banned]': `<%= html('') %>` + '[/banned]': `<%= html('') %>` + '[fortune]': (text) -> `<%= html('') %>` + '[/fortune]': `<%= html('') %>` + '[i]': `<%= html('') %>` + '[/i]': `<%= html('') %>` + '[red]': `<%= html('') %>` + '[/red]': `<%= html('') %>` + '[green]': `<%= html('') %>` + '[/green]': `<%= html('') %>` + '[blue]': `<%= html('') %>` + '[/blue]': `<%= html('') %>` diff --git a/src/classes/Notice.coffee b/src/classes/Notice.coffee index fea553941b..bbebab5f6c 100644 --- a/src/classes/Notice.coffee +++ b/src/classes/Notice.coffee @@ -1,7 +1,7 @@ class Notice constructor: (type, content, @timeout, @onclose) -> @el = $.el 'div', - <%= html('
              ') %> + `<%= html('
              ') %>` @el.style.opacity = 0 @setType type $.on @el.firstElementChild, 'click', @close @@ -30,5 +30,3 @@ class Notice $.off d, 'visibilitychange', @add $.rm @el @onclose?() - -return Notice diff --git a/src/classes/Post.Clone.coffee b/src/classes/Post.Clone.coffee index a3e788b6e5..efa3089b78 100644 --- a/src/classes/Post.Clone.coffee +++ b/src/classes/Post.Clone.coffee @@ -1,8 +1,13 @@ Post.Clone = class extends Post isClone: true - constructor: (@origin, @context, contractThumb) -> - for key in ['ID', 'fullID', 'board', 'thread', 'info', 'quotes', 'isReply'] + constructor: -> + that = Object.create(Post.Clone.prototype) + that.construct arguments... + return that + + construct: (@origin, @context, contractThumb) -> + for key in ['ID', 'postID', 'threadID', 'boardID', 'siteID', 'fullID', 'board', 'thread', 'info', 'quotes', 'isReply'] # Copy or point to the origin's key value. @[key] = @origin[key] @@ -11,45 +16,51 @@ Post.Clone = class extends Post @cloneWithoutVideo nodes.root else nodes.root.cloneNode true - Post.Clone.prefix or= 0 + Post.Clone.suffix or= 0 for node in [root, $$('[id]', root)...] - node.id = Post.Clone.prefix + node.id - Post.Clone.prefix++ - - @nodes = @parseNodes root + node.id += "_#{Post.Clone.suffix}" + Post.Clone.suffix++ # Remove inlined posts inside of this post. - for inline in $$ '.inline', @nodes.post + for inline in $$ '.inline', root $.rm inline - for inlined in $$ '.inlined', @nodes.post + for inlined in $$ '.inlined', root $.rmClass inlined, 'inlined' + @nodes = @parseNodes root + root.hidden = false # post hiding $.rmClass root, 'forwarded' # quote inlining $.rmClass @nodes.post, 'highlight' # keybind navigation, ID highlighting + # Remove catalog stuff. + unless @isReply + @setCatalogOP false + $.rm $('.catalog-link', @nodes.post) + $.rm $('.catalog-stats', @nodes.post) + $.rm $('.catalog-replies', @nodes.post) + @parseQuotes() @quotes = [@origin.quotes...] - if @origin.file + @files = [] + fileRoots = @fileRoots() if @origin.files.length + for originFile in @origin.files # Copy values, point to relevant elements. - # See comments in Post's constructor. - @file = {} - for key, val of @origin.file - @file[key] = val - file = $ '.file', @nodes.post - @file.text = file.firstElementChild - @file.link = $ '.fileText > a, .fileText-original', file - @file.thumb = $ '.fileThumb > [data-md5]', file - @file.fullImage = $ '.full-image', file - @file.videoControls = $ '.video-controls', @file.text - - @file.thumb.muted = true if @file.videoThumb + file = {} + for key, val of originFile + file[key] = val + fileRoot = fileRoots[file.docIndex] + for key, selector of g.SITE.selectors.file + file[key] = $ selector, fileRoot + file.thumbLink = file.thumb?.parentNode + file.fullImage = $ '.full-image', file.thumbLink if file.thumbLink + file.videoControls = $ '.video-controls', file.text + file.thumb.muted = true if file.videoThumb + @files.push file - if @file.thumb?.dataset.src - @file.thumb.src = @file.thumb.dataset.src - # XXX https://bugzilla.mozilla.org/show_bug.cgi?id=1021289 - @file.thumb.removeAttribute 'data-src' + if @files.length + @file = @files[0] # Contract thumbnails in quote preview ImageExpand.contract @ if @file.thumb and contractThumb diff --git a/src/classes/Post.coffee b/src/classes/Post.coffee index 7ac804a271..8551aa718a 100644 --- a/src/classes/Post.coffee +++ b/src/classes/Post.coffee @@ -1,79 +1,92 @@ class Post toString: -> @ID - constructor: (root, @thread, @board) -> + constructor: (root, @thread, @board, flags={}) -> <% if (readJSON('/.tests_enabled')) { %> - @normalizedOriginal = Build.Test.normalize root + @normalizedOriginal = Test.normalize root <% } %> - @ID = +root.id[2..] - @fullID = "#{@board}.#{@ID}" - @context = @ + $.extend @, flags + @ID = +root.id.match(/\d*$/)[0] + @postID = @ID + @threadID = @thread.ID + @boardID = @board.ID + @siteID = g.SITE.ID + @fullID = "#{@board}.#{@ID}" + @context = @ + @isReply = (@ID isnt @threadID) root.dataset.fullID = @fullID @nodes = @parseNodes root - unless (@isReply = $.hasClass @nodes.post, 'reply') + if not @isReply @thread.OP = @ - @thread.isArchived = !!$ '.archivedIcon', @nodes.info - @thread.isSticky = !!$ '.stickyIcon', @nodes.info - @thread.isClosed = @thread.isArchived or !!$ '.closedIcon', @nodes.info - @thread.kill() if @thread.isArchived + for key in ['isSticky', 'isClosed', 'isArchived'] + if (selector = g.SITE.selectors.icons[key]) + @thread[key] = !!$(selector, @nodes.info) + if @thread.isArchived + @thread.isClosed = true + @thread.kill() @info = - nameBlock: if Conf['Anonymize'] then 'Anonymous' else @nodes.nameBlock.textContent.trim() subject: @nodes.subject?.textContent or undefined name: @nodes.name?.textContent + email: if @nodes.email then decodeURIComponent(@nodes.email.href.replace(/^mailto:/, '')) tripcode: @nodes.tripcode?.textContent - uniqueID: @nodes.uniqueID?.firstElementChild.textContent + uniqueID: @nodes.uniqueID?.textContent capcode: @nodes.capcode?.textContent.replace '## ', '' + pass: @nodes.pass?.title.match(/\d*$/)[0] flagCode: @nodes.flag?.className.match(/flag-(\w+)/)?[1].toUpperCase() + flagCodeTroll: @nodes.flag?.className.match(/bfl-(\w+)/)?[1].toUpperCase() flag: @nodes.flag?.title - date: if @nodes.date then new Date(@nodes.date.dataset.utc * 1000) + date: if @nodes.date then g.SITE.parseDate(@nodes.date) + + if Conf['Anonymize'] + @info.nameBlock = 'Anonymous' + else + @info.nameBlock = "#{@info.name or ''} #{@info.tripcode or ''}".trim() + @info.nameBlock += " ## #{@info.capcode}" if @info.capcode + @info.nameBlock += " (ID: #{@info.uniqueID})" if @info.uniqueID @parseComment() @parseQuotes() - @parseFile() + @parseFiles() @isDead = false @isHidden = false @clones = [] <% if (readJSON('/.tests_enabled')) { %> - return if arguments[3] is 'forBuildTest' + return if @forBuildTest <% } %> - if g.posts[@fullID] + if g.posts.get(@fullID) @isRebuilt = true - @clones = g.posts[@fullID].clones + @clones = g.posts.get(@fullID).clones clone.origin = @ for clone in @clones + @thread.lastPost = @ID if !@isFetchedQuote and @ID > @thread.lastPost @board.posts.push @ID, @ @thread.posts.push @ID, @ g.posts.push @fullID, @ parseNodes: (root) -> - post = $ '.post', root - info = $ '.postInfo', post + s = g.SITE.selectors + post = $(s.post, root) or root + info = $ s.infoRoot, post nodes = root: root + bottom: if @isReply or !g.SITE.isOPContainerThread then root else $(s.opBottom, root) post: post info: info - subject: $ '.subject', info - name: $ '.name', info - email: $ '.useremail', info - tripcode: $ '.postertrip', info - uniqueID: $ '.posteruid', info - capcode: $ '.capcode.hand', info - flag: $ '.flag, .countryFlag', info - date: $ '.dateTime', info - nameBlock: $ '.nameBlock', info - quote: $ '.postNum > a:nth-of-type(2)', info - reply: $ '.replylink', info - comment: $ '.postMessage', post - links: [] + comment: $ s.comment, post quotelinks: [] archivelinks: [] + embedlinks: [] + for key, selector of s.info + nodes[key] = $ selector, info + g.SITE.parseNodes?(@, nodes) + nodes.uniqueIDRoot or= nodes.uniqueID # XXX Edge invalidates HTMLCollections when an ancestor node is inserted into another node. # https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7560353/ @@ -81,9 +94,9 @@ class Post Object.defineProperty nodes, 'backlinks', configurable: true enumerable: true - get: -> info.getElementsByClassName 'backlink' + get: -> post.getElementsByClassName 'backlink' else - nodes.backlinks = info.getElementsByClassName 'backlink' + nodes.backlinks = post.getElementsByClassName 'backlink' nodes @@ -96,29 +109,28 @@ class Post # Remove: # 'Comment too long'... # EXIF data. (/p/) - # Rolls. (/tg/) - # Fortunes. (/s4s/) - bq = @nodes.comment.cloneNode true - for node in $$ '.abbr + br, .exif, b, .fortune', bq - $.rm node - if abbr = $ '.abbr', bq - $.rm abbr + @nodes.commentClean = bq = @nodes.comment.cloneNode true + g.SITE.cleanComment?(bq) @info.comment = @nodesToText bq - if abbr - @info.comment = @info.comment.replace /\n\n$/, '' - # Hide spoilers. - # Remove: + commentDisplay: -> + # Get the comment's text for display purposes (e.g. notifications, excerpts). + # In addition to what's done in generating `@info.comment`, remove: + # Spoilers. (filter to '[spoiler]') + # Rolls. (/tg/, /qst/) + # Fortunes. (/s4s/) # Preceding and following new lines. # Trailing spaces. - commentDisplay = @info.comment - unless Conf['Remove Spoilers'] or Conf['Reveal Spoilers'] - spoilers = $$ 's', bq - if spoilers.length - for node in spoilers - $.replace node, $.tn '[spoiler]' - commentDisplay = @nodesToText bq - @info.commentDisplay = commentDisplay.trim().replace /\s+$/gm, '' + bq = @nodes.commentClean.cloneNode true + @cleanSpoilers bq unless Conf['Remove Spoilers'] or Conf['Reveal Spoilers'] + g.SITE.cleanCommentDisplay?(bq) + @nodesToText(bq).trim().replace(/\s+$/gm, '') + + commentOrig: -> + # Get the comment's text for reposting purposes. + bq = @nodes.commentClean.cloneNode true + g.SITE.insertTags?(bq) + @nodesToText bq nodesToText: (bq) -> text = "" @@ -128,11 +140,15 @@ class Post text += node.data or '\n' text + cleanSpoilers: (bq) -> + spoilers = $$ g.SITE.selectors.spoiler, bq + for node in spoilers + $.replace node, $.tn '[spoiler]' + return + parseQuotes: -> @quotes = [] - # XXX https://github.com/4chan/4chan-JS/issues/77 - # 4chan currently creates quote links inside [code] tags; ignore them - for quotelink in $$ ':not(pre) > .quotelink', @nodes.comment + for quotelink in $$ g.SITE.selectors.quotelink, @nodes.comment @parseQuote quotelink return @@ -143,13 +159,7 @@ class Post # - catalog links. (>>>/b/catalog or >>>/b/search) # - rules links. (>>>/a/rules) # - text-board quotelinks. (>>>/img/1234) - match = quotelink.href.match /// - ^https?://boards\.4chan\.org/+ - ([^/]+) # boardID - /+(?:res|thread)/+\d+(?:[/?][^#]*)?#p - (\d+) # postID - $ - /// + match = quotelink.href.match g.SITE.regexp.quotelink return unless match or (@isClone and quotelink.dataset.postID) # normal or resurrected quote @nodes.quotelinks.push quotelink @@ -157,39 +167,57 @@ class Post return if @isClone # ES6 Set when? - fullID = "#{match[1]}.#{match[2]}" + fullID = "#{match[1]}.#{match[3]}" @quotes.push fullID unless fullID in @quotes - parseFile: -> - return unless fileEl = $ '.file', @nodes.post - return unless link = $ '.fileText > a, .fileText-original > a', fileEl - return unless info = link.nextSibling?.textContent.match /\(([\d.]+ [KMG]?B).*\)/ - fileText = fileEl.firstElementChild - @file = - text: fileText - link: link - url: link.href - name: fileText.title or link.title or link.textContent - size: info[1] - isImage: /(jpg|png|gif)$/i.test link.href - isVideo: /webm$/i.test link.href - dimensions: info[0].match(/\d+x\d+/)?[0] - tag: info[0].match(/,[^,]*, ([a-z]+)\)/i)?[1] - size = +@file.size.match(/[\d.]+/)[0] - unit = ['B', 'KB', 'MB', 'GB'].indexOf @file.size.match(/\w+$/)[0] + parseFiles: -> + @files = [] + fileRoots = @fileRoots() + index = 0 + for fileRoot, docIndex in fileRoots + if (file = @parseFile fileRoot) + file.index = (index++) + file.docIndex = docIndex + @files.push file + if @files.length + @file = @files[0] + + fileRoots: -> + if g.SITE.selectors.multifile + roots = $$(g.SITE.selectors.multifile, @nodes.root) + return roots if roots.length + [@nodes.root] + + parseFile: (fileRoot) -> + file = {} + for key, selector of g.SITE.selectors.file + file[key] = $ selector, fileRoot + file.thumbLink = file.thumb?.parentNode + + return if not (file.text and file.link) + return if not g.SITE.parseFile @, file + + $.extend file, + url: file.link.href + isImage: $.isImage file.link.href + isVideo: $.isVideo file.link.href + size = +file.size.match(/[\d.]+/)[0] + unit = ['B', 'KB', 'MB', 'GB'].indexOf file.size.match(/\w+$/)[0] size *= 1024 while unit-- > 0 - @file.sizeInBytes = size - if (thumb = $ '.fileThumb > [data-md5]', fileEl) - $.extend @file, - thumb: thumb - thumbURL: if m = link.href.match(/\d+(?=\.\w+$)/) then "#{location.protocol}//i.4cdn.org/#{@board}/#{m[0]}s.jpg" - MD5: thumb.dataset.md5 - isSpoiler: $.hasClass thumb.parentNode, 'imgspoiler' - - kill: (file) -> + file.sizeInBytes = size + + file + + @deadMark = + # \u00A0 is nbsp + $.el 'span', + textContent: '\u00A0(Dead)' + className: 'qmark-dead' + + kill: (file, index=0) -> if file - return if @isDead or @file.isDead - @file.isDead = true + return if @isDead or @files[index].isDead + @files[index].isDead = true $.addClass @nodes.root, 'deleted-file' else return if @isDead @@ -197,7 +225,7 @@ class Post $.rmClass @nodes.root, 'deleted-file' $.addClass @nodes.root, 'deleted-post' - unless (strong = $ 'strong.warning', @nodes.info) + if not (strong = $ 'strong.warning', @nodes.info) strong = $.el 'strong', className: 'warning' $.after $('input', @nodes.info), strong @@ -205,13 +233,13 @@ class Post return if @isClone for clone in @clones - clone.kill file + clone.kill file, index return if file # Get quotelinks/backlinks to this post # and paint them (Dead). for quotelink in Get.allQuotelinksLinkingTo @ when not $.hasClass quotelink, 'deadlink' - quotelink.textContent = quotelink.textContent + '\u00A0(Dead)' + $.add quotelink, Post.deadMark.cloneNode(true) $.addClass quotelink, 'deadlink' return @@ -222,7 +250,8 @@ class Post $.rmClass @nodes.root, 'deleted-post' strong = $ 'strong.warning', @nodes.info # no false-positive files - if @file and @file.isDead + if @files.some((file) -> file.isDead) + $.addClass @nodes.root, 'deleted-file' strong.textContent = '[File deleted]' else $.rm strong @@ -232,7 +261,7 @@ class Post clone.resurrect() for quotelink in Get.allQuotelinksLinkingTo @ when $.hasClass quotelink, 'deadlink' - quotelink.textContent = quotelink.textContent.replace '\u00A0(Dead)', '' + $.rm $('.qmark-dead', quotelink) $.rmClass quotelink, 'deadlink' return @@ -242,6 +271,8 @@ class Post @board.posts.rm @ addClone: (context, contractThumb) -> + # Callbacks may not have been run yet due to anti-browser-lock delay in Main.callbackNodesDB. + Callbacks.Post.execute @ new Post.Clone @, context, contractThumb rmClone: (index) -> @@ -250,4 +281,9 @@ class Post clone.nodes.root.dataset.clone = index++ return -return Post + setCatalogOP: (isCatalogOP) -> + @nodes.root.classList.toggle 'catalog-container', isCatalogOP + @nodes.root.classList.toggle 'opContainer', !isCatalogOP + @nodes.post.classList.toggle 'catalog-post', isCatalogOP + @nodes.post.classList.toggle 'op', !isCatalogOP + @nodes.post.style.left = @nodes.post.style.right = null diff --git a/src/classes/RandomAccessList.coffee b/src/classes/RandomAccessList.coffee index 7143d6abae..b37fe246af 100644 --- a/src/classes/RandomAccessList.coffee +++ b/src/classes/RandomAccessList.coffee @@ -87,5 +87,3 @@ class RandomAccessList next.prev = prev else @last = prev - -return RandomAccessList diff --git a/src/classes/ShimSet.coffee b/src/classes/ShimSet.coffee new file mode 100644 index 0000000000..eb37fe33eb --- /dev/null +++ b/src/classes/ShimSet.coffee @@ -0,0 +1,16 @@ +class ShimSet + constructor: -> + @elements = $.dict() + @size = 0 + has: (value) -> + value of @elements + add: (value) -> + return if @elements[value] + @elements[value] = true + @size++ + delete: (value) -> + return unless @elements[value] + delete @elements[value] + @size-- + +window.Set = ShimSet unless 'Set' of window diff --git a/src/classes/SimpleDict.coffee b/src/classes/SimpleDict.coffee index f9c354e099..455ac7d097 100644 --- a/src/classes/SimpleDict.coffee +++ b/src/classes/SimpleDict.coffee @@ -17,4 +17,8 @@ class SimpleDict fn @[key] for key in [@keys...] return -return SimpleDict + get: (key) -> + if key is 'keys' + undefined + else + $.getOwn(@, key) diff --git a/src/classes/Thread.coffee b/src/classes/Thread.coffee index ec11ba58cb..90da7dbfc6 100644 --- a/src/classes/Thread.coffee +++ b/src/classes/Thread.coffee @@ -1,28 +1,36 @@ class Thread toString: -> @ID - constructor: (@ID, @board) -> + constructor: (ID, @board) -> + @ID = +ID + @threadID = @ID + @boardID = @board.ID + @siteID = g.SITE.ID @fullID = "#{@board}.#{@ID}" @posts = new SimpleDict() @isDead = false @isHidden = false - @isOnTop = false @isSticky = false @isClosed = false @isArchived = false @postLimit = false @fileLimit = false + @lastPost = 0 @ipCount = undefined + @json = null @OP = null @catalogView = null + @nodes = + root: null + @board.threads.push @ID, @ g.threads.push @fullID, @ setPage: (pageNum) -> {info, reply} = @OP.nodes - unless (icon = $ '.page-num', info) + if not (icon = $ '.page-num', info) icon = $.el 'span', className: 'page-num' $.replace reply.parentNode.previousSibling, [$.tn(' '), icon, $.tn(' ')] icon.title = "This thread is on page #{pageNum} in the original index." @@ -55,10 +63,12 @@ class Thread $.rm $ ".#{typeLC}Icon", @catalogView.nodes.icons if @catalogView return icon = $.el 'img', - src: "#{Build.staticPath}#{typeLC}#{Build.gifIcon}" + src: "#{g.SITE.Build.staticPath}#{typeLC}#{g.SITE.Build.gifIcon}" alt: type title: type className: "#{typeLC}Icon retina" + if g.BOARD.ID is 'f' + icon.style.cssText = 'height: 18px; width: 18px;' root = if type isnt 'Sticky' and @isSticky $ '.stickyIcon', @OP.nodes.info @@ -73,8 +83,12 @@ class Thread @isDead = true collect: -> - @posts.forEach (post) -> post.collect() - g.threads.rm @fullID - @board.threads.rm @ - -return Thread + n = 0 + @posts.forEach (post) -> + if post.clones.length + n++ + else + post.collect() + unless n + g.threads.rm @fullID + @board.threads.rm @ diff --git a/src/config/Config.coffee b/src/config/Config.coffee index bead6a5ce5..bbb8d7409b 100644 --- a/src/config/Config.coffee +++ b/src/config/Config.coffee @@ -1,6 +1,10 @@ Config = main: 'Miscellaneous': + 'Redirect to HTTPS': [ + true + 'Redirect to the HTTPS version of 4chan.' + ] 'JSON Index': [ true 'Replace the original board index with one supporting searching, sorting, infinite scrolling, and a catalog mode.' @@ -15,6 +19,10 @@ Config = 'Show a notice at the top of the page when the index is refreshed.' 1 ] + 'Follow Cursor': [ + true + 'Image Hover and Quote Preview move with the mouse cursor.' + ] 'Open Threads in New Tab': [ false 'Make links to threads in the index / <%= meta.name %> catalog open in a new tab.' @@ -39,6 +47,10 @@ Config = true 'Redirect dead threads and images to the archives.' ] + 'Archive Report': [ + true + 'Enable reporting posts to supported archives.' + ] 'Exempt Archives from Encryption': [ true 'Permit loading content from, and warningless redirects to, HTTP-only archives from HTTPS pages.' @@ -80,6 +92,10 @@ Config = false 'Add buttons to navigate to top / bottom of thread.' ] + 'Unique ID and Capcode Navigation': [ + false + 'Add buttons to navigate to posts having the same unique ID or capcode.' + ] 'Custom Board Titles': [ true 'Allow editing of the board title and subtitle by ctrl/\u2318+clicking them.' @@ -97,6 +113,10 @@ Config = true 'Assign unique colors to user IDs on boards that use them' ] + 'Count Posts by ID': [ + true + 'Display number of posts in the thread when hovering over an ID.' + ] 'Remove Spoilers': [ false 'Remove all spoilers in text.' @@ -109,6 +129,10 @@ Config = true 'Rewrite the URL of the current page, removing slugs and excess slashes, and changing /res/ to /thread/.' ] + 'Work around CORB Bug': [ + true + 'Leave this checked until your garbage browser is fixed.' + ] 'Disable Autoplaying Sounds': [ false 'Prevent sounds on the page from autoplaying.' @@ -132,6 +156,11 @@ Config = 'Replace the link of a supported site with its actual title.' 1 ] + 'Cover Preview': [ + true + 'Show preview of supported links on hover.' + 1 + ] 'Embedding': [ true 'Embed supported services. Note: Some services don\'t work on HTTPS.' @@ -162,6 +191,16 @@ Config = 'When enabled, shows backlinks to filtered posts with a line-through decoration. Otherwise, hides the backlinks.' 1 ] + 'Filter in Native Catalog': [ + true + 'Apply 4chan X filters in native catalog.' + 1 + ] + 'MD5 Quick Filter Notifications': [ + true + 'Show notification when quick filtering MD5s using the button or keybind.' + 1 + ] 'Recursive Hiding': [ true 'Hide replies of hidden posts, recursively.' @@ -189,12 +228,12 @@ Config = 'Show full image / video on mouseover.' ] 'Image Hover in Catalog': [ - false + true 'Show full image / video on mouseover in <%= meta.name %> catalog.' ] 'Gallery': [ true - 'Adds a simple and cute image gallery.' + 'Adds a simple and cute image gallery. Has more options in the gallery menu.' ] 'Fullscreen Gallery': [ false @@ -232,11 +271,11 @@ Config = ] 'Replace WEBM': [ false - 'Replace webm thumbnails with the actual webm video. Probably will degrade browser performance ;)' + 'Replace webm, mp4, and ogv thumbnails with the actual video. Probably will degrade browser performance ;)' ] 'Image Prefetching': [ - false - 'Add link in header menu to turn on image preloading.' + true + 'Add a shortcut icon to the header to turn on image preloading.' ] 'Fappe Tyme': [ true @@ -250,6 +289,10 @@ Config = true 'Videos begin playing immediately when opened.' ] + 'Restart when Opened': [ + false + 'Restart GIFs and WebMs when you hover over or expand them.' + ] 'Show Controls': [ true 'Show controls on videos expanded inline.' @@ -286,6 +329,11 @@ Config = 'Add a report link to the menu.' 1 ] + 'Copy Text Link': [ + true + 'Add a link to copy the post\'s text.' + 1 + ] 'Thread Hiding Link': [ true 'Add a link to hide entire threads.' @@ -312,7 +360,7 @@ Config = 1 ] 'Download Link': [ - true + false 'Add a download with original filename link to the menu.' 1 ] @@ -353,6 +401,15 @@ Config = 'Scroll back to the last read post when reopening a thread.' 1 ] + 'Unread Line in Index': [ + false + 'Show a line between read and unread posts in threads in the index.' + 1 + ] + 'Remove Thread Excerpt': [ + false + 'Replace the excerpt of the thread in the tab title with the board title.' + ] 'Thread Stats': [ true 'Display reply and image count.' @@ -373,16 +430,16 @@ Config = ] 'Thread Watcher': [ true - 'Bookmark threads.' + 'Bookmark threads. Has more options in the thread watcher menu.' ] 'Fixed Thread Watcher': [ true 'Makes the thread watcher scroll with the page.' 1 ] - 'Toggleable Thread Watcher': [ - true - 'Adds a shortcut for the thread watcher and hides the watcher by default.' + 'Persistent Thread Watcher': [ + false + 'The thread watcher will be visible when the page is loaded.' 1 ] 'Mark New IPs': [ @@ -391,7 +448,12 @@ Config = ] 'Reply Pruning': [ true - 'Hide old replies in long threads. Number of replies shown can be set from header menu.' + 'Add option in header menu to hide old replies in long threads. Activated by default in stickies.' + ] + 'Prune All Threads': [ + false + 'Activate Reply Pruning by default in all threads.' + 1 ] 'Posting and Captchas': @@ -407,6 +469,11 @@ Config = 'Auto Hide QR': [ true 'Automatically hide the quick reply when posting.' + 2 + ] + 'Open Post in New Tab': [ + true + 'Open new threads in a new tab, and open replies in a new tab if you\'re not already in the thread.' 1 ] 'Remember QR Size': [ @@ -454,21 +521,9 @@ Config = 'Submit the post immediately when the captcha is completed.' 1 ] - 'Captcha Fixes': [ - true - 'Make captcha easier to use, especially with the keyboard.' - ] - 'Use Recaptcha v1': [ - false - 'Use the old text version of Recaptcha in the post form.' - ] - 'Use Recaptcha v1 in Reports': [ - false - 'Use the text captcha in the report window.' - ] 'Force Noscript Captcha': [ false - 'Use the non-Javascript fallback captcha even if Javascript is enabled (Recaptcha v2 only).' + 'Use the non-Javascript fallback captcha even if Javascript is enabled.' ] 'Pass Link': [ false @@ -485,6 +540,11 @@ Config = 'Add backlinks to the OP.' 1 ] + 'Bottom Backlinks': [ + false + 'Place backlinks at the bottom of posts.' + 1 + ] 'Quote Inlining': [ true 'Inline quoted post on click.' @@ -574,11 +634,15 @@ Config = false 'Expand all images only from current position to thread end.' ] + 'Expand thread only': [ + false + 'In index, expand all images only within the current thread.' + ] 'Advance on contract': [ false 'Advance to next post when contracting an expanded image.' ] - + gallery: 'Hide Thumbnails': [ false @@ -611,23 +675,37 @@ Config = 'Periodically check status of watched threads.' ] 'Auto Watch': [ - false + true 'Automatically watch threads you start.' ] 'Auto Watch Reply': [ - false + true 'Automatically watch threads you reply to.' ] 'Auto Prune': [ false 'Automatically remove dead threads.' ] + 'Show Page': [ + true + 'Show what page watched threads are on.' + ] 'Show Unread Count': [ true 'Show number of unread posts in watched threads.' ] + 'Show Site Prefix': [ + true + 'When multiple sites are shown in the thread watcher, add a prefix to board names to distinguish them.' + ] + 'Require OP Quote Link': [ + false + 'For purposes of thread watcher highlighting, only consider posts with a quote link to the OP as replies to the OP.' + ] filter: + general: '' + postID: """ # Highlight dubs on [s4s]: #/(\\d)\\1$/;highlight;top:no;boards:s4s @@ -655,6 +733,13 @@ Config = #/Admin$/;highlight:admin;op:yes """ + pass: """ + # Filter anyone using since4pass: + #/./ + """ + + email: '' + subject: """ # Filter Generals on /v/: #/general/i;boards:v;op:only @@ -681,27 +766,36 @@ Config = MD5: '' sauces: """ + # Known filename formats: + https://www.pixiv.net/member_illust.php?mode=medium&illust_id=%$1;regexp:/^(\\d+)_p\\d+/ + javascript:void(open("https://www.deviantart.com/"+%$1.replace(/_/g,"-")+"/art/"+parseInt(%$2,36)));regexp:/^\\w+_by_(\\w+)[_-]d([\\da-z]{6})\\b/ + https://imgur.com/%$1;regexp:/^(?![a-zA-Z][a-z]{6})(?![A-Z]{7})(?!\\d{7})([\\da-zA-Z]{7})(?: \\(\\d+\\))?\\.\\w+$/ + https://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/ + # Reverse image search: - https://www.google.com/searchbyimage?image_url=%IMG&safe=off - #https://www.yandex.com/images/search?rpt=imageview&img_url=%IMG + https://www.google.com/searchbyimage?sbisrc=4chanx&image_url=%IMG&safe=off + https://yandex.com/images/search?rpt=imageview&url=%IMG #//tineye.com/search?url=%IMG + #//www.bing.com/images/search?q=imgurl:%IMG&view=detailv2&iss=sbi#enterInsights + #https://lens.google.com/uploadbyurl?url=%IMG;text:lens # Specialized reverse image search: //iqdb.org/?url=%IMG - https://whatanime.ga/?auto&url=%IMG;text:wait + https://trace.moe/?auto&url=%IMG;text:wait #//3d.iqdb.org/?url=%IMG #//saucenao.com/search.php?url=%IMG # "View Same" in archives: http://eye.swfchan.com/search/?q=%name;types:swf - #https://desustorage.org/_/search/image/%sMD5/ + #https://desuarchive.org/_/search/image/%sMD5/ #https://archive.4plebs.org/_/search/image/%sMD5/ #https://boards.fireden.net/_/search/image/%sMD5/ #https://foolz.fireden.net/_/search/image/%sMD5/ # Other tools: - #http://regex.info/exif.cgi?imgurl=%URL - #//imgops.com/%URL;types:gif,jpg,png + #http://exif.regex.info/exif.cgi?imgurl=%URL + #//imgops.com/start?url=%URL;types:gif,jpg,png #//www.gif-explode.com/%URL;types:gif """ @@ -714,10 +808,12 @@ Config = 'Index Mode': 'paged' 'Previous Index Mode': 'paged' 'Index Size': 'small' - 'Show Replies': true - 'Pin Watched Threads': false - 'Anchor Hidden Threads': true - 'Refreshed Navigation': false + 'Show Replies': [true, 'Show replies in the index, and also in the catalog if "Catalog hover expand" is checked.'] + 'Catalog Hover Expand': [false, 'Expand the comment and show more details when you hover over a thread in the catalog.'] + 'Catalog Hover Toggle': [true, 'Turn "Catalog hover expand" on and off by clicking in the catalog.'] + 'Pin Watched Threads': [false, 'Move watched threads to the start of the index.'] + 'Anchor Hidden Threads': [true, 'Move hidden threads to the end of the index.'] + 'Refreshed Navigation': [false, 'Refresh index when navigating through pages.'] Header: 'Fixed Header': true @@ -731,27 +827,20 @@ Config = 'Custom Board Navigation': true archives: - archiveLists: 'https://mayhemydg.github.io/archives.json/archives.json' + archiveLists: 'https://4chenz.github.io/archives.json/archives.json' lastarchivecheck: 0 archiveAutoUpdate: true + externalCatalogURLs: """ + //catalog.neet.tv/%board/;boards:4chan.org: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 + """ + boardnav: """ [ toggle-all ] - a-replace - c-replace - g-replace - k-replace - v-replace - vg-replace - vr-replace - ck-replace - co-replace - fit-replace - jp-replace - mu-replace - sp-replace - tv-replace - vp-replace + [current-index-text:"Index" + current-catalog-text:"Catalog" + current-expired-text:"Expired" + current-archive-text:"Archive"] [external-text:"FAQ","<%= meta.faq %>"] """ @@ -761,26 +850,18 @@ Config = """ sjisPreview: false - jsWhitelist: ''' - http://s.4cdn.org - https://s.4cdn.org - http://www.google.com - https://www.google.com - https://www.gstatic.com - http://cdn.mathjax.org - https://cdn.mathjax.org - 'self' - 'unsafe-inline' - 'unsafe-eval' - ''' + jsWhitelist: '' captchaLanguage: '' time: '%m/%d/%y(%a)%H:%M:%S' + timeLocale: '' backlink: '>>%id' - fileInfo: '%l (%p%s, %r%g)' + pastedname: 'file' + + fileInfo: '%l %d (%p%s, %r%g)' favicon: 'ferongr' @@ -836,6 +917,18 @@ Config = 'Alt+s' 'Toggle sage in options field.' ] + 'Toggle Cooldown': [ + 'Alt+Comma' + 'Toggle custom cooldown timer.' + ] + 'Post from URL': [ + 'Alt+l' + 'Post from URL.' + ] + 'Add new post': [ + 'Alt+n' + 'Add new post to the QR dump list.' + ] 'Submit QR': [ 'Ctrl+Enter' 'Submit post.' @@ -853,6 +946,18 @@ Config = 'Shift+r' 'Manually refresh thread watcher.' ] + 'Toggle thread watcher': [ + 't' + 'Toggle visibility of thread watcher.' + ] + 'Toggle threading': [ + 'Shift+t' + 'Toggle threading.' + ] + 'Mark thread read': [ + 'Ctrl+0' + 'Mark thread read from index (requires "Unread Line in Index").' + ] # Images 'Expand image': [ 'Shift+e' @@ -866,6 +971,18 @@ Config = 'g' 'Opens the gallery.' ] + 'Next Gallery Image': [ + 'Right', + 'Go to the next image in gallery mode.' + ] + 'Previous Gallery Image': [ + 'Left', + 'Go to the previous image in gallery mode.' + ] + 'Advance Gallery': [ + 'Enter', + 'Go to next image or, if Autoplay is off, play video.' + ], 'Pause': [ 'p' 'Pause/play videos in the gallery.' @@ -874,6 +991,18 @@ Config = 'Ctrl+Right' 'Toggle the gallery slideshow mode.' ] + 'Rotate image clockwise': [ + 'Shift+Right' + 'Rotate image clockwise in gallery.' + ] + 'Rotate image anticlockwise': [ + 'Shift+Left' + 'Rotate image anticlockwise in gallery.' + ] + 'Download Gallery Image': [ + 'Shift+j' + 'Download current image in gallery.' + ] 'fappeTyme': [ 'f' 'Toggle Fappe Tyme.' @@ -961,6 +1090,10 @@ Config = 'x' 'Hide thread.' ] + 'Quick Filter MD5': [ + '5' + 'Add the MD5 of the selected image to the filter list.' + ] 'Previous Post Quoting You': [ 'Alt+Up' 'Scroll to the previous post that quotes you.' @@ -1000,7 +1133,7 @@ Config = false 'Increase the intervals between updates on threads without new posts.' ] - 'Interval': 30 + 'Interval': 5 customCooldown: 0 customCooldownEnabled: true @@ -1011,4 +1144,21 @@ Config = 'Autohiding Scrollbar': false -return Config + position: + 'embedding.position': 'top: 50px; right: 0px;' + 'thread-stats.position': 'bottom: 0px; right: 0px;' + 'updater.position': 'bottom: 0px; left: 0px;' + 'thread-watcher.position': 'top: 50px; left: 0px;' + 'qr.position': 'top: 50px; right: 0px;' + + fourchanImageHost: 'i.4cdn.org' + + hiddenPSAList: [{}] + + knownBanners: '<%= readJSON("banners.json").join(",") %>' + + passMessageClosed: false + + 'Prerequest Captcha': false + + 'PSAseen': [[]] diff --git a/src/Miscellaneous/Banner/banners.json b/src/config/banners.json similarity index 100% rename from src/Miscellaneous/Banner/banners.json rename to src/config/banners.json diff --git a/src/css/CSS.js b/src/css/CSS.js index 2bb0209dfe..8d106a0892 100644 --- a/src/css/CSS.js +++ b/src/css/CSS.js @@ -2,7 +2,7 @@ var inc = require['style']; var faCSS = read('/node_modules/font-awesome/css/font-awesome.css'); var faWebFont = readBase64('/node_modules/font-awesome/fonts/fontawesome-webfont.woff'); - var mainCSS = ['font-awesome', 'style', 'yotsuba', 'yotsuba-b', 'futaba', 'burichan', 'tomorrow', 'photon'].map(x => read(`${x}.css`)).join(''); + var mainCSS = ['font-awesome', 'style', 'yotsuba', 'yotsuba-b', 'futaba', 'burichan', 'tomorrow', 'photon', 'spooky'].map(x => read(`${x}.css`)).join(''); var iconNames = files.filter(f => /^linkify\.[^.]+\.png$/.test(f)); var icons = iconNames.map(readBase64); %>CSS = { @@ -16,6 +16,22 @@ report: <%= multiline(read('report.css')) %>, www: -<%= multiline(read('www.css')) %> +<%= multiline(read('www.css')) %>, + +sub: function(css) { + var variables = { + site: g.SITE.selectors + }; + return css.replace(/\$[\w\$]+/g, function(name) { + var words = name.slice(1).split('$'); + var sel = variables; + for (var i = 0; i < words.length; i++) { + if (typeof sel !== 'object') return ':not(*)'; + sel = $.getOwn(sel, words[i]); + } + if (typeof sel !== 'string') return ':not(*)'; + return sel; + }); +} }; diff --git a/src/css/burichan.css b/src/css/burichan.css index 1317878756..065b0f92a3 100644 --- a/src/css/burichan.css +++ b/src/css/burichan.css @@ -30,6 +30,17 @@ background-color: #D6DAF0; } +/* Catalog */ +:root.burichan.catalog-hover-expand .catalog-container:hover > .post { + background-color: #D6DAF0; +} +:root.burichan.werkTyme .catalog-thread:not(:hover), +:root.burichan.werkTyme:not(.catalog-hover-expand) .catalog-thread, +:root.burichan.catalog-hover-expand .catalog-container:hover > .post, +:root.burichan.catalog-hover-expand .catalog-container:hover .catalog-reply { + border-color: #B7C5D9; +} + /* Quote */ :root.burichan .backlink.deadlink { color: #34345C !important; @@ -44,6 +55,11 @@ color: #D6DAF0; } +/* Anonymize */ +:root.burichan.anonymize $site$info$name::before { + font-size: 12pt; +} + /* QR */ .burichan #dump-list::-webkit-scrollbar-thumb { background-color: #D6DAF0; @@ -71,8 +87,13 @@ background: rgba(255, 255, 255, .33); } +/* Unread */ +:root.burichan .unread-mark-read { + background-color: rgba(214,218,240,0.5); +} + /* Thread Watcher */ -:root.burichan .replies-quoting-you > a, :root.burichan #watcher-link.disabled.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; } diff --git a/src/css/futaba.css b/src/css/futaba.css index d31d923d78..5fbe337c14 100644 --- a/src/css/futaba.css +++ b/src/css/futaba.css @@ -30,6 +30,17 @@ background-color: #F0E0D6; } +/* Catalog */ +:root.futaba.catalog-hover-expand .catalog-container:hover > .post { + background-color: #F0E0D6; +} +:root.futaba.werkTyme .catalog-thread:not(:hover), +:root.futaba.werkTyme:not(.catalog-hover-expand) .catalog-thread, +:root.futaba.catalog-hover-expand .catalog-container:hover > .post, +:root.futaba.catalog-hover-expand .catalog-container:hover .catalog-reply { + border-color: #D9BFB7; +} + /* Quote */ :root.futaba .backlink.deadlink { color: #00E !important; @@ -44,6 +55,11 @@ color: #F0E0D6; } +/* Anonymize */ +:root.futaba.anonymize $site$info$name::before { + font-size: 12pt; +} + /* QR */ .futaba #dump-list::-webkit-scrollbar-thumb { background-color: #F0E0D6; @@ -71,8 +87,13 @@ background: rgba(255, 255, 255, .33); } +/* Unread */ +:root.futaba .unread-mark-read { + background-color: rgba(240,224,214,0.5); +} + /* Thread Watcher */ -:root.futaba .replies-quoting-you > a, :root.futaba #watcher-link.disabled.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; } diff --git a/src/css/linkify.bitchute.png b/src/css/linkify.bitchute.png new file mode 100644 index 0000000000..c99f8cce3e Binary files /dev/null and b/src/css/linkify.bitchute.png differ diff --git a/src/css/linkify.peertube.png b/src/css/linkify.peertube.png new file mode 100644 index 0000000000..4fc79098bf Binary files /dev/null and b/src/css/linkify.peertube.png differ diff --git a/src/css/linkify.streamable.png b/src/css/linkify.streamable.png new file mode 100644 index 0000000000..c14819c269 Binary files /dev/null and b/src/css/linkify.streamable.png differ diff --git a/src/css/linkify.vidlii.png b/src/css/linkify.vidlii.png new file mode 100644 index 0000000000..a875fa4391 Binary files /dev/null and b/src/css/linkify.vidlii.png differ diff --git a/src/css/photon.css b/src/css/photon.css index 6941869617..4cb64bc663 100644 --- a/src/css/photon.css +++ b/src/css/photon.css @@ -8,6 +8,17 @@ border-color: #EA8; } +/* 4chan style fixes */ +:root.photon #arc-list tr:nth-of-type(odd) span.quote { + color: #C0E17A; +} +:root.photon.highlight-you .quotesYou$site$highlightable$reply { + border-left: 3px solid rgba(221, 0, 0, .8) !important; +} +:root.photon.highlight-own .yourPost$site$highlightable$reply { + border-left: 3px dashed rgba(221, 0, 0, .8) !important; +} + /* Header */ :root.photon #header-bar.dialog { background-color: rgba(221,221,221,0.98); @@ -31,14 +42,17 @@ } /* Catalog */ -:root.photon .catalog-code { - background-color: rgba(150, 150, 150, 0.2); +:root.photon.catalog-hover-expand .catalog-container:hover > .post { + background-color: #DDD; +} +:root.photon.werkTyme .catalog-thread:not(:hover), +:root.photon.werkTyme:not(.catalog-hover-expand) .catalog-thread, +:root.photon.catalog-hover-expand .catalog-container:hover > .post, +:root.photon.catalog-hover-expand .catalog-container:hover .catalog-reply { + border-color: #CCC; } /* Quote */ -:root.photon #arc-list tr:nth-of-type(odd) span.quote { - color: #C0E17A; -} :root.photon .backlink.deadlink { color: #F60 !important; } @@ -79,8 +93,13 @@ background: rgba(255, 255, 255, .33); } +/* Unread */ +:root.photon .unread-mark-read { + background-color: rgba(221,221,221,0.5); +} + /* Thread Watcher */ -:root.photon .replies-quoting-you > a, :root.photon #watcher-link.disabled.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; } diff --git a/src/css/report.css b/src/css/report.css index 30b1402c6d..e72ded8dd1 100644 --- a/src/css/report.css +++ b/src/css/report.css @@ -5,3 +5,24 @@ #captchaContainerAlt td:nth-child(2) { display: table-cell !important; } + +/* Archive reports */ +#archive-report { + padding: 3px; +} +#archive-report-enabled { + vertical-align: middle; +} +#archive-report > label { + display: block; +} +#archive-report-reason { + display: block; + width: 98%; +} +.archive-report-success { + color: green; +} +.archive-report-error { + color: red; +} \ No newline at end of file diff --git a/src/css/spooky.css b/src/css/spooky.css new file mode 100644 index 0000000000..7211d6a520 --- /dev/null +++ b/src/css/spooky.css @@ -0,0 +1,176 @@ +/* General */ +:root.spooky .dialog { + background-color: #171526; + border-color: #707070; +} +:root.spooky .field:focus, +:root.spooky .field.focus { + border-color: #98E; +} + +/* 4chan style fixes */ +:root.spooky #arc-list span.quote { + color: #634C2C; +} +:root.spooky.highlight-you .quotesYou$site$highlightable$reply { + border-left: 3px solid rgba(145, 182, 214, .8) !important; +} +:root.spooky.highlight-own .yourPost$site$highlightable$reply { + border-left: 3px dashed rgba(145, 182, 214, .8) !important; +} + +/* Header */ +:root.spooky #header-bar.dialog { + background-color: rgba(23,21,38,0.98); +} +:root.spooky:not(.fixed) #header-bar, :root.spooky #notifications { + font-size: 9pt; +} +:root.spooky #header-bar, :root.spooky #notifications { + color: #C49756; +} +:root.spooky #board-list a, :root.spooky #shortcuts a { + color: #FE9600; +} +:root.spooky.shortcut-icons .native-settings { + background-image: url('//s.4cdn.org/image/favicon-ws.ico'); +} + +/* Settings */ +:root.spooky #fourchanx-settings fieldset, :root.spooky .section-main div::before { + border-color: #707070; +} +:root.spooky .suboption-list > div:last-of-type { + background-color: #171526; +} + +/* Catalog */ +:root.spooky.catalog-hover-expand .catalog-container:hover > .post { + background-color: #171526; +} +:root.spooky.werkTyme .catalog-thread:not(:hover), +:root.spooky.werkTyme:not(.catalog-hover-expand) .catalog-thread, +:root.spooky.catalog-hover-expand .catalog-container:hover > .post, +:root.spooky.catalog-hover-expand .catalog-container:hover .catalog-reply { + border-color: #707070; +} + +/* Quote */ +:root.spooky .backlink.deadlink { + color: #FE9600 !important; +} +:root.spooky .inline { + border-color: #707070; + background-color: rgba(255, 255, 255, .14); +} + +/* Fappe and Werk Tyme */ +:root.spooky .indicator { + color: #171526; +} + +/* Highlighting */ +:root.spooky .qphl { + outline: 2px solid rgba(145, 182, 214, .8); +} +:root.spooky.highlight-you .quotesYou$site$highlightable$op, +:root.spooky.highlight-you .quotesYou$site$highlightable$reply { + border-left: 3px solid rgba(145, 182, 214, .8); +} +:root.spooky.highlight-own .yourPost$site$highlightable$op, +:root.spooky.highlight-own .yourPost$site$highlightable$reply { + border-left: 3px dashed rgba(145, 182, 214, .8); +} +:root.spooky .filter-highlight$site$highlightable$op, +:root.spooky .filter-highlight$site$highlightable$reply { + box-shadow: inset 5px 0 rgba(145, 182, 214, .5); +} +:root.spooky.highlight-own .yourPost > $site$sideArrows, +:root.spooky.highlight-you .quotesYou > $site$sideArrows, +:root.spooky .filter-highlight > $site$sideArrows { + color: rgb(155, 185, 210); +} + +/* QR */ +.spooky #dump-list::-webkit-scrollbar-thumb { + background-color: #171526; + border-color: #707070; +} +:root.spooky .qr-preview { + background-color: rgba(0, 0, 0, .15); +} +:root.spooky #qr .field { + background-color: rgb(26, 27, 29); + color: rgb(197,200,198); + border-color: rgb(40, 41, 42); +} +:root.spooky #qr .field:focus, +:root.spooky #qr .field.focus { + border-color: rgb(254, 150, 0) !important; + background-color: rgb(30,32,36); +} +:root.spooky .persona button { + background: linear-gradient(to bottom, #2E3035, #222427) no-repeat; + color: rgb(197,200,198); + border-color: rgb(40, 41, 42); + outline: none; +} +:root.spooky .persona button::-moz-focus-inner { + border: none; +} +:root.spooky .persona button:focus { + border-color: rgb(254, 150, 0); +} +:root.spooky #qr.sjis-preview #sjis-toggle, +:root.spooky #qr.tex-preview #tex-preview-button { + background: rgb(26, 27, 29); +} +:root.spooky #qr select, +:root.spooky #file-n-submit > input, +:root.spooky #qr-draw-button { + border-color: rgb(40, 41, 42); +} +:root.spooky #qr-filename { + color: rgb(197,200,198); +} + +:root.spooky .qr-link { + border-color: rgb(8, 6, 23) rgb(8, 6, 23) rgb(0, 0, 8); + background: linear-gradient(#262435, #171526) repeat scroll 0% 0% transparent; +} +:root.spooky .qr-link:hover { + background: #1A1829; +} + + +/* Menu */ +:root.spooky #menu { + color: #FE9600; +} +:root.spooky .entry { + font-size: 10pt; +} +:root.spooky .focused.entry { + background: rgba(255, 255, 255, .33); +} + +/* Unread */ +:root.spooky .unread-line { + border-color: rgb(197, 200, 198); + visibility: visible; + opacity: 1; +} +:root.spooky .unread-mark-read { + background-color: rgba(23,21,38,0.5); +} + +/* Thread Watcher */ +:root.spooky .replies-quoting-you > a, :root.spooky #watcher-link.replies-quoting-you, :root.spooky .last-page > a > .watcher-page { + color: #F00 !important; +} + +/* Watcher Favicon */ +:root.spooky .watch-thread-link +{ + background-image: url("data:image/svg+xml,"); +} diff --git a/src/css/style.css b/src/css/style.css index 0086ba717d..cbe3c1f62c 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -21,13 +21,11 @@ font: 13px sans-serif; outline: none; transition: color .25s, border-color .25s; - transition: color .25s, border-color .25s; } -.field::-moz-placeholder, -.field:hover::-moz-placeholder { - color: #AAA !important; - font-size: 13px !important; - opacity: 1.0 !important; +.field::-moz-placeholder { + color: #AAA; + font-size: 13px; + opacity: 1; } .captch-img:hover, .field:hover { @@ -56,10 +54,10 @@ a[href="javascript:;"] { .warning { color: red; } -#boardNavDesktop, #boardNavMobile { +:root.sw-yotsuba #boardNavDesktop, :root.sw-yotsuba #boardNavMobile { display: none !important; } -:root.hide-bottom-board-list #boardNavDesktopFoot { +:root.hide-bottom-board-list $site$boardListBottom { display: none; } body.hasDropDownNav{ @@ -72,64 +70,142 @@ body.hasDropDownNav{ border-radius: 3px; padding: 0px 2px; } +[hidden] { + display: none !important; +} /* 4chan style fixes */ -.opContainer, .op { - display: block !important; - overflow: visible !important; +/* overrides 4chan CSS on div.opContainer, div.op */ +:root.sw-yotsuba .opContainer, :root.sw-yotsuba .op { + display: block; + overflow: visible; } -.reply > .file > .fileText { +:root.sw-yotsuba .reply > .file > .fileText { margin: 0 20px; } -.hashlink::before { - content: ' '; - visibility: hidden; -} -.inline + .hashlink, -[hidden] { - display: none !important; +:root.sw-yotsuba #arc-list span.quote { + color: #789922; } -.fileText a { +:root.sw-yotsuba .fileText a { unicode-bidi: -moz-isolate; unicode-bidi: -webkit-isolate; } -#g-recaptcha { +:root.sw-yotsuba #g-recaptcha { min-height: 78px; height: auto; } -:root:not(.js-enabled) #postForm { +:root.sw-yotsuba:not(.js-enabled) #postForm { display: table; } -#captchaContainerAlt td:nth-child(2) { +:root.sw-yotsuba #captchaContainerAlt td:nth-child(2) { display: table-cell !important; } -canvas#tegaki-canvas { +:root.sw-yotsuba canvas#tegaki-canvas { background: none; } /* Disable obnoxious captcha fade-in. */ -body > div:last-of-type { +:root.sw-yotsuba > body > div:last-of-type { transition: none !important; } /* Fix captcha scrolling to top of page. */ -body > div[style*=" top: -10000px;"] { +:root.sw-yotsuba > body > div[style*=" top: -10000px;"] { visibility: hidden !important; } +/* Make long filenames wrap properly: https://github.com/ccd0/4chan-x/issues/1082 */ +:root.sw-yotsuba .post > .file { + /* currently nonstandard but may be added: https://lists.w3.org/Archives/Public/www-style/2016Mar/0352.html, https://bugzilla.mozilla.org/show_bug.cgi?id=1296042 */ + word-break: break-word; +} +:root.sw-yotsuba:not(.ua-webkit):not(.ua-blink) .fileText { + word-wrap: break-word; + max-width: calc(100vw - 90px); +} +:root.sw-yotsuba > body.is_catalog .thread > a > img { + display: inline-block; +} +/* Links to NSFW boards */ +:root.sw-yotsuba .nwsb { + display: inline; +} +:root.sw-yotsuba .fileText { + max-width: auto; + white-space: normal; +} /* Ads */ -:root:not(.ads-loaded) .ad-cnt, -:root:not(.ads-loaded) .ad-plea, -:root:not(.ads-loaded) hr.abovePostForm, -:root:not(.ads-loaded) .ad-plea-bottom + hr { +:root.sw-yotsuba .ad-cnt > *, :root.sw-yotsuba .adg-rects > *, :root.sw-yotsuba .bsa-cnt { + height: auto !important; +} +:root.sw-yotsuba:not(.ads-loaded) hr.abovePostForm, +:root.sw-yotsuba:not(.ads-loaded) .adg-rects > hr, +:root.sw-yotsuba #adg-ol + hr, +:root.sw-yotsuba .danbo-slot:empty { display: none; } -hr + div.center:not(.ad-cnt):not(.topad):not(.middlead):not(.bottomad) { +:root.sw-yotsuba .adg-rects { + margin: 0; + font-size: 0; +} +:root.sw-yotsuba div.center[style] { display: none !important; } +/* Tinyboard / vichan conflicts */ +#menu > .hide-thread-link { + width: auto; + height: auto; + overflow: visible; + background-image: none; +} +#menu label.entry { + display: block; +} +#fourchanx-settings label { + display: inline; +} +.intro a[href="javascript:;"], +#menu a { + margin: 0; +} +.gal-buttons.gal-buttons a { + font-size: inherit; +} +:root.sw-tinyboard.fixed.top-header:not(.autohide) .boardlist, +:root.sw-tinyboard.fixed.top-header:not(.autohide) .bar.top { + position: static; +} +:root.sw-tinyboard.fixed.top-header:not(.autohide) div.pages.top { + top: auto; + bottom: 0; +} +:root.sw-tinyboard.fixed.top-header.autohide .boardlist, +:root.sw-tinyboard.fixed.top-header.autohide .bar.top { + z-index: 3; +} + +/* Tinyboard site style conflicts */ +:root[data-host="fufufu.moe"].fixed.top-header:not(.autohide) div.pages.top { + top: 26px; + bottom: auto; +} +:root[data-host="merorin.com"].fixed.top-header:not(.autohide) span.settings { + top: 26px; +} +:root[data-host="fufufu.moe"]:not(.fixed) #header-bar { + margin-top: 38px; +} +:root[data-host="lainchan.org"]:not(.fixed) #header-bar { + margin-top: 17px; +} +:root[data-host="smuglo.li"]:not(.fixed) #header-bar { + margin-top: 8px; +} + /* Anti-autoplay */ audio.controls-added { display: block; margin: auto; + white-space: normal; } :root.anti-autoplay div.embed { position: static; @@ -138,15 +214,13 @@ audio.controls-added { text-align: center; } :root.anti-autoplay .autoplay-removed { - display: block !important; visibility: visible !important; min-width: 640px; - min-height: 390px; + min-height: 360px; } /* fixed, z-index */ #overlay, -#fourchanx-settings, #qp, #ihover, #navlinks, .fixed #header-bar, :root.float #updater, @@ -154,11 +228,8 @@ audio.controls-added { #qr { position: fixed; } -#fourchanx-settings { - z-index: 999; -} #overlay { - z-index: 900; + z-index: 999; } #qp, #ihover { z-index: 60; @@ -326,56 +397,57 @@ audio.controls-added { #toggleMsgBtn { display: none !important; } -.current { +.current, +:root.sw-yotsuba div#boardNavDesktopFoot a.current { font-weight: bold; } @media (min-width: 1300px) { - :root.fixed:not(.centered-links) #header-bar { + :root.sw-yotsuba.fixed:not(.centered-links) #header-bar { white-space: nowrap; display: -webkit-flex; display: flex; -webkit-align-items: center; align-items: center; } - :root.fixed:not(.centered-links) #board-list { + :root.sw-yotsuba.fixed:not(.centered-links) #board-list { -webkit-flex: auto; flex: auto; } - :root.fixed:not(.centered-links) #full-board-list { + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list { display: -webkit-flex; display: flex; } - :root.fixed:not(.centered-links) .hide-board-list-container { + :root.sw-yotsuba.fixed:not(.centered-links) .hide-board-list-container { -webkit-flex: none; flex: none; margin-right: 5px; } - :root.fixed:not(.centered-links) #full-board-list > .boardList { + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList { -webkit-flex: auto; flex: auto; display: -webkit-flex; display: flex; width: 0px; /* XXX Fixes Edge not shrinking the board list below default size when needed */ } - :root.fixed:not(.centered-links) #full-board-list > .boardList > a, - :root.fixed:not(.centered-links) #full-board-list > .boardList > span:not(.space):not(.spacer) { + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > a, + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > span:not(.space):not(.spacer) { -webkit-flex: none; flex: none; padding: .17em; margin: -.17em -.32em; } - :root.fixed:not(.centered-links) #full-board-list > .boardList > span { + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > span { pointer-events: none; } - :root.fixed:not(.centered-links) #full-board-list > .boardList > span.space { + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > span.space { -webkit-flex: 0 .63 .63em; flex: 0 .63 .63em; } - :root.fixed:not(.centered-links) #full-board-list > .boardList > span.spacer { + :root.sw-yotsuba.fixed:not(.centered-links) #full-board-list > .boardList > span.spacer { -webkit-flex: 0 .38 .38em; flex: 0 .38 .38em; } - :root.fixed:not(.centered-links) #shortcuts { + :root.sw-yotsuba.fixed:not(.centered-links) #shortcuts { float: initial; -webkit-flex: none; flex: none; @@ -402,6 +474,9 @@ audio.controls-added { left: 0; visibility: visible; } +#notifications:empty { + display: none; +} :root.fixed.top-header:not(.gallery-open) #header-bar #notifications, :root.fixed.top-header #header-bar.autohide #notifications { position: absolute; @@ -466,6 +541,8 @@ audio.controls-added { } #overlay { background-color: rgba(0, 0, 0, .5); + display: -webkit-flex; + display: flex; top: 0; left: 0; height: 100%; @@ -480,16 +557,16 @@ audio.controls-added { width: 900px; max-width: 100%; margin: auto; - padding: 3px; - top: 50%; - left: 50%; - -moz-transform: translate(-50%, -50%); - -webkit-transform: translate(-50%, -50%); - transform: translate(-50%, -50%); + padding: 5px; + display: -webkit-flex; + display: flex; + -webkit-flex-direction: column; + flex-direction: column; } #fourchanx-settings > nav { - padding: 2px 2px 0; - height: 15px; + padding: 2px 2px 8px; + display: -webkit-flex; + display: flex; } #fourchanx-settings > nav a { text-decoration: underline; @@ -500,20 +577,16 @@ audio.controls-added { margin: 0; } .section-container { + -webkit-flex: 1; + flex: 1; + position: relative; overflow: auto; - position: absolute; - top: 2.1em; - right: 5px; - bottom: 5px; - left: 5px; padding-right: 5px; + overscroll-behavior: contain; } .sections-list { - padding: 0 3px; - float: left; -} -.credits { - float: right; + -webkit-flex: 1; + flex: 1; } .export, .import, .reset { cursor: pointer; @@ -580,6 +653,9 @@ div[data-checked="false"] > .suboption-list { border-left: 1px solid; border-bottom: 1px solid; } +#fourchanx-settings .section-main p { + margin: .5em 0 0; +} .section-filter ul { padding: 0; } @@ -593,8 +669,20 @@ div[data-checked="false"] > .suboption-list { .section-main a, .section-filter a, .section-advanced a { text-decoration: underline; } +#sauce-doc-expand:not(:checked) ~ #sauce-doc { + max-height: 130px; + overflow: auto; +} +#sauce-doc > label { + float: right; + margin: 0 5px; +} +/* XXX for OneeChan */ +#sauce-doc-expand + .riceCheck { + display: none; +} .section-sauce textarea { - height: 350px; + height: 430px; } .section-advanced .field[name="boardnav"] { width: 100%; @@ -602,7 +690,9 @@ div[data-checked="false"] > .suboption-list { .section-advanced textarea { height: 150px; } -.section-advanced textarea[name="archiveLists"] { +.section-advanced textarea[name="archiveLists"], +.section-advanced textarea[name="externalCatalogURLs"], +.section-advanced textarea[name="knownBanners"] { height: 75px; } .section-advanced .archive-cell { @@ -621,6 +711,12 @@ div[data-checked="false"] > .suboption-list { font-style: normal; font-size: 11px; } +.favicon-preview > img { + vertical-align: middle; +} +.favicon-preview > img:nth-of-type(3n+1) { + margin-left: 4px; +} .section-keybinds .field { font-family: monospace; } @@ -636,8 +732,8 @@ div[data-checked="false"] > .suboption-list { } #fourchanx-settings textarea { font-family: monospace; - min-width: 100%; - max-width: 100%; + width: 100%; + resize: vertical; } #fourchanx-settings code { color: #000; @@ -651,8 +747,8 @@ div[data-checked="false"] > .suboption-list { #fourchanx-settings p { margin: 1em 0px; } -.unscroll { - overflow: hidden; +#fourchanx-settings table { + margin: auto; } /* Index */ @@ -691,9 +787,26 @@ div[data-checked="false"] > .suboption-list { #index-search:not([data-searching]) + #index-search-clear { display: none; } -#index-mode, #index-sort, #index-size { +#index-options { float: right; } +#lastlong-options { + display: inline-block; + vertical-align: middle; + height: 28px; + margin: -14px 0; +} +#lastlong-options > input { + padding: 0; + border: 0 !important; + text-align: center; + background: transparent; + display: block; + font-size: 12px; + height: 12px; + width: 30px; + margin: 1px 0; +} .summary { text-decoration: none; } @@ -703,34 +816,78 @@ div[data-checked="false"] > .suboption-list { text-align: center; } .catalog-thread { - display: -webkit-inline-flex; - display: inline-flex; - text-align: left; - -webkit-flex-direction: column; - flex-direction: column; - -webkit-align-items: center; - align-items: center; - margin: 0 2px 5px; + display: inline-block; + -moz-box-sizing: border-box; + box-sizing: border-box; + border: 1px solid transparent; word-wrap: break-word; vertical-align: top; position: relative; } -.catalog-thread > a { - flex-shrink: 0; - -webkit-flex-shrink: 0; - position: relative; +/* overrides 4chan CSS on div.thread */ +.catalog-thread.catalog-thread { + margin: 2px; } -.catalog-small .catalog-thread { +.catalog-small > .catalog-thread { width: 165px; - max-height: 320px; + height: 320px; } -.catalog-large .catalog-thread { +.catalog-large > .catalog-thread { width: 270px; - max-height: 410px; + height: 410px; +} +:root.catalog-hover-expand .catalog-thread:hover { + z-index: 1; +} +.catalog-container { + position: absolute; + top: -4px; + left: 0; + right: 0; + bottom: 0; +} +.catalog-container:not(:hover), +:root:not(.catalog-hover-expand) .catalog-container { + overflow: hidden; +} +.catalog-post { + position: absolute; + top: 4px; + left: 0; + right: 0; + border: 1px solid transparent; + padding-top: 20px; +} +/* overrides inline CSS from Index.cb.hoverAdjust */ +:root:not(.catalog-hover-expand) .catalog-post { + left: 0 !important; + right: 0 !important; +} +/* overrides 4chan CSS on div.post */ +.catalog-post.catalog-post { + margin: -21px -1px -1px; + overflow: visible; +} +.catalog-thread.noFile > * > .catalog-post { + margin-top: -7px; + padding-top: 6px; +} +:root.catalog-hover-expand .catalog-container:hover > .catalog-post { + margin-left: -61px; + margin-right: -61px; +} +:root.catalog-hover-expand .catalog-container:hover > * > :not(.catalog-replies) { + padding-left: 2px; + padding-right: 2px; +} +.catalog-link { + display: block; + position: relative; } .catalog-thumb { border-radius: 2px; box-shadow: 0 0 5px rgba(0, 0, 0, .25); + vertical-align: top; } .catalog-thumb.spoiler-file { width: 100px; @@ -755,45 +912,144 @@ div[data-checked="false"] > .suboption-list { padding-left: 2px; } .catalog-stats > .menu-button { - text-align: center; font-weight: normal; } .catalog-stats > .menu-button > i::before { line-height: 11px; } .catalog-stats { - -webkit-flex-shrink: 0; - flex-shrink: 0; - cursor: help; font-size: 10px; font-weight: 700; - margin-top: 2px; + padding-top: 2px; } -.catalog-thread > .subject { - -webkit-flex-shrink: 0; - flex-shrink: 0; - -webkit-align-self: stretch; - align-self: stretch; - font-weight: 700; - line-height: 1; - text-align: center; +.catalog-stats > [title] { + cursor: help; } -.catalog-thread > .comment { - -webkit-flex-shrink: 1; - flex-shrink: 1; - -webkit-align-self: stretch; - align-self: stretch; +.catalog-post > .postMessage { + margin: 0; + padding-bottom: .3em; +} +.catalog-container:not(:hover) > * > .file, +.catalog-container:not(:hover) > * > .postInfo > :not(.subject), +.catalog-container:not(:hover) > * > .catalog-replies, +.catalog-container:not(:hover) .extra-linebreak, +.catalog-container:not(:hover) .abbr, +:root:not(.catalog-hover-expand) .catalog-container > * > .file, +:root:not(.catalog-hover-expand) .catalog-container > * > .postInfo > :not(.subject), +:root:not(.catalog-hover-expand) .catalog-container > * > .catalog-replies, +:root:not(.catalog-hover-expand) .catalog-container .extra-linebreak, +:root:not(.catalog-hover-expand) .catalog-container .abbr, +.catalog-thread > .catalog-container > :not(.catalog-post), +.catalog-post > .file > :not(.fileText), +.catalog-post > * > .fileText > :not(:first-child), +.catalog-post > .postInfo > :not(.subject):not(.nameBlock):not(.dateTime), +.catalog-post > .postInfo > .nameBlock > .contact-links, +.catalog-post > * > * > .posteruid, +.catalog-post > * > * > .postJumper, +:root.bottom-backlinks .catalog-post > .container, +.post:not(.catalog-post) > .catalog-link, +.post:not(.catalog-post) > .catalog-stats, +.post:not(.catalog-post) > .catalog-replies { + display: none; +} +.catalog-post > .file { + position: absolute; + left: 0; + right: 0; + top: 0; + min-height: 20px; + background-color: inherit; +} +.catalog-post > * > .fileText { + position: relative; + padding: 2px; + background-color: inherit; +} +.catalog-small .catalog-post > * .fileText { + font-size: 10px; +} +.catalog-post > * > .fileText:not(:hover) { + white-space: nowrap; overflow: hidden; - text-align: center; + text-overflow: ellipsis; } -/* /tg/ dice rolls */ -.board_tg .catalog-thread > .comment > b { - font-weight: normal; +.catalog-post > * > .fileText:hover { + z-index: 1; } -.catalog-code { - background-color: #FFF; +/* overrides 4chan CSS on div.post div.postInfo */ +.catalog-post > .postInfo.postInfo { + width: auto; +} +.catalog-post > * > .subject { + display: block; +} +.catalog-post > * > .dateTime { display: inline-block; + font-style: italic; +} +:root.catalog-hover-expand .catalog-container:hover > * > * > .nameBlock, +:root.catalog-hover-expand .catalog-container:hover > * > * > .dateTime, +:root.catalog-hover-expand .catalog-container:hover > * > .postMessage:not(:empty) { + padding-top: .3em; +} +.catalog-post .extra-linebreak { + content: ''; /* makes this work in Blink/WebKit */ + display: block; + margin-top: .3em; +} +.catalog-reply { + text-align: left; + white-space: nowrap; + border-top: 1px solid transparent; + display: -webkit-flex; + display: flex; + -webkit-flex-direction: row; + flex-direction: row; + -webkit-align-items: stretch; + align-items: stretch; +} +.catalog-reply > * { + padding: 3px; + overflow: hidden; + -webkit-flex: none; + flex: none; +} +.catalog-reply > span { + font-style: italic; + font-weight: bold; +} +.catalog-reply-excerpt { + -webkit-flex: 1 1 auto; + flex: 1 1 auto; +} +.catalog-post .prettyprinted { max-width: 100%; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.catalog-post .MathJax_Display { + text-align: center !important; +} +.catalog-container:not(:hover) .exif, +:root:not(.catalog-hover-expand) .catalog-container .exif { + display: none !important; +} +.catalog-post > * > .exif { + border-collapse: collapse; +} +:root.catalog-hover-expand .catalog-container:hover .exif[style*="display: block;"] { + display: inline-block !important; +} +.catalog-post > * > .exif, +.catalog-post > * > .exif > tbody { + background-color: inherit; +} +.catalog-post > * > .exif, +.catalog-post > * > .exif td { + min-width: 0; +} +.catalog-post > * > .exif td { + padding-top: 1px; } :root.hats-enabled .catalog-thread::after { content: ''; @@ -801,39 +1057,59 @@ div[data-checked="false"] > .suboption-list { position: absolute; background-size: contain; } -:root.hats-enabled .catalog-small .catalog-thread::after { - left: -10px; - top: -65px; - width: 100px; - height: 100px; +:root.hats-enabled .catalog-small > .catalog-thread::after { + left: -8px; + top: -59px; + width: 96px; + height: 96px; } -:root.hats-enabled .catalog-large .catalog-thread::after { +:root.hats-enabled:not(.werkTyme) .catalog-small > .catalog-thread:not(.noFile)::after { + left: calc(67px - .3px * var(--tn-w)); +} +:root.hats-enabled .catalog-large > .catalog-thread::after { left: -15px; - top: -105px; + top: -98px; width: 160px; height: 160px; } +:root.hats-enabled:not(.werkTyme) .catalog-large > .catalog-thread:not(.noFile)::after { + left: calc(110px - .5px * var(--tn-w)); +} + +/* Copy Text Link's textarea element */ +textarea.copy-text-element { + height: 0; + width: 0; + position: absolute; + top: -10000px; +} /* Announcement Hiding */ -:root.hide-announcement #globalMessage { +:root.hide-announcement $site$psa { display: none; } -span.hide-announcement { - font-size: 11px; - position: relative; - bottom: 5px; -} -.globalMessage, h2, h3 { - color: inherit !important; - font-size: 13px; - font-weight: 100; +.hide-announcement-button { + opacity: 0.4; + float: left; } /* Unread */ -#unread-line { +.unread-line { margin: 0; border-color: rgb(255,0,0); } +.unread-line + br { + display: none; +} +.unread-mark-read { + float: right; + clear: both; + width: 100%; + text-align: right; +} +:not(.unread-thread) > .unread-mark-read { + display: none; +} /* Thread Updater */ #updater { @@ -843,10 +1119,11 @@ span.hide-announcement { } #updater > .move { position: absolute; - left: 0; top: -5px; - width: 100%; - height: 5px; + bottom: -5px; + left: -5px; + right: -5px; + z-index: -1; } #updater > div:last-child { text-align: center; @@ -915,12 +1192,11 @@ span.hide-announcement { -webkit-flex-direction: row; flex-direction: row; } +#watched-threads .watcher-page, #watched-threads .watcher-unread { -webkit-flex: 0 0 auto; flex: 0 0 auto; -} -#watched-threads .watcher-unread::after { - content: "\00a0"; + margin-right: 2px; } #watched-threads .watcher-title { overflow: hidden; @@ -928,12 +1204,15 @@ span.hide-announcement { -webkit-flex: 0 1 auto; flex: 0 1 auto; } +#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; +} #thread-watcher a { text-decoration: none; } -:root:not(.toggleable-watcher) #thread-watcher .move > .close { - display: none; -} #thread-watcher .move > .close { position: absolute; right: 0px; @@ -973,17 +1252,24 @@ span.hide-announcement { } /* Quote */ -.catalog-thread > .comment > span.quote, #arc-list span.quote { - color: #789922; +.hashlink::before { + content: ' '; + visibility: hidden; +} +.inline + .hashlink { + display: none !important; } -:root:not(.catalog-mode) .deadlink { +:root.resurrect-quotes .deadlink { text-decoration: none !important; } +.catalog-post .qmark-ct { + display: none; +} .backlink.deadlink:not(.forwardlink), .quotelink.deadlink:not(.forwardlink) { text-decoration: underline !important; } -.inlined { +:root:not(.catalog-mode) .inlined { opacity: .5; } #qp input, .forwarded { @@ -1004,11 +1290,25 @@ span.hide-announcement { .postNum + .container::before { content: " "; } +:root.bottom-backlinks .container { + display: block; + clear: both; + margin: 0 4px; +} +:root.bottom-backlinks .backlink { + font-size: 90%; +} .inline { border: 1px solid; display: table; margin: 2px 0; } +.container ~ .inline { + margin-left: 20px; +} +:root.catalog-mode .inline { + display: none; +} .inline .post { border: 0 !important; background-color: transparent !important; @@ -1048,7 +1348,7 @@ span.hide-announcement { .expanded-image > .post > .file > .fileThumb > img[data-md5] { display: none; } -.full-image { +.full-image[data-file-i-d] { display: none; cursor: pointer; } @@ -1078,6 +1378,13 @@ span.hide-announcement { .fileThumb > .warning { 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-title > a::before { content: "title"; @@ -1110,22 +1417,29 @@ input[name="Default Volume"] { margin: 0px; } /* Fappe and Werk Tyme */ -:root.fappeTyme .thread > .noFile, -:root.fappeTyme .threadContainer > .noFile { +:root.fappeTyme $site$replyOriginal.noFile, +:root.fappeTyme $site$replyOriginal.noFile + br { display: none; } -:root.werkTyme .postContainer:not(.noFile) .fileThumb, +:root.werkTyme $site$thumbLink, +:root.werkTyme $site$file$thumb, :root.werkTyme .catalog-thumb:not(.deleted-file):not(.no-file), :root:not(.werkTyme) .werkTyme-filename { display: none; } .werkTyme-filename { font-weight: bold; + font-size: 110%; } -:root.werkTyme .catalog-thread > a { +:root.werkTyme .catalog-link { + box-shadow: 0 0 5px rgba(0, 0, 0, .25); + padding: 8px; text-align: center; - -webkit-align-self: stretch; - align-self: stretch; +} +:root.werkTyme .catalog-thumb { + box-shadow: none; + padding: 0; + vertical-align: middle; } .indicator { background: rgba(255,0,0,0.8); @@ -1158,42 +1472,47 @@ input[name="Default Volume"] { .qphl { outline: 2px solid rgba(216, 94, 49, .8); } -:root.highlight-you .quotesYou.opContainer, -:root.highlight-you .quotesYou > .reply { +:root.highlight-you .quotesYou$site$highlightable$op, +:root.highlight-you .quotesYou$site$highlightable$reply { border-left: 3px solid rgba(221, 0, 0, .8); } -:root.highlight-own .yourPost.opContainer, -:root.highlight-own .yourPost > .reply { +:root.highlight-own .yourPost$site$highlightable$op, +:root.highlight-own .yourPost$site$highlightable$reply { border-left: 3px dashed rgba(221, 0, 0, .8); } -.filter-highlight.opContainer, -.filter-highlight > .reply { +.filter-highlight$site$highlightable$op, +.filter-highlight$site$highlightable$reply { box-shadow: inset 5px 0 rgba(221, 0, 0, .5); } -:root.highlight-own .yourPost > div.sideArrows, -:root.highlight-you .quotesYou > div.sideArrows, -.filter-highlight > div.sideArrows { +:root.highlight-own .yourPost > $site$sideArrows, +:root.highlight-you .quotesYou > $site$sideArrows, +.filter-highlight > $site$sideArrows { color: rgba(221, 0, 0, .8); } -:root.highlight-own .yourPost.opContainer::after, -:root.highlight-you .quotesYou.opContainer::after, -.filter-highlight.opContainer::after { +:root.highlight-own .yourPost$site$highlightable$op::after, +:root.highlight-you .quotesYou$site$highlightable$op::after, +.filter-highlight$site$highlightable$op::after { content: ""; display: block; clear: both; } -.filter-highlight .catalog-thumb, -.filter-highlight .werkTyme-filename { +:root:not(.werkTyme) .catalog-thread.filter-highlight .catalog-thumb, +:root.werkTyme .catalog-thread.filter-highlight:not(:hover), +:root.werkTyme:not(.catalog-hover-expand) .catalog-thread.filter-highlight, +:root.werkTyme.catalog-hover-expand .catalog-thread.filter-highlight > .catalog-container:hover > .catalog-post, +:root.catalog $site$catalog$thread.filter-highlight$site$highlightable$catalog { box-shadow: 0 0 3px 3px rgba(255, 0, 0, .5); } -.catalog-thread.watched .catalog-thumb, -.catalog-thread.watched .werkTyme-filename { +:root:not(.werkTyme) .catalog-thread.watched .catalog-thumb, +:root:root.werkTyme .catalog-thread.watched:not(:hover), +:root:root.werkTyme:not(.catalog-hover-expand) .catalog-thread.watched, +:root.werkTyme.catalog-hover-expand .catalog-thread.watched > .catalog-container:hover > .catalog-post { border: 2px solid rgba(255, 0, 0, .75); } /* Spoiler text */ -:root.reveal-spoilers s, -:root.reveal-spoilers s > a { +:root.reveal-spoilers $site$spoiler, +:root.reveal-spoilers $site$spoiler > a { color: white !important; } :root.reveal-spoilers .removed-spoiler::before { @@ -1210,6 +1529,13 @@ input[name="Default Volume"] { margin-right: 4px; padding: 2px; } +$site$infoRoot a.hide-reply-button { + margin-right: 6px; + padding: 0; +} +.replacedSideArrows { + float: left; +} .hide-thread-button:not(:hover), .hide-reply-button:not(:hover) { opacity: 0.4; @@ -1221,19 +1547,42 @@ input[name="Default Volume"] { } .hide-thread-button { margin-top: -1px; + width: 11px; } -.stub ~ * { +.stub ~ :not(.threadDivider) { display: none !important; } .stub input { display: inline-block; } -.thread[hidden] + hr { +$site$thread[hidden] + hr { display: none; } -:root.reply-hide div.sideArrows { +:root.reply-hide $site$sideArrows { + display: none; +} +:root.sw-yotsuba.thread-hide .party-hat { + left: 19px; +} + +/* Anonymize */ +:root.anonymize $site$info$name, +:root.sw-yotsuba.anonymize .post-author:not([class*=capcode]) { + font-size: 0; +} +:root.anonymize $site$info$tripcode, +:root.sw-yotsuba.anonymize .n-pu { display: none; } +:root.anonymize $site$info$name::before, +:root.sw-yotsuba.anonymize .post-author:not([class*=capcode])::before { + content: "Anonymous"; + font-size: 10pt; +} +:root.sw-yotsuba.anonymize .flashListing .name::before, +:root.sw-yotsuba.anonymize .post-last > .post-author:not([class*=capcode])::before { + font-size: 9pt; +} /* QR */ :root.hide-original-post-form #togglePostFormLink, @@ -1317,8 +1666,8 @@ input[name="Default Volume"] { #qr.reply-to-thread input[data-name="sub"]:not(.force-show), body:not(.board_f) #qr select[name="filetag"], #qr.reply-to-thread select[name="filetag"], -body:not(.board_jp) #sjis-toggle, -body:not(.board_sci) #tex-preview-button, +#qr:not(.has-sjis) #sjis-toggle, +#qr:not(.has-math) #tex-preview-button, #qr.tex-preview .textarea > :not(#tex-preview), #qr:not(.tex-preview) #tex-preview { display: none; @@ -1359,11 +1708,12 @@ input.field.tripped:not(:hover):not(:focus) { text-shadow: none !important; } #qr textarea { - min-width: 100%; + min-width: 300px; resize: both; } .field { -moz-box-sizing: border-box; + box-sizing: border-box; margin: 0px; padding: 2px 4px 3px; } @@ -1372,23 +1722,6 @@ input.field.tripped:not(:hover):not(:focus) { top: 2px; } -/* Recaptcha v1 */ -.captcha-img { - margin: 0px; - text-align: center; - background-image: #fff; - font-size: 0px; - min-height: 59px; - min-width: 302px; -} -.captcha-input { - width: 100%; - margin: 1px 0 0; -} -#qr.captcha-v1 #qr-captcha-iframe { - display: none; -} - /* Recaptcha v2 */ #qr .captcha-root { position: relative; @@ -1397,14 +1730,18 @@ input.field.tripped:not(:hover):not(:focus) { margin: auto; width: 304px; } -/* scrollable with scroll bar hidden; prevents scroll on space press */ -:root.ua-blink #qr .captcha-container > div { +/* XXX scrollable with scroll bar hidden; prevents scroll on space press */ +:root.ua-blink #qr .captcha-container > div, +:root.ua-edge #qr .captcha-container > div { overflow: hidden; } -:root.ua-blink #qr .captcha-container > div > div:first-of-type { +:root.ua-blink #qr .captcha-container > div > div:first-of-type, +:root.ua-edge #qr .captcha-container > div > div:first-of-type { overflow-y: scroll; overflow-x: hidden; - padding-right: 15px; + padding-right: 30px; + height: 99%; + width: 100%; } #qr .captcha-counter { display: block; @@ -1418,6 +1755,7 @@ input.field.tripped:not(:hover):not(:focus) { } #qr .captcha-counter > a { pointer-events: auto; + display: inline-block; /* XXX https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8851747/ */ } #qr:not(.captcha-open) .captcha-counter > a { display: block; @@ -1635,6 +1973,7 @@ input[type="checkbox"]:checked ~ .checkbox-letter { } .qr-preview { -moz-box-sizing: border-box; + box-sizing: border-box; counter-increment: thumbnails; cursor: move; display: inline-block; @@ -1715,7 +2054,8 @@ a:only-of-type > .remove { position: absolute; bottom: 20px; right: 10px; - -moz-transform: translateY(-50%); + -webkit-transform: translateY(-50%); + transform: translateY(-50%); } .textarea { position: relative; @@ -1734,6 +2074,13 @@ a:only-of-type > .remove { #char-count.warning { color: red; } +#split-post { + font-size: 8pt; + position: absolute; + bottom: 2px; + left: 2px; + cursor: pointer; +} /* Menu */ .menu-button:not(.fa-bars) { @@ -1749,7 +2096,7 @@ a:only-of-type > .remove { margin: 2px; vertical-align: middle; } -.post .menu-button, +.postInfo > .menu-button, #thread-watcher .menu-button { width: 18px; height: 15px; @@ -1758,6 +2105,7 @@ a:only-of-type > .remove { #menu { position: fixed; outline: none; + font-weight: normal; } #menu, .submenu { border-radius: 3px; @@ -1843,6 +2191,9 @@ a:only-of-type > .remove { } /* Embedding */ +.embedder:not(.embedded) > span { + display: none; +} #embedding { padding: 1px 4px 1px 4px; position: fixed; @@ -1979,6 +2330,10 @@ a:only-of-type > .remove { overflow-x: scroll !important; } .gal-image a { + display: -webkit-flex; + display: flex; + -webkit-align-items: flex-start; + align-items: flex-start; margin: auto; line-height: 0; max-width: 100%; @@ -1987,6 +2342,11 @@ a:only-of-type > .remove { width: 100%; height: 100%; } +.gal-image img, +.gal-image video { + -webkit-flex: none; + flex: none; +} .gal-fit-width .gal-image img, .gal-fit-width .gal-image video { max-width: 100%; @@ -2043,56 +2403,87 @@ a:only-of-type > .remove { bottom: 2px; vertical-align: baseline; } -.gal-buttons, -.gal-name, -.gal-count { +.gal-labels { position: fixed; - right: 195px; + bottom: 6px; + display: -webkit-flex; + display: flex; + -webkit-flex-direction: column; + flex-direction: column; + -webkit-align-items: flex-end; + align-items: flex-end; + } -.gal-hide-thumbnails .gal-buttons, -.gal-hide-thumbnails .gal-count, -.gal-hide-thumbnails .gal-name { - right: 44px; +:root:not(.show-sauce) .gal-sauce { + display: none; } -.gal-name { - bottom: 6px; +.gal-name, +.gal-count, +.gal-sauce { background: rgba(0,0,0,0.6) !important; border-radius: 3px; padding: 1px 5px 2px 5px; + margin-top: 3px; + color: #ffffff !important; text-decoration: none !important; - color: white !important; +} +.gal-sauce a { + color: #ffffff !important; } .gal-name:hover, -.gal-buttons a:hover { +.gal-buttons a:hover, +.gal-sauce a:hover { color: rgb(95, 95, 101) !important; } :root.gal-pdf .gal-buttons a:hover { color: rgb(204, 204, 204) !important; } -.gal-count { - bottom: 27px; - background: rgba(0,0,0,0.6) !important; - border-radius: 3px; - padding: 1px 5px 2px 5px; - color: #ffffff !important; +.gal-buttons, +.gal-labels { + position: fixed; + right: 195px; } -:root:not(.gal-fit-width):not(.gal-pdf) .gal-name { - bottom: 23px !important; +.gal-hide-thumbnails .gal-buttons, +.gal-hide-thumbnails .gal-labels { + right: 44px; } -:root:not(.gal-fit-width):not(.gal-pdf) .gal-count { - bottom: 44px !important; +:root:not(.gal-fit-width):not(.gal-pdf) .gal-labels { + bottom: 23px !important; } :root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-buttons, -:root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-name, -:root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-count { +:root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-labels { right: 178px !important; } -:root.gal-hide-thumbnails:.gal-fit-height:not(.gal-pdf) .gal-buttons, -:root.gal-hide-thumbnails:.gal-fit-height:not(.gal-pdf) .gal-name, -:root.gal-hide-thumbnails:.gal-fit-height:not(.gal-pdf) .gal-count { +:root.gal-hide-thumbnails.gal-fit-height:not(.gal-pdf) .gal-buttons, +:root.gal-hide-thumbnails.gal-fit-height:not(.gal-pdf) .gal-labels { right: 28px !important; } :root.gallery-open.fixed #header-bar:not(.autohide), :root.gallery-open.fixed #header-bar:not(.autohide) #shortcuts .fa::before { visibility: hidden; } + +/* Mod Contact Links */ +.contact-links { + margin-left: 2px; +} +.move-note > a { + text-decoration: underline; +} +.invisible { + font-size: 0; +} + +/* PostJumper */ +.postJumper > .prev, +.postJumper > .next { + font-size: 120%; +} + +/* PSA */ +.fcx-announcement { + text-align: center; +} +.fcx-announcement a { + text-decoration: underline; +} diff --git a/src/css/style.inc b/src/css/style.inc index ac391e658a..8eb7dae906 100644 --- a/src/css/style.inc +++ b/src/css/style.inc @@ -28,7 +28,8 @@ var icons = (names, data) => ( '/* Link Title Favicons */\n' + names.map((file, i) => -`.linkify.${file.split('.')[1]} { +`.linkify.${file.split('.')[1]}::before { + content: ""; background: transparent url('data:image/png;base64,${data[i]}') center left no-repeat!important; padding-left: 18px; } diff --git a/src/css/tomorrow.css b/src/css/tomorrow.css index c257729e73..e3ce82ddd5 100644 --- a/src/css/tomorrow.css +++ b/src/css/tomorrow.css @@ -4,6 +4,17 @@ border-color: #111; } +/* 4chan style fixes */ +:root.tomorrow #arc-list span.quote { + color: #B5BD68; +} +:root.tomorrow.highlight-you .quotesYou$site$highlightable$reply { + border-left: 3px solid rgba(145, 182, 214, .8) !important; +} +:root.tomorrow.highlight-own .yourPost$site$highlightable$reply { + border-left: 3px dashed rgba(145, 182, 214, .8) !important; +} + /* Header */ :root.tomorrow #header-bar.dialog { background-color: rgba(40,42,46,0.9); @@ -30,14 +41,17 @@ } /* Catalog */ -:root.tomorrow .catalog-code { - background-color: rgba(255, 255, 255, 0.1); +:root.tomorrow.catalog-hover-expand .catalog-container:hover > .post { + background-color: #282A2E; +} +:root.tomorrow.werkTyme .catalog-thread:not(:hover), +:root.tomorrow.werkTyme:not(.catalog-hover-expand) .catalog-thread, +:root.tomorrow.catalog-hover-expand .catalog-container:hover > .post, +:root.tomorrow.catalog-hover-expand .catalog-container:hover .catalog-reply { + border-color: #111; } /* Quote */ -:root.tomorrow .catalog-thread > .comment > span.quote, :root.tomorrow #arc-list span.quote { - color: #B5BD68; -} :root.tomorrow .backlink.deadlink { color: #81A2BE !important; } @@ -55,32 +69,37 @@ :root.tomorrow .qphl { outline: 2px solid rgba(145, 182, 214, .8); } -:root.tomorrow.highlight-you .quotesYou.opContainer, -:root.tomorrow.highlight-you .quotesYou > .reply { +:root.tomorrow.highlight-you .quotesYou$site$highlightable$op, +:root.tomorrow.highlight-you .quotesYou$site$highlightable$reply { border-left: 3px solid rgba(145, 182, 214, .8); } -:root.tomorrow.highlight-own .yourPost.opContainer, -:root.tomorrow.highlight-own .yourPost > .reply { +:root.tomorrow.highlight-own .yourPost$site$highlightable$op, +:root.tomorrow.highlight-own .yourPost$site$highlightable$reply { border-left: 3px dashed rgba(145, 182, 214, .8); } -:root.tomorrow .opContainer.filter-highlight, -:root.tomorrow .filter-highlight > .reply { +:root.tomorrow .filter-highlight$site$highlightable$op, +:root.tomorrow .filter-highlight$site$highlightable$reply { box-shadow: inset 5px 0 rgba(145, 182, 214, .5); } -:root.tomorrow.highlight-own .yourPost > div.sideArrows, -:root.tomorrow.highlight-you .quotesYou > div.sideArrows, -:root.tomorrow .filter-highlight > div.sideArrows { +:root.tomorrow.highlight-own .yourPost > $site$sideArrows, +:root.tomorrow.highlight-you .quotesYou > $site$sideArrows, +:root.tomorrow .filter-highlight > $site$sideArrows { color: rgb(155, 185, 210); } -:root.tomorrow .filter-highlight .catalog-thumb, -:root.tomorrow .filter-highlight .werkTyme-filename { +:root.tomorrow .catalog-thread.filter-highlight .catalog-thumb, +:root.tomorrow.werkTyme .catalog-thread.filter-highlight:not(:hover), +:root.tomorrow.werkTyme:not(.catalog-hover-expand) .catalog-thread.filter-highlight, +:root.tomorrow.werkTyme.catalog-hover-expand .catalog-thread.filter-highlight > .catalog-container:hover > .catalog-post { box-shadow: 0 0 3px 3px rgba(64, 192, 255, .7); } :root.tomorrow .catalog-thread.watched .catalog-thumb, -:root.tomorrow .catalog-thread.watched .werkTyme-filename { +:root.tomorrow.werkTyme .catalog-thread.watched:not(:hover), +:root.tomorrow.werkTyme:not(.catalog-hover-expand) .catalog-thread.watched, +:root.tomorrow.werkTyme.catalog-hover-expand .catalog-thread.watched > .catalog-container:hover > .catalog-post { border: 2px solid rgb(64, 192, 255); } + /* QR */ .tomorrow #dump-list::-webkit-scrollbar-thumb { background-color: #282A2E; @@ -143,12 +162,15 @@ } /* Unread */ -:root.tomorrow #unread-line { +:root.tomorrow .unread-line { border-color: rgb(197, 200, 198); } +:root.tomorrow .unread-mark-read { + background-color: rgba(40,42,46,0.5); +} /* Thread Watcher */ -:root.tomorrow .replies-quoting-you > a, :root.tomorrow #watcher-link.disabled.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; } diff --git a/src/css/www.css b/src/css/www.css index 01b12c3991..75d86eeb41 100644 --- a/src/css/www.css +++ b/src/css/www.css @@ -1,3 +1,11 @@ #captcha-cnt { height: auto; } +:root:not(.js-enabled) #form { + display: block; +} +#bd > div[style], #bd > div[style] > * { + height: auto !important; + margin: 0 !important; + font-size: 0; +} diff --git a/src/css/yotsuba-b.css b/src/css/yotsuba-b.css index 66b59bcced..b04f9246cd 100644 --- a/src/css/yotsuba-b.css +++ b/src/css/yotsuba-b.css @@ -8,6 +8,14 @@ border-color: #98E; } +/* 4chan style fixes */ +:root.yotsuba-b.highlight-you .quotesYou$site$highlightable$reply { + border-left: 3px solid rgba(221, 0, 0, .8) !important; +} +:root.yotsuba-b.highlight-own .yourPost$site$highlightable$reply { + border-left: 3px dashed rgba(221, 0, 0, .8) !important; +} + /* Header */ :root.yotsuba-b #header-bar.dialog { background-color: rgba(214,218,240,0.98); @@ -30,6 +38,17 @@ background-color: #D6DAF0; } +/* Catalog */ +:root.yotsuba-b.catalog-hover-expand .catalog-container:hover > .post { + background-color: #D6DAF0; +} +:root.yotsuba-b.werkTyme .catalog-thread:not(:hover), +:root.yotsuba-b.werkTyme:not(.catalog-hover-expand) .catalog-thread, +:root.yotsuba-b.catalog-hover-expand .catalog-container:hover > .post, +:root.yotsuba-b.catalog-hover-expand .catalog-container:hover .catalog-reply { + border-color: #B7C5D9; +} + /* Quote */ :root.yotsuba-b .backlink.deadlink { color: #34345C !important; @@ -72,8 +91,13 @@ background: rgba(255, 255, 255, .33); } +/* Unread */ +:root.yotsuba-b .unread-mark-read { + background-color: rgba(214,218,240,0.5); +} + /* Thread Watcher */ -:root.yotsuba-b .replies-quoting-you > a, :root.yotsuba-b #watcher-link.disabled.replies-quoting-you { +:root.yotsuba-b .replies-quoting-you > a, :root.yotsuba-b #watcher-link.replies-quoting-you { color: #F00; } diff --git a/src/css/yotsuba.css b/src/css/yotsuba.css index 9acd569fb3..ea8b8d799c 100644 --- a/src/css/yotsuba.css +++ b/src/css/yotsuba.css @@ -8,6 +8,14 @@ border-color: #EA8; } +/* 4chan style fixes */ +:root.yotsuba.highlight-you .quotesYou$site$highlightable$reply { + border-left: 3px solid rgba(221, 0, 0, .8) !important; +} +:root.yotsuba.highlight-own .yourPost$site$highlightable$reply { + border-left: 3px dashed rgba(221, 0, 0, .8) !important; +} + /* Header */ :root.yotsuba #header-bar.dialog { background-color: rgba(240,224,214,0.98); @@ -30,6 +38,17 @@ background-color: #F0E0D6; } +/* Catalog */ +:root.yotsuba.catalog-hover-expand .catalog-container:hover > .post { + background-color: #F0E0D6; +} +:root.yotsuba.werkTyme .catalog-thread:not(:hover), +:root.yotsuba.werkTyme:not(.catalog-hover-expand) .catalog-thread, +:root.yotsuba.catalog-hover-expand .catalog-container:hover > .post, +:root.yotsuba.catalog-hover-expand .catalog-container:hover .catalog-reply { + border-color: #D9BFB7; +} + /* Quote */ :root.yotsuba .backlink.deadlink { color: #00E !important; @@ -71,8 +90,13 @@ background: rgba(255, 255, 255, .33); } +/* Unread */ +:root.yotsuba .unread-mark-read { + background-color: rgba(240,224,214,0.5); +} + /* Thread Watcher */ -:root.yotsuba .replies-quoting-you > a, :root.yotsuba #watcher-link.disabled.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; } diff --git a/src/globals/globals.js b/src/globals/globals.js index c9e91136bc..6b3f2b47f8 100644 --- a/src/globals/globals.js +++ b/src/globals/globals.js @@ -1,6 +1,6 @@ var Conf, E, c, d, doc, docSet, g; -Conf = {}; +Conf = Object.create(null); c = console; d = document; doc = d.documentElement; @@ -13,7 +13,8 @@ docSet = function() { g = { VERSION: '<%= readJSON('/version.json').version %>', NAMESPACE: '<%= meta.name %>.', - boards: {} + sites: Object.create(null), + boards: Object.create(null) }; E = (function() { @@ -43,7 +44,3 @@ E.cat = function(templates) { } return html; }; - -E.url = function(content) { - return "data:text/html;charset=utf-8," + encodeURIComponent(content.innerHTML); -}; diff --git a/src/main/Main.coffee b/src/main/Main.coffee index 5d59cd5886..fa6d5ebcbe 100644 --- a/src/main/Main.coffee +++ b/src/main/Main.coffee @@ -1,23 +1,22 @@ Main = 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. - return if window['<%= meta.name %> antidup'] - window['<%= meta.name %> antidup'] = true - - if location.hostname is 'www.google.com' - $.get 'Captcha Fixes', true, ({'Captcha Fixes': enabled}) -> - if enabled - $.ready -> Captcha.fixes.init() - return + # XXX Firefox reinjects WebExtension content scripts when extension is updated / reloaded. + try + w = window + w = (w.wrappedJSObject or w) if $.platform is 'crx' + return if '<%= meta.name %> antidup' of w + w['<%= meta.name %> antidup'] = true # Don't run inside ad iframes. try - return if window.frameElement and window.frameElement.src is '' + return if window.frameElement and window.frameElement.src in ['', 'about:blank'] # Detect multiple copies of 4chan X + return if doc and $.hasClass(doc, 'fourchan-x') + $.asap docSet, -> + $.addClass doc, 'fourchan-x', 'seaweedchan' + $.addClass doc, "ua-#{$.engine}" if $.engine $.on d, '4chanXInitFinished', -> if Main.expectInitFinished delete Main.expectInitFinished @@ -25,10 +24,19 @@ Main = new Notice 'error', 'Error: Multiple copies of 4chan X are enabled.' $.addClass doc, 'tainted' + # Detect "mounted" event from Kissu + mountedCB = -> + d.removeEventListener 'mounted', mountedCB, true + Main.isMounted = true + for cb in Main.mountedCBs + try + cb() + d.addEventListener 'mounted', mountedCB, true + # Flatten default values from Config into Conf flatten = (parent, obj) -> if obj instanceof Array - Conf[parent] = obj[0] + Conf[parent] = $.dict.clone(obj[0]) else if typeof obj is 'object' for key, val of obj flatten key, val @@ -36,14 +44,31 @@ Main = Conf[parent] = obj return + # XXX Remove document-breaking ad + if location.hostname in ['boards.4chan.org', 'boards.4channel.org'] + $.global -> + fromCharCode0 = String.fromCharCode + String.fromCharCode = -> + if document.body + String.fromCharCode = fromCharCode0 + else if document.currentScript and not document.currentScript.src + throw Error() + fromCharCode0.apply @, arguments + $.asap docSet, -> + $.onExists doc, 'iframe[srcdoc]', $.rm + flatten null, Config for db in DataBoard.keys - Conf[db] = boards: {} + Conf[db] = $.dict() + Conf['customTitles'] = $.dict.clone {'4chan.org': {boards: {'qa': {'boardTitle': {orig: '/qa/ - Question & Answer', title: '/qa/ - 2D/Random'}}}}} + Conf['boardConfig'] = boards: $.dict() Conf['archives'] = Redirect.archives - Conf['selectedArchives'] = {} - Conf['cooldowns'] = {} - Conf['Index Sort'] = {} + Conf['selectedArchives'] = $.dict() + Conf['cooldowns'] = $.dict() + Conf['Index Sort'] = $.dict() + Conf["Last Long Reply Thresholds #{i}"] = $.dict() for i in [0...2] + Conf['siteProperties'] = $.dict() # XXX old key names Conf['Except Archives from Encryption'] = false @@ -52,36 +77,33 @@ Main = Conf['Show Name and Subject'] = false Conf['QR Shortcut'] = true Conf['Bottom QR Link'] = true - - # Pseudo-enforce default whitelist while configuration loads - if $.platform is 'crx' then $.global -> - {whitelist} = document.currentScript.dataset - whitelist = whitelist.split('\n').filter (x) -> x[0] isnt "'" - whitelist.push "#{location.protocol}//#{location.host}" - oldFun = {} - for key in ['createElement', 'write'] - oldFun[key] = document[key] - document[key] = do (key) -> (arg) -> - s = document.currentScript - if s and s.src and whitelist.indexOf(s.src.split('/')[..2].join('/')) < 0 - throw Error() - oldFun[key].call document, arg - document.addEventListener 'csp-ready', -> - document[key] = oldFun[key] for key of oldFun - , false - , - whitelist: Conf['jsWhitelist'] + Conf['Toggleable Thread Watcher'] = true + Conf['siteSoftware'] = '' + Conf['Use Faster Image Host'] = 'true' + Conf['Captcha Fixes'] = true + Conf['captchaServiceDomain'] = '' + Conf['captchaServiceKey'] = $.dict() + + # Enforce JS whitelist + if ( + /\.4chan(?:nel)?\.org$/.test(location.hostname) and + !SW.yotsuba.regexp.pass.test(location.href) and + !SW.yotsuba.regexp.captcha.test(location.href) and + !$$('script:not([src])', d).filter((s) -> /this\[/.test(s.textContent)).length + ) + ($.getSync or $.get) {'jsWhitelist': Conf['jsWhitelist']}, ({jsWhitelist}) -> + parsedList = jsWhitelist.replace(/^#.*$/mg, '').replace(/[\s;]+/g, ' ').trim() + if /\S/.test(parsedList) + $.addCSP "script-src #{parsedList}" # Get saved values as items - items = {} + items = $.dict() items[key] = undefined for key of Conf items['previousversion'] = undefined ($.getSync or $.get) items, (items) -> - # Enforce JS whitelist - jsWhitelist = items['jsWhitelist'] ? Conf['jsWhitelist'] - $.addCSP "script-src #{jsWhitelist.replace(/[\s;]+/g, ' ')}" - $.event 'csp-ready' if $.platform is 'crx' - + if !$.perProtocolSettings and /\.4chan(?:nel)?\.org$/.test(location.hostname) and (items['Redirect to HTTPS'] ? Conf['Redirect to HTTPS']) and location.protocol isnt 'https:' + location.replace('https://' + location.host + location.pathname + location.search + location.hash) + return $.asap docSet, -> # Don't hide the local storage warning behind a settings panel. @@ -90,6 +112,7 @@ Main = # Fresh install else if !items.previousversion? + Main.isFirstRun = true Main.ready -> $.set 'previousversion', g.VERSION Settings.open() @@ -102,7 +125,7 @@ Main = for key, val of Conf Conf[key] = items[key] ? val - Main.initFeatures() + Site.init Main.initFeatures upgrade: (items) -> {previousversion} = items @@ -111,63 +134,68 @@ Main = $.set changes, -> if items['Show Updated Notifications'] ? true el = $.el 'span', - <%= html(meta.name + ' has been updated to version ${g.VERSION}.') %> + `<%= html(meta.name + ' has been updated to version ${g.VERSION}.') %>` new Notice 'info', el, 15 + parseURL: (site=g.SITE, url=location) -> + r = {} + + return r if !site + r.siteID = site.ID + + return r if site.isBoardlessPage?(url) + pathname = url.pathname.split /\/+/ + r.boardID = pathname[1] + + if site.isFileURL(url) + r.VIEW = 'file' + else if site.isAuxiliaryPage?(url) + # pass + else if pathname[2] in ['thread', 'res'] + r.VIEW = 'thread' + r.threadID = r.THREADID = +pathname[3].replace(/\.\w+$/, '') + else if pathname[2] is 'archive' and pathname[3] is 'res' + r.VIEW = 'thread' + r.threadID = r.THREADID = +pathname[4].replace(/\.\w+$/, '') + r.threadArchived = true + else if /^(?:catalog|archive)(?:\.\w+)?$/.test(pathname[2]) + r.VIEW = pathname[2].replace(/\.\w+$/, '') + else if /^(?:index|\d*)(?:\.\w+)?$/.test(pathname[2]) + r.VIEW = 'index' + r + initFeatures: -> - {hostname, search} = location - pathname = location.pathname.split /\/+/ - g.BOARD = new Board pathname[1] unless hostname is 'www.4chan.org' + $.global -> + document.documentElement.classList.add 'js-enabled' + window.FCX = {} + Main.jsEnabled = $.hasClass doc, 'js-enabled' - if hostname in ['boards.4chan.org', 'sys.4chan.org', 'www.4chan.org'] - $.global -> - document.documentElement.classList.add 'js-enabled' - window.FCX = {} - Main.jsEnabled = $.hasClass doc, 'js-enabled' - - switch hostname - when 'www.4chan.org' - $.onExists doc, 'body', -> $.addStyle CSS.www - Captcha.replace.init() - return - when 'sys.4chan.org' - if pathname[2] is 'imgboard.php' - if /\bmode=report\b/.test search - Report.init() - else if (match = search.match /\bres=(\d+)/) - $.ready -> - if Conf['404 Redirect'] and $.id('errmsg')?.textContent is 'Error: Specified thread does not exist.' - Redirect.navigate 'thread', - boardID: g.BOARD.ID - postID: +match[1] - else if pathname[2] is 'post' - PostSuccessful.init() - return - when 'i.4cdn.org' - return unless pathname[2] and not /s\.jpg$/.test(pathname[2]) - $.asap (-> d.readyState isnt 'loading'), -> - if Conf['404 Redirect'] and d.title in ['4chan - Temporarily Offline', '4chan - 404 Not Found'] - Redirect.navigate 'file', - boardID: g.BOARD.ID - filename: pathname[pathname.length - 1] - else if video = $ 'video' - if Conf['Volume in New Tab'] - Volume.setup video - if Conf['Loop in New Tab'] - video.loop = true - video.controls = false - video.play() - ImageCommon.addControls video - return + # XXX https://bugs.chromium.org/p/chromium/issues/detail?id=920638 + $.ajaxPageInit?() - if pathname[2] in ['thread', 'res'] - g.VIEW = 'thread' - g.THREADID = +pathname[3] - else if pathname[2] in ['catalog', 'archive'] - g.VIEW = pathname[2] - else if pathname[2].match /^\d*$/ - g.VIEW = 'index' - else + $.extend g, Main.parseURL() + g.BOARD = new Board g.boardID if g.boardID + + if !g.VIEW + g.SITE.initAuxiliary?() + return + + if g.VIEW is 'file' + $.asap (-> d.readyState isnt 'loading'), -> + if g.SITE.software is 'yotsuba' and Conf['404 Redirect'] and g.SITE.is404?() + pathname = location.pathname.split /\/+/ + Redirect.navigate 'file', { + boardID: g.BOARD.ID + filename: pathname[pathname.length - 1] + } + else if video = $ 'video' + if Conf['Volume in New Tab'] + Volume.setup video + if Conf['Loop in New Tab'] + video.loop = true + video.controls = false + video.play() + ImageCommon.addControls video return g.threads = new SimpleDict() @@ -178,6 +206,7 @@ Main = # c.time 'All initializations' for [name, feature] in Main.features + continue if g.SITE.disabledFeatures and name in g.SITE.disabledFeatures # c.time "#{name} initialization" try feature.init() @@ -197,123 +226,151 @@ Main = # disable the mobile layout $('link[href*=mobile]', d.head)?.disabled = true - $.addClass doc, 'fourchan-x', 'seaweedchan' + doc.dataset.host = location.host + $.addClass doc, "sw-#{g.SITE.software}" $.addClass doc, if g.VIEW is 'thread' then 'thread-view' else g.VIEW - $.addClass doc, "ua-#{$.engine}" if $.engine - $.onExists doc, '.ad-cnt', (ad) -> $.onExists ad, 'img', -> $.addClass doc, 'ads-loaded' + $.onExists doc, '.ad-cnt, .adg-rects > .desktop', (ad) -> $.onExists ad, 'img, iframe', -> $.addClass doc, 'ads-loaded' $.addClass doc, 'autohiding-scrollbar' if Conf['Autohiding Scrollbar'] $.ready -> if d.body.clientHeight > doc.clientHeight and (window.innerWidth is doc.clientWidth) isnt Conf['Autohiding Scrollbar'] Conf['Autohiding Scrollbar'] = !Conf['Autohiding Scrollbar'] $.set 'Autohiding Scrollbar', Conf['Autohiding Scrollbar'] $.toggleClass doc, 'autohiding-scrollbar' - $.addStyle CSS.boards, 'fourchanx-css' + $.addStyle CSS.sub(CSS.boards), 'fourchanx-css' Main.bgColorStyle = $.el 'style', id: 'fourchanx-bgcolor-css' keyboard = false $.on d, 'mousedown', -> keyboard = false - $.on d, 'keydown', (e) -> keyboard = true if e.keyCode is 9 # tab + $.on d, 'keydown', (e) -> (keyboard = true if e.keyCode is 9) # tab window.addEventListener 'focus', (-> doc.classList.toggle 'keyboard-focus', keyboard), true Main.setClass() setClass: -> - if g.VIEW is 'catalog' - $.addClass doc, $.id('base-css').href.match(/catalog_(\w+)/)[1].replace('_new', '').replace /_+/g, '-' - return + knownStyles = ['yotsuba', 'yotsuba-b', 'futaba', 'burichan', 'photon', 'tomorrow', 'spooky'] + + if g.SITE.software is 'yotsuba' and g.VIEW is 'catalog' + if (mainStyleSheet = $.id('base-css')) + style = mainStyleSheet.href.match(/catalog_(\w+)/)?[1].replace('_new', '').replace(/_+/g, '-') + if style in knownStyles + $.addClass doc, style + return + + style = mainStyleSheet = styleSheets = null - style = 'yotsuba-b' - mainStyleSheet = $ 'link[title=switch]', d.head - styleSheets = $$ 'link[rel="alternate stylesheet"]', d.head setStyle = -> - $.rmClass doc, style - style = null - for styleSheet in styleSheets - if styleSheet.href is mainStyleSheet?.href - style = styleSheet.title.toLowerCase().replace('new', '').trim().replace /\s+/g, '-' - break - if style - $.addClass doc, style - $.rm Main.bgColorStyle - else - # Determine proper background color for dialogs if 4chan is using a special stylesheet. - div = $.el 'div', - className: 'reply' - div.style.cssText = 'position: absolute; visibility: hidden;' - $.add d.body, div - bgColor = window.getComputedStyle(div).backgroundColor - $.rm div - Main.bgColorStyle.textContent = """ - .dialog, .suboption-list > div:last-of-type { - background-color: #{bgColor}; + # Use preconfigured CSS for 4chan's default themes. + if g.SITE.software is 'yotsuba' + $.rmClass doc, style + style = null + for styleSheet in styleSheets + if styleSheet.href is mainStyleSheet?.href + style = styleSheet.title.toLowerCase().replace('new', '').trim().replace /\s+/g, '-' + style = styleSheet.href.match(/[a-z]*(?=[^/]*$)/)[0] if style is '_special' + style = null unless style in knownStyles + break + if style + $.addClass doc, style + $.rm Main.bgColorStyle + return + + # Determine proper dialog background color for other themes. + div = g.SITE.bgColoredEl() + div.style.position = 'absolute'; + div.style.visibility = 'hidden'; + $.add d.body, div + bgColor = window.getComputedStyle(div).backgroundColor + $.rm div + rgb = bgColor.match(/[\d.]+/g) + # Use body background if reply background is transparent + unless /^rgb\(/.test(bgColor) + s = window.getComputedStyle(d.body) + bgColor = "#{s.backgroundColor} #{s.backgroundImage} #{s.backgroundRepeat} #{s.backgroundPosition}" + css = """ + .dialog, .suboption-list > div:last-of-type, :root.catalog-hover-expand .catalog-container:hover > .post { + background: #{bgColor}; + } + .unread-mark-read { + background-color: rgba(#{rgb[...3].join(', ')}, #{0.5*(rgb[3] || 1)}); + } + """ + if $.luma(rgb) < 100 + css += """ + .watch-thread-link { + background-image: url("data:image/svg+xml,"); } """ - $.after $.id('fourchanx-css'), Main.bgColorStyle - setStyle() - return unless mainStyleSheet - new MutationObserver(setStyle).observe mainStyleSheet, - attributes: true - attributeFilter: ['href'] + Main.bgColorStyle.textContent = css + $.after $.id('fourchanx-css'), Main.bgColorStyle + + $.onExists d.head, g.SITE.selectors.styleSheet, (el) -> + mainStyleSheet = el + if g.SITE.software is 'yotsuba' + styleSheets = $$ 'link[rel="alternate stylesheet"]', d.head + new MutationObserver(setStyle).observe mainStyleSheet, { + attributes: true + attributeFilter: ['href'] + } + $.on mainStyleSheet, 'load', setStyle + setStyle() + unless mainStyleSheet + for styleSheet in $$ 'link[rel="stylesheet"]', d.head + $.on styleSheet, 'load', setStyle + setStyle() initReady: -> - # XXX Sometimes threads don't 404 but are left over as stubs containing one garbage reply post. - if g.VIEW is 'thread' and (d.title in ['4chan - Temporarily Offline', '4chan - 404 Not Found'] or ($('.board') and not $('.opContainer'))) - ThreadWatcher.set404 g.BOARD.ID, g.THREADID, -> - if Conf['404 Redirect'] - Redirect.navigate 'thread', - boardID: g.BOARD.ID - threadID: g.THREADID - postID: +location.hash.match /\d+/ # post number or 0 - , "/#{g.BOARD}/" - return + if g.SITE.is404?() + if g.VIEW is 'thread' + ThreadWatcher.set404 g.BOARD.ID, g.THREADID, -> + if Conf['404 Redirect'] + Redirect.navigate 'thread', + boardID: g.BOARD.ID + threadID: g.THREADID + postID: +location.hash.match /\d+/ # post number or 0 + , "/#{g.BOARD}/" - return if d.title in ['4chan - Temporarily Offline', '4chan - 404 Not Found'] + return - if g.VIEW in ['index', 'thread'] and not $('.board + *') + if g.SITE.isIncomplete?() msg = $.el 'div', - <%= html('The page didn't load completely.
              Some features may not work unless you reload.') %> + `<%= html('The page didn't load completely.
              Some features may not work unless you reload.') %>` $.on $('a', msg), 'click', -> location.reload() new Notice 'warning', msg # Parse HTML or skip it and start building from JSON. - unless Conf['JSON Index'] and g.VIEW is 'index' - Main.initThread() + if g.VIEW is 'catalog' + Main.initCatalog() + else if !Index.enabled + if g.SITE.awaitBoard + g.SITE.awaitBoard Main.initThread + else + Main.initThread() else Main.expectInitFinished = true $.event '4chanXInitFinished' initThread: -> - if (board = $ '.board') + s = g.SITE.selectors + if (board = $ (s.boardFor?[g.VIEW] or s.board)) threads = [] posts = [] + errors = [] - for threadRoot in $$ '.board > .thread', board - thread = new Thread +threadRoot.id[1..], g.BOARD - threads.push thread - for postRoot in $$('.thread > .postContainer', threadRoot) when $('.postMessage', postRoot) - try - posts.push new Post postRoot, thread, g.BOARD - catch err - # Skip posts that we failed to parse. - unless errors - errors = [] - errors.push - message: "Parsing of Post No.#{postRoot.id.match(/\d+/)} failed. Post will be skipped." - error: err - Main.handleErrors errors if errors + try + g.SITE.preParsingFixes?(board) + + Main.addThreadsObserver = new MutationObserver Main.addThreads + Main.addPostsObserver = new MutationObserver Main.addPosts + Main.addThreadsObserver.observe board, {childList: true} + + Main.parseThreads $$(s.thread, board), threads, posts, errors + Main.handleErrors errors if errors.length if g.VIEW is 'thread' - scriptData = Get.scriptData() - threads[0].postLimit = /\bbumplimit *= *1\b/.test scriptData - threads[0].fileLimit = /\bimagelimit *= *1\b/.test scriptData - threads[0].ipCount = if m = scriptData.match /\bunique_ips *= *(\d+)\b/ then +m[1] - - if g.BOARD.ID is 'f' and g.VIEW is 'thread' - $.ajax "//a.4cdn.org/f/thread/#{g.THREADID}.json", - timeout: $.MINUTE - onloadend: -> - if @response and posts[0].file - posts[0].file.text.dataset.md5 = posts[0].file.MD5 = @response.posts[0].md5 + if g.threadArchived + threads[0].isArchived = true + threads[0].kill() + g.SITE.parseThreadMetadata?(threads[0]) Main.callbackNodes 'Thread', threads Main.callbackNodesDB 'Post', posts, -> @@ -325,6 +382,124 @@ Main = Main.expectInitFinished = true $.event '4chanXInitFinished' + parseThreads: (threadRoots, threads, posts, errors) -> + for threadRoot in threadRoots + boardObj = if (boardID = threadRoot.dataset.board) + boardID = encodeURIComponent boardID + g.boards[boardID] or new Board(boardID) + else + g.BOARD + threadID = +threadRoot.id.match(/\d*$/)[0] + return if !threadID or boardObj.threads.get(threadID)?.nodes.root + thread = new Thread threadID, boardObj + thread.nodes.root = threadRoot + threads.push thread + postRoots = $$ g.SITE.selectors.postContainer, threadRoot + postRoots.unshift threadRoot if g.SITE.isOPContainerThread + Main.parsePosts postRoots, thread, posts, errors + Main.addPostsObserver.observe threadRoot, {childList: true} + + parsePosts: (postRoots, thread, posts, errors) -> + for postRoot in postRoots when !(postRoot.dataset.fullID and g.posts.get(postRoot.dataset.fullID)) and $(g.SITE.selectors.comment, postRoot) + try + posts.push new Post postRoot, thread, thread.board + catch err + # Skip posts that we failed to parse. + errors.push + message: "Parsing of Post No.#{postRoot.id.match(/\d+/)} failed. Post will be skipped." + error: err + html: postRoot.outerHTML + return + + addThreads: (records) -> + threadRoots = [] + for record in records + for node in record.addedNodes when node.nodeType is Node.ELEMENT_NODE and node.matches(g.SITE.selectors.thread) + threadRoots.push node + return unless threadRoots.length + threads = [] + posts = [] + errors = [] + Main.parseThreads threadRoots, threads, posts, errors + Main.handleErrors errors if errors.length + Main.callbackNodes 'Thread', threads + Main.callbackNodesDB 'Post', posts, -> + $.event 'PostsInserted', null, records[0].target + + addPosts: (records) -> + threads = [] + threadsRM = [] + posts = [] + errors = [] + for record in records + thread = Get.threadFromRoot record.target + postRoots = [] + for node in record.addedNodes when node.nodeType is Node.ELEMENT_NODE + if node.matches(g.SITE.selectors.postContainer) or (node = $(g.SITE.selectors.postContainer, node)) + postRoots.push node + n = posts.length + Main.parsePosts postRoots, thread, posts, errors + if posts.length > n and thread not in threads + threads.push thread + anyRemoved = false + for el in record.removedNodes + if Get.postFromRoot(el)?.nodes.root is el and !doc.contains(el) + anyRemoved = true + break + if anyRemoved and thread not in threadsRM + threadsRM.push thread + Main.handleErrors errors if errors.length + Main.callbackNodesDB 'Post', posts, -> + for thread in threads + $.event 'PostsInserted', null, thread.nodes.root + for thread in threadsRM + $.event 'PostsRemoved', null, thread.nodes.root + return + + initCatalog: -> + s = g.SITE.selectors.catalog + if s and (board = $ s.board) + threads = [] + errors = [] + + Main.addCatalogThreadsObserver = new MutationObserver Main.addCatalogThreads + Main.addCatalogThreadsObserver.observe board, {childList: true} + + Main.parseCatalogThreads $$(s.thread, board), threads, errors + Main.handleErrors errors if errors.length + + Main.callbackNodes 'CatalogThreadNative', threads + + Main.expectInitFinished = true + $.event '4chanXInitFinished' + + parseCatalogThreads: (threadRoots, threads, errors) -> + for threadRoot in threadRoots + try + thread = new CatalogThreadNative threadRoot + if thread.thread.catalogViewNative?.nodes.root isnt threadRoot + thread.thread.catalogViewNative = thread + threads.push thread + catch err + # Skip threads that we failed to parse. + errors.push + message: "Parsing of Catalog Thread No.#{(threadRoot.dataset.id or threadRoot.id).match(/\d+/)} failed. Thread will be skipped." + error: err + html: threadRoot.outerHTML + return + + addCatalogThreads: (records) -> + threadRoots = [] + for record in records + for node in record.addedNodes when node.nodeType is Node.ELEMENT_NODE and node.matches(g.SITE.selectors.catalog.thread) + threadRoots.push node + return unless threadRoots.length + threads = [] + errors = [] + Main.parseCatalogThreads threadRoots, threads, errors + Main.handleErrors errors if errors.length + Main.callbackNodes 'CatalogThreadNative', threads + callbackNodes: (klass, nodes) -> i = 0 cb = Callbacks[klass] @@ -336,7 +511,7 @@ Main = i = 0 cbs = Callbacks[klass] fn = -> - return false unless node = nodes[i] + return false if not (node = nodes[i]) cbs.execute node ++i % 25 @@ -344,7 +519,7 @@ Main = while fn() continue unless nodes[i] - cb() if cb + (cb() if cb) return setTimeout softTask, 0 @@ -356,6 +531,16 @@ Main = new Notice 'error', 'Error: Multiple copies of 4chan X are enabled.' $.addClass doc, 'tainted' + # Detect conflicts with native extension + if g.SITE.testNativeExtension and not $.hasClass(doc, 'tainted') + {enabled} = g.SITE.testNativeExtension() + if enabled + $.addClass doc, 'tainted' + if Conf['Disable Native Extension'] and !Main.isFirstRun + msg = $.el 'div', + `<%= html('Failed to disable the native extension. You may need to block it.') %>` + new Notice 'error', msg + unless errors instanceof Array error = errors else if errors.length is 1 @@ -365,12 +550,13 @@ Main = return div = $.el 'div', - <%= html('${errors.length} errors occurred.&{Main.reportLink(errors)} [show]') %> + `<%= html('${errors.length} errors occurred.&{Main.reportLink(errors)} [show]') %>` $.on div.lastElementChild, 'click', -> - [@textContent, logs.hidden] = if @textContent is 'show' + [@textContent, logs.hidden] = if @textContent is 'show' then ( ['hide', false] - else + ) else ( ['show', true] + ) logs = $.el 'div', hidden: true @@ -382,7 +568,7 @@ Main = parseError: (data, reportLink) -> c.error data.message, data.error.stack message = $.el 'div', - <%= html('${data.message}?{reportLink}{&{reportLink}}') %> + `<%= html('${data.message}?{reportLink}{&{reportLink}}') %>` error = $.el 'div', textContent: "#{data.error.name or 'Error'}: #{data.error.message or 'see console for details'}" lines = data.error.stack?.match(/\d+(?=:\d+\)?$)/mg)?.join().replace(/^/, ' at ') or '' @@ -396,38 +582,52 @@ Main = title += " (+#{errors.length - 1} other errors)" if errors.length > 1 details = '' addDetails = (text) -> - unless encodeURIComponent(title + details + text + '\n').length > <%= meta.newIssueMaxLength - meta.newIssue.replace(/%(title|details)/, '').length %> + unless encodeURIComponent(title + details + text + '\n').length > `<%= meta.newIssueMaxLength - meta.newIssue.replace(/%(title|details)/, '').length %>` details += text + '\n' addDetails """ [Please describe the steps needed to reproduce this error.] Script: <%= meta.name %> <%= meta.fork %> v#{g.VERSION} #{$.platform} - User agent: #{navigator.userAgent} URL: #{location.href} + User agent: #{navigator.userAgent} """ + if $.platform is 'userscript' and (info = if GM? then GM.info else (if GM_info? then GM_info)) + addDetails "Userscript manager: #{info.scriptHandler} #{info.version}" addDetails '\n' + data.error addDetails data.error.stack.replace(data.error.toString(), '').trim() if data.error.stack addDetails '\n`' + data.html + '`' if data.html details = details.replace /file:\/{3}.+\//g, '' # Remove local file paths - url = "<%= meta.newIssue.replace('%title', '#{encodeURIComponent title}').replace('%details', '#{encodeURIComponent details}') %>" - <%= html(' [report]') %> + url = '<%= meta.newIssue %>'.replace('%title', encodeURIComponent title).replace('%details', encodeURIComponent details) + `<%= html(' [report]') %>` isThisPageLegit: -> - # 404 error page or similar. + # not 404 error page or similar. unless 'thisPageIsLegit' of Main - Main.thisPageIsLegit = location.hostname is 'boards.4chan.org' and - !$('link[href*="favicon-status.ico"]', d.head) and - d.title not in ['4chan - Temporarily Offline', '4chan - Error', '504 Gateway Time-out'] + Main.thisPageIsLegit = if g.SITE.isThisPageLegit + g.SITE.isThisPageLegit() + else + !/^[45]\d\d\b/.test(document.title) and !/\.(?:json|rss)$/.test(location.pathname) Main.thisPageIsLegit ready: (cb) -> $.ready -> - cb() if Main.isThisPageLegit() + (cb() if Main.isThisPageLegit()) + + mounted: (cb) -> + if Main.isMounted + cb() + else + Main.mountedCBs.push cb + + mountedCBs: [] features: [ ['Polyfill', Polyfill] + ['Board Configuration', BoardConfig] ['Normalize URL', NormalizeURL] + ['Delay Redirect on Post', PostRedirect] ['Captcha Configuration', Captcha.replace] + ['Image Host Rewriting', ImageHost] ['Redirect', Redirect] ['Header', Header] ['Catalog Links', CatalogLinks] @@ -436,8 +636,10 @@ Main = ['Disable Autoplay', AntiAutoplay] ['Announcement Hiding', PSAHiding] ['Fourchan thingies', Fourchan] + ['Tinyboard Glue', Tinyboard] ['Color User IDs', IDColor] ['Highlight by User ID', IDHighlight] + ['Count Posts by ID', IDPostCount] ['Custom CSS', CustomCSS] ['Thread Links', ThreadLinks] ['Linkify', Linkify] @@ -451,10 +653,12 @@ Main = ['Quick Reply Personas', QR.persona] ['Quick Reply', QR] ['Cooldown', QR.cooldown] + ['Post Jumper', PostJumper] ['Pass Link', PassLink] ['Menu', Menu] ['Index Generator (Menu)', Index.menu] ['Report Link', ReportLink] + ['Copy Text Link', CopyTextLink] ['Thread Hiding (Menu)', ThreadHiding.menu] ['Reply Hiding (Menu)', PostHiding.menu] ['Delete Link', DeleteLink] @@ -487,6 +691,7 @@ Main = ['Thread Expansion', ExpandThread] ['Favicon', Favicon] ['Unread', Unread] + ['Unread Line in Index', UnreadIndex] ['Quote Threading', QuoteThreading] ['Thread Stats', ThreadStats] ['Thread Updater', ThreadUpdater] @@ -496,11 +701,12 @@ Main = ['Index Navigation', Nav] ['Keybinds', Keybinds] ['Banner', Banner] + ['Announcements', PSA] ['Flash Features', Flash] ['Reply Pruning', ReplyPruning] - <% if (readJSON('/.tests_enabled')) { %> - ['Build Test', Build.Test] - <% } %> + ['Mod Contact Links', ModContact] ] -return Main +<% if (readJSON('/.tests_enabled')) { %> +Main.features.push ['Build Test', Test] +<% } %> diff --git a/src/meta/eventPage.coffee b/src/meta/eventPage.coffee index e5a107dc04..a0e0055fdd 100644 --- a/src/meta/eventPage.coffee +++ b/src/meta/eventPage.coffee @@ -4,25 +4,43 @@ chrome.runtime.onMessage.addListener (request, sender, sendResponse) -> id = requestID requestID++ sendResponse id + handlers[request.type] request, (response) -> + chrome.tabs.sendMessage sender.tab.id, {id, data: response} - xhr = new XMLHttpRequest() - xhr.open 'GET', request.url, true - xhr.responseType = request.responseType - xhr.addEventListener 'load', -> - if @readyState is @DONE && xhr.status is 200 - contentType = @getResponseHeader 'Content-Type' - contentDisposition = @getResponseHeader 'Content-Disposition' - {response} = @ - if request.responseType is 'arraybuffer' +handlers = + permission: (request, cb) -> + origins = request.origins or ['*://*/'] + chrome.permissions.contains {origins}, (result) -> + if result + cb result + else + chrome.permissions.request {origins}, (result) -> + if chrome.runtime.lastError + cb false + else + cb result + + ajax: (request, cb) -> + xhr = new XMLHttpRequest() + xhr.open 'GET', request.url, true + xhr.responseType = request.responseType + xhr.timeout = request.timeout + for key, value of (request.headers or {}) + xhr.setRequestHeader key, value + xhr.addEventListener 'load', -> + {status, statusText, response} = @ + responseHeaderString = @getAllResponseHeaders() + if response and request.responseType is 'arraybuffer' response = [new Uint8Array(response)...] - chrome.tabs.sendMessage sender.tab.id, {id, response, contentType, contentDisposition} - else - chrome.tabs.sendMessage sender.tab.id, {id, error: true} - , false - xhr.addEventListener 'error', -> - chrome.tabs.sendMessage sender.tab.id, {id, error: true} - , false - xhr.addEventListener 'abort', -> - chrome.tabs.sendMessage sender.tab.id, {id, error: true} - , false - xhr.send() + cb {status, statusText, response, responseHeaderString} + , false + xhr.addEventListener 'error', -> + cb {error: true} + , false + xhr.addEventListener 'abort', -> + cb {error: true} + , false + xhr.addEventListener 'timeout', -> + cb {error: true} + , false + xhr.send() diff --git a/src/meta/jshint.json b/src/meta/jshint.json index 9c2d3e8208..1b58dc59c8 100644 --- a/src/meta/jshint.json +++ b/src/meta/jshint.json @@ -16,11 +16,15 @@ "globals": { "MediaError": false, "Set": false, + "Promise": false, + "BroadcastChannel": false, "GM_info": false, "cloneInto": false, + "XPCNativeWrapper": false, "unsafeWindow": false, - "chrome": false<%= - meta.grants.map(x => `,\n "${x}": false`).join('') + "chrome": false, + "GM": false<%= + meta.grants.filter(x => !/\./.test(x)).map(x => `,\n "${x}": false`).join('') %><%= read('/tmp/declaration.js').match(/^var (.*);/)[1].split(', ').map(x => `,\n "${x}": true`).join('') %><%= diff --git a/src/meta/manifest.json b/src/meta/manifest.json index 6685c40110..1c82e9c8e0 100644 --- a/src/meta/manifest.json +++ b/src/meta/manifest.json @@ -10,7 +10,7 @@ }, "content_scripts": [{ "js": ["script.js"], - "matches": <%= JSON.stringify(meta.matches) %>, + "matches": <%= JSON.stringify(meta.matches_only.concat(meta.matches, meta.matches_extra)) %>, "exclude_matches": <%= JSON.stringify(meta.exclude_matches) %>, "all_frames": true, "run_at": "document_start" @@ -23,9 +23,14 @@ <% if (channel !== '-noupdate') { %> "update_url": "<%= meta.downloads %>updates<%= channel %>.xml", "key": "<%= meta.appid %>", <% } %> "minimum_chrome_version": "<%= meta.min.chrome %>", - "permissions": [ - "storage", - "http://*/", - "https://*/" - ] + "permissions": <%= JSON.stringify(meta.matches_only.concat(meta.matches, ["storage"])) %>, + "optional_permissions": [ + "*://*/" + ], + "applications": { + "gecko": { + "id": "<%= meta.appidGecko %>"<% if (channel !== '-noupdate') { %>, + "update_url": "<%= meta.downloads %>updates<%= channel %>.json" +<% } %> } + } } diff --git a/src/meta/metadata.js b/src/meta/metadata.js index dde2cff794..f154139e15 100644 --- a/src/meta/metadata.js +++ b/src/meta/metadata.js @@ -25,7 +25,7 @@ return expand(matches, /^\*/, ['http', 'https']); } return [].concat( - expandMatches(meta.matches).map(function(match) { + expandMatches(meta.includes_only.concat(meta.matches, meta.matches_extra)).map(function(match) { return '// @include ' + match; }), expandMatches(meta.exclude_matches).map(function(match) { @@ -34,7 +34,22 @@ ).join('\n'); })() %> -// @connect i.4cdn.org +// @connect 4chan.org +// @connect 4channel.org +// @connect 4cdn.org +// @connect 4chenz.github.io +<%= + readJSON('/src/Archive/archives.json').map(function(archive) { + return '// @connect ' + archive.domain; + }).join('\n') +%> +// @connect api.clyp.it +// @connect api.dailymotion.com +// @connect api.github.com +// @connect soundcloud.com +// @connect api.streamable.com +// @connect vimeo.com +// @connect www.youtube.com // @connect * <%= meta.grants.map(function(grant) { diff --git a/src/meta/npm-shrinkwrap.json b/src/meta/npm-shrinkwrap.json deleted file mode 100644 index 4a6d8e1898..0000000000 --- a/src/meta/npm-shrinkwrap.json +++ /dev/null @@ -1,823 +0,0 @@ -{ - "name": "4chan-X", - "dependencies": { - "ansi-regex": { - "version": "2.0.0", - "from": "ansi-regex@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" - }, - "ansi-styles": { - "version": "2.2.1", - "from": "ansi-styles@>=2.2.1 <3.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" - }, - "archiver": { - "version": "0.8.1", - "from": "archiver@>=0.8.0 <0.9.0", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-0.8.1.tgz" - }, - "argparse": { - "version": "1.0.7", - "from": "argparse@>=1.0.7 <2.0.0", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.7.tgz" - }, - "asap": { - "version": "2.0.4", - "from": "asap@>=2.0.3 <2.1.0", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.4.tgz" - }, - "asn1": { - "version": "0.2.3", - "from": "asn1@0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz" - }, - "assert-plus": { - "version": "0.2.0", - "from": "assert-plus@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz" - }, - "async": { - "version": "1.5.2", - "from": "async@>=1.5.2 <2.0.0", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz" - }, - "aws-sign2": { - "version": "0.6.0", - "from": "aws-sign2@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz" - }, - "aws4": { - "version": "1.4.1", - "from": "aws4@>=1.2.1 <2.0.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.4.1.tgz" - }, - "balanced-match": { - "version": "0.4.1", - "from": "balanced-match@>=0.4.1 <0.5.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.1.tgz" - }, - "bl": { - "version": "1.1.2", - "from": "bl@>=1.1.2 <1.2.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", - "dependencies": { - "isarray": { - "version": "1.0.0", - "from": "isarray@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" - }, - "readable-stream": { - "version": "2.0.6", - "from": "readable-stream@>=2.0.5 <2.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" - } - } - }, - "bluebird": { - "version": "2.10.2", - "from": "bluebird@>=2.3.0 <3.0.0", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.10.2.tgz" - }, - "boom": { - "version": "2.10.1", - "from": "boom@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" - }, - "brace-expansion": { - "version": "1.1.4", - "from": "brace-expansion@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.4.tgz" - }, - "buffer-crc32": { - "version": "0.2.5", - "from": "buffer-crc32@>=0.2.1 <0.3.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.5.tgz" - }, - "caseless": { - "version": "0.11.0", - "from": "caseless@>=0.11.0 <0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz" - }, - "chalk": { - "version": "1.1.3", - "from": "chalk@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" - }, - "cli": { - "version": "0.6.6", - "from": "cli@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/cli/-/cli-0.6.6.tgz" - }, - "coffee-script": { - "version": "1.9.3", - "from": "coffee-script@1.9.3", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.9.3.tgz" - }, - "combined-stream": { - "version": "1.0.5", - "from": "combined-stream@>=1.0.5 <1.1.0", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz" - }, - "commander": { - "version": "2.9.0", - "from": "commander@>=2.5.0 <3.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz" - }, - "concat-map": { - "version": "0.0.1", - "from": "concat-map@0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" - }, - "console-browserify": { - "version": "1.1.0", - "from": "console-browserify@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz" - }, - "core-util-is": { - "version": "1.0.2", - "from": "core-util-is@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" - }, - "crc32-stream": { - "version": "0.2.0", - "from": "crc32-stream@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-0.2.0.tgz" - }, - "crx": { - "version": "3.0.3", - "from": "crx@>=3.0.3 <4.0.0", - "resolved": "https://registry.npmjs.org/crx/-/crx-3.0.3.tgz" - }, - "cryptiles": { - "version": "2.0.5", - "from": "cryptiles@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz" - }, - "dashdash": { - "version": "1.14.0", - "from": "dashdash@>=1.12.0 <2.0.0", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.0.tgz", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - } - } - }, - "date-now": { - "version": "0.1.4", - "from": "date-now@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz" - }, - "debug": { - "version": "1.0.4", - "from": "debug@>=1.0.2 <1.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz" - }, - "deflate-crc32-stream": { - "version": "0.1.2", - "from": "deflate-crc32-stream@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/deflate-crc32-stream/-/deflate-crc32-stream-0.1.2.tgz" - }, - "delayed-stream": { - "version": "1.0.0", - "from": "delayed-stream@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" - }, - "dom-serializer": { - "version": "0.1.0", - "from": "dom-serializer@>=0.0.0 <1.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", - "dependencies": { - "domelementtype": { - "version": "1.1.3", - "from": "domelementtype@>=1.1.1 <1.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz" - }, - "entities": { - "version": "1.1.1", - "from": "entities@>=1.1.1 <1.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz" - } - } - }, - "domelementtype": { - "version": "1.3.0", - "from": "domelementtype@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz" - }, - "domhandler": { - "version": "2.3.0", - "from": "domhandler@>=2.3.0 <2.4.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz" - }, - "domutils": { - "version": "1.5.1", - "from": "domutils@>=1.5.0 <1.6.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz" - }, - "ecc-jsbn": { - "version": "0.1.1", - "from": "ecc-jsbn@>=0.0.1 <1.0.0", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz" - }, - "entities": { - "version": "1.0.0", - "from": "entities@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz" - }, - "es6-promise": { - "version": "2.3.0", - "from": "es6-promise@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.3.0.tgz" - }, - "escape-string-regexp": { - "version": "1.0.5", - "from": "escape-string-regexp@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" - }, - "esprima": { - "version": "2.7.2", - "from": "esprima@>=2.7.2 <3.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.2.tgz" - }, - "exit": { - "version": "0.1.2", - "from": "exit@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" - }, - "extend": { - "version": "3.0.0", - "from": "extend@>=3.0.0 <3.1.0", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz" - }, - "extsprintf": { - "version": "1.0.2", - "from": "extsprintf@1.0.2", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz" - }, - "file-utils": { - "version": "0.1.5", - "from": "file-utils@>=0.1.5 <0.2.0", - "resolved": "https://registry.npmjs.org/file-utils/-/file-utils-0.1.5.tgz", - "dependencies": { - "lodash": { - "version": "2.1.0", - "from": "lodash@>=2.1.0 <2.2.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.1.0.tgz" - } - } - }, - "findup-sync": { - "version": "0.1.3", - "from": "findup-sync@>=0.1.2 <0.2.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz" - }, - "font-awesome": { - "version": "4.6.3", - "from": "font-awesome@latest", - "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.6.3.tgz" - }, - "forever-agent": { - "version": "0.6.1", - "from": "forever-agent@>=0.6.1 <0.7.0", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" - }, - "form-data": { - "version": "1.0.0-rc4", - "from": "form-data@>=1.0.0-rc3 <1.1.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz" - }, - "generate-function": { - "version": "2.0.0", - "from": "generate-function@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" - }, - "generate-object-property": { - "version": "1.2.0", - "from": "generate-object-property@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz" - }, - "getpass": { - "version": "0.1.6", - "from": "getpass@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - } - } - }, - "glob": { - "version": "3.2.11", - "from": "glob@>=3.2.1 <3.3.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", - "dependencies": { - "minimatch": { - "version": "0.3.0", - "from": "minimatch@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz" - } - } - }, - "graceful-readlink": { - "version": "1.0.1", - "from": "graceful-readlink@>=1.0.0", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" - }, - "har-validator": { - "version": "2.0.6", - "from": "har-validator@>=2.0.6 <2.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz" - }, - "has-ansi": { - "version": "2.0.0", - "from": "has-ansi@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" - }, - "hawk": { - "version": "3.1.3", - "from": "hawk@>=3.1.3 <3.2.0", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz" - }, - "hoek": { - "version": "2.16.3", - "from": "hoek@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" - }, - "htmlparser2": { - "version": "3.8.3", - "from": "htmlparser2@>=3.8.0 <3.9.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", - "dependencies": { - "readable-stream": { - "version": "1.1.14", - "from": "readable-stream@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz" - } - } - }, - "http-signature": { - "version": "1.1.1", - "from": "http-signature@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz" - }, - "iconv-lite": { - "version": "0.2.11", - "from": "iconv-lite@>=0.2.11 <0.3.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz" - }, - "inflight": { - "version": "1.0.5", - "from": "inflight@>=1.0.4 <2.0.0", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.5.tgz" - }, - "inherits": { - "version": "2.0.1", - "from": "inherits@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" - }, - "is-my-json-valid": { - "version": "2.13.1", - "from": "is-my-json-valid@>=2.12.4 <3.0.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.13.1.tgz" - }, - "is-property": { - "version": "1.0.2", - "from": "is-property@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" - }, - "is-typedarray": { - "version": "1.0.0", - "from": "is-typedarray@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" - }, - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - }, - "isbinaryfile": { - "version": "0.1.9", - "from": "isbinaryfile@>=0.1.9 <0.2.0", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-0.1.9.tgz" - }, - "isstream": { - "version": "0.1.2", - "from": "isstream@>=0.1.2 <0.2.0", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" - }, - "jodid25519": { - "version": "1.0.2", - "from": "jodid25519@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz" - }, - "jsbn": { - "version": "0.1.0", - "from": "jsbn@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz" - }, - "jshint": { - "version": "2.9.2", - "from": "jshint@>=2.9.1 <3.0.0", - "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.2.tgz", - "dependencies": { - "lodash": { - "version": "3.7.0", - "from": "lodash@>=3.7.0 <3.8.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.7.0.tgz" - }, - "minimatch": { - "version": "2.0.10", - "from": "minimatch@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz" - } - } - }, - "json-schema": { - "version": "0.2.2", - "from": "json-schema@0.2.2", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz" - }, - "json-stringify-safe": { - "version": "5.0.1", - "from": "json-stringify-safe@>=5.0.1 <5.1.0", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" - }, - "jsonpointer": { - "version": "2.0.0", - "from": "jsonpointer@2.0.0", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz" - }, - "jsprim": { - "version": "1.2.2", - "from": "jsprim@>=1.2.2 <2.0.0", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.2.2.tgz" - }, - "jszip": { - "version": "3.0.0", - "from": "jszip@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.0.0.tgz", - "dependencies": { - "es6-promise": { - "version": "3.0.2", - "from": "es6-promise@>=3.0.2 <3.1.0", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz" - }, - "isarray": { - "version": "1.0.0", - "from": "isarray@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" - }, - "readable-stream": { - "version": "2.0.6", - "from": "readable-stream@>=2.0.6 <2.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" - } - } - }, - "lazystream": { - "version": "0.1.0", - "from": "lazystream@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-0.1.0.tgz" - }, - "linkify-it": { - "version": "1.2.4", - "from": "linkify-it@>=1.2.2 <1.3.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-1.2.4.tgz" - }, - "lodash": { - "version": "2.4.2", - "from": "lodash@>=2.4.1 <2.5.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz" - }, - "lodash._reinterpolate": { - "version": "3.0.0", - "from": "lodash._reinterpolate@>=3.0.0 <3.1.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz" - }, - "lodash.assigninwith": { - "version": "4.0.7", - "from": "lodash.assigninwith@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash.assigninwith/-/lodash.assigninwith-4.0.7.tgz" - }, - "lodash.escape": { - "version": "4.0.0", - "from": "lodash.escape@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.0.tgz" - }, - "lodash.keys": { - "version": "4.0.7", - "from": "lodash.keys@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-4.0.7.tgz" - }, - "lodash.keysin": { - "version": "4.1.4", - "from": "lodash.keysin@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash.keysin/-/lodash.keysin-4.1.4.tgz" - }, - "lodash.rest": { - "version": "4.0.3", - "from": "lodash.rest@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash.rest/-/lodash.rest-4.0.3.tgz" - }, - "lodash.template": { - "version": "4.2.5", - "from": "lodash.template@latest", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.2.5.tgz" - }, - "lodash.templatesettings": { - "version": "4.0.1", - "from": "lodash.templatesettings@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.0.1.tgz" - }, - "lodash.tostring": { - "version": "4.1.3", - "from": "lodash.tostring@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash.tostring/-/lodash.tostring-4.1.3.tgz" - }, - "lru-cache": { - "version": "2.7.3", - "from": "lru-cache@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz" - }, - "markdown-it": { - "version": "6.0.5", - "from": "markdown-it@>=6.0.2 <7.0.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-6.0.5.tgz", - "dependencies": { - "entities": { - "version": "1.1.1", - "from": "entities@>=1.1.1 <1.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz" - } - } - }, - "markdown-it-anchor": { - "version": "2.5.0", - "from": "markdown-it-anchor@latest", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-2.5.0.tgz" - }, - "mdurl": { - "version": "1.0.1", - "from": "mdurl@>=1.0.1 <1.1.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz" - }, - "mime-db": { - "version": "1.23.0", - "from": "mime-db@>=1.23.0 <1.24.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz" - }, - "mime-types": { - "version": "2.1.11", - "from": "mime-types@>=2.1.7 <2.2.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz" - }, - "minimatch": { - "version": "0.2.14", - "from": "minimatch@>=0.2.12 <0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz" - }, - "ms": { - "version": "0.6.2", - "from": "ms@0.6.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz" - }, - "node-rsa": { - "version": "0.2.30", - "from": "node-rsa@>=0.2.10 <0.3.0", - "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-0.2.30.tgz", - "dependencies": { - "lodash": { - "version": "3.3.0", - "from": "lodash@3.3.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.3.0.tgz" - } - } - }, - "node-uuid": { - "version": "1.4.7", - "from": "node-uuid@>=1.4.7 <1.5.0", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz" - }, - "oauth-sign": { - "version": "0.8.2", - "from": "oauth-sign@>=0.8.1 <0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz" - }, - "once": { - "version": "1.3.3", - "from": "once@>=1.3.0 <2.0.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz" - }, - "open": { - "version": "0.0.5", - "from": "open@0.0.5", - "resolved": "https://registry.npmjs.org/open/-/open-0.0.5.tgz" - }, - "os-tmpdir": { - "version": "1.0.1", - "from": "os-tmpdir@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.1.tgz" - }, - "pako": { - "version": "1.0.1", - "from": "pako@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.1.tgz" - }, - "path-is-absolute": { - "version": "1.0.0", - "from": "path-is-absolute@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz" - }, - "pinkie": { - "version": "2.0.4", - "from": "pinkie@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" - }, - "pinkie-promise": { - "version": "2.0.1", - "from": "pinkie-promise@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" - }, - "process-nextick-args": { - "version": "1.0.7", - "from": "process-nextick-args@>=1.0.6 <1.1.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" - }, - "q": { - "version": "1.4.1", - "from": "q@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz" - }, - "qs": { - "version": "6.1.0", - "from": "qs@>=6.1.0 <6.2.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.1.0.tgz" - }, - "readable-stream": { - "version": "1.0.34", - "from": "readable-stream@>=1.0.24 <1.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz" - }, - "request": { - "version": "2.72.0", - "from": "request@latest", - "resolved": "https://registry.npmjs.org/request/-/request-2.72.0.tgz" - }, - "request-promise": { - "version": "2.0.1", - "from": "request-promise@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-2.0.1.tgz", - "dependencies": { - "lodash": { - "version": "4.13.1", - "from": "lodash@>=4.5.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.13.1.tgz" - } - } - }, - "rimraf": { - "version": "2.2.8", - "from": "rimraf@>=2.2.2 <2.3.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz" - }, - "shelljs": { - "version": "0.3.0", - "from": "shelljs@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz" - }, - "sigmund": { - "version": "1.0.1", - "from": "sigmund@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz" - }, - "sntp": { - "version": "1.0.9", - "from": "sntp@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" - }, - "sprintf-js": { - "version": "1.0.3", - "from": "sprintf-js@>=1.0.2 <1.1.0", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" - }, - "sshpk": { - "version": "1.8.3", - "from": "sshpk@>=1.7.0 <2.0.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.8.3.tgz", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - } - } - }, - "string": { - "version": "3.3.1", - "from": "string@>=3.0.1 <4.0.0", - "resolved": "https://registry.npmjs.org/string/-/string-3.3.1.tgz" - }, - "string_decoder": { - "version": "0.10.31", - "from": "string_decoder@>=0.10.0 <0.11.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" - }, - "stringstream": { - "version": "0.0.5", - "from": "stringstream@>=0.0.4 <0.1.0", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz" - }, - "strip-ansi": { - "version": "3.0.1", - "from": "strip-ansi@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" - }, - "strip-json-comments": { - "version": "1.0.4", - "from": "strip-json-comments@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz" - }, - "supports-color": { - "version": "2.0.0", - "from": "supports-color@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" - }, - "temp": { - "version": "0.8.3", - "from": "temp@>=0.8.1 <0.9.0", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz" - }, - "tough-cookie": { - "version": "2.2.2", - "from": "tough-cookie@>=2.2.0 <2.3.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz" - }, - "tunnel-agent": { - "version": "0.4.3", - "from": "tunnel-agent@>=0.4.1 <0.5.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz" - }, - "tweetnacl": { - "version": "0.13.3", - "from": "tweetnacl@>=0.13.0 <0.14.0", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.13.3.tgz" - }, - "uc.micro": { - "version": "1.0.1", - "from": "uc.micro@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.1.tgz" - }, - "util-deprecate": { - "version": "1.0.2", - "from": "util-deprecate@>=1.0.1 <1.1.0", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" - }, - "verror": { - "version": "1.3.6", - "from": "verror@1.3.6", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz" - }, - "webstore-upload": { - "version": "0.0.7", - "from": "webstore-upload@latest", - "resolved": "https://registry.npmjs.org/webstore-upload/-/webstore-upload-0.0.7.tgz", - "dependencies": { - "glob": { - "version": "7.0.3", - "from": "glob@>=7.0.0 <8.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.3.tgz" - }, - "minimatch": { - "version": "3.0.0", - "from": "minimatch@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz" - } - } - }, - "wrappy": { - "version": "1.0.2", - "from": "wrappy@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" - }, - "wrench": { - "version": "1.5.9", - "from": "wrench@>=1.5.0 <2.0.0", - "resolved": "https://registry.npmjs.org/wrench/-/wrench-1.5.9.tgz" - }, - "xtend": { - "version": "4.0.1", - "from": "xtend@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" - }, - "zip-stream": { - "version": "0.3.7", - "from": "zip-stream@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-0.3.7.tgz" - } - } -} diff --git a/src/meta/updates.json b/src/meta/updates.json new file mode 100644 index 0000000000..dc9d876f69 --- /dev/null +++ b/src/meta/updates.json @@ -0,0 +1,12 @@ +{ + "addons": { + "<%= meta.appidGecko %>": { + "updates": [ + { + "version": "<%= readJSON('/version.json').version %>", + "update_link": "<%= meta.downloads %><%= name %><%= channel %>.crx" + } + ] + } + } +} diff --git a/src/platform/$$.coffee b/src/platform/$$.coffee index 8f7b773a60..4c54223abc 100644 --- a/src/platform/$$.coffee +++ b/src/platform/$$.coffee @@ -1,2 +1,2 @@ -return (selector, root=d.body) -> +$$ = (selector, root=d.body) -> [root.querySelectorAll(selector)...] diff --git a/src/platform/$.coffee b/src/platform/$.coffee index 23cdd5dfa2..3831052b70 100644 --- a/src/platform/$.coffee +++ b/src/platform/$.coffee @@ -4,10 +4,13 @@ $ = (selector, root=d.body) -> root.querySelector selector -$.DAY = 24 * - $.HOUR = 60 * - $.MINUTE = 60 * +$.DAY = 24 * ( + $.HOUR = 60 * ( + $.MINUTE = 60 * ( $.SECOND = 1000 + ) + ) +) $.id = (id) -> d.getElementById id @@ -37,58 +40,188 @@ $.extend = (object, properties) -> object[key] = val return +$.dict = -> + Object.create(null) + +$.dict.clone = (obj) -> + if typeof obj isnt 'object' or obj is null + obj + else if obj instanceof Array + arr = [] + for i in [0...obj.length] by 1 + arr.push $.dict.clone(obj[i]) + arr + else + map = Object.create(null) + for key, val of obj + map[key] = $.dict.clone(val) + map + +$.dict.json = (str) -> + $.dict.clone(JSON.parse(str)) + +$.hasOwn = (obj, key) -> + Object::hasOwnProperty.call(obj, key) + +$.getOwn = (obj, key) -> + if Object::hasOwnProperty.call(obj, key) then obj[key] else undefined + $.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 = {} + try + pageXHR = if window.wrappedJSObject and not XMLHttpRequest.wrappedJSObject then XPCNativeWrapper window.wrappedJSObject.XMLHttpRequest else XMLHttpRequest + catch + pageXHR = XMLHttpRequest - (url, options={}, extra={}) -> - {type, whenModified, upCallbacks, form} = extra + (url, options={}) -> + options.responseType ?= 'json' + options.type or= options.form and 'post' or 'get' # 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/' - r = new XMLHttpRequest() - type or= form and 'post' or 'get' + url = url.replace /^((?:https?:)?\/\/(?:\w+\.)?(?:4chan|4channel|4cdn)\.org)\/adv\//, '$1//adv/' + <% if (type === 'crx') { %> + # XXX https://bugs.chromium.org/p/chromium/issues/detail?id=920638 + if Conf['Work around CORB Bug'] and g.SITE.software is 'yotsuba' and !options.testCORB and FormData.prototype.entries + return $.ajaxPage url, options + <% } %> + {onloadend, timeout, responseType, withCredentials, type, onprogress, form, headers} = options + r = new pageXHR() try r.open type, url, true - if whenModified - r.setRequestHeader 'If-Modified-Since', lastModified[whenModified][url] if lastModified[whenModified]?[url]? - $.on r, 'load', -> (lastModified[whenModified] or= {})[url] = r.getResponseHeader 'Last-Modified' - if /\.json$/.test url - options.responseType ?= 'json' - $.extend r, options - $.extend r.upload, upCallbacks + for key, value of (headers or {}) + r.setRequestHeader key, value + $.extend r, {onloadend, timeout, responseType, withCredentials} + $.extend r.upload, {onprogress} # 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', -> + if !Conf['Work around CORB Bug'] and r.readyState is 4 and r.status is 200 and r.statusText is '' and r.response is null + $.set 'Work around CORB Bug', (Conf['Work around CORB Bug'] = Date.now()) + <% } %> r.send form catch err # 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 - for event in ['error', 'loadend'] - r["on#{event}"] = options["on#{event}"] - $.queueTask $.event, event, null, r + r.onloadend = onloadend + $.queueTask $.event, 'error', null, r + $.queueTask $.event, 'loadend', null, r r +<% if (type === 'crx') { %> +# XXX https://bugs.chromium.org/p/chromium/issues/detail?id=920638 do -> - reqs = {} - $.cache = (url, cb, options) -> - if req = reqs[url] - if req.readyState is 4 - $.queueTask -> cb.call req, req.evt, true - else + requestID = 0 + requests = $.dict() + + $.ajaxPageInit = -> + $.global -> + window.FCX.requests = Object.create(null) + + document.addEventListener '4chanXAjax', (e) -> + {url, timeout, responseType, withCredentials, type, onprogress, form, headers, id} = e.detail + window.FCX.requests[id] = r = new XMLHttpRequest() + r.open type, url, true + for key, value of (headers or {}) + r.setRequestHeader key, value + r.responseType = if responseType is 'document' then 'text' else responseType + r.timeout = timeout + r.withCredentials = withCredentials + if onprogress + r.upload.onprogress = (e) -> + {loaded, total} = e + detail = {loaded, total, id} + document.dispatchEvent new CustomEvent '4chanXAjaxProgress', {bubbles: true, detail} + r.onloadend = -> + delete window.FCX.requests[id] + {status, statusText, response} = @ + responseHeaderString = @getAllResponseHeaders() + detail = {status, statusText, response, responseHeaderString, id} + document.dispatchEvent new CustomEvent '4chanXAjaxLoadend', {bubbles: true, detail} + # connection error or content blocker + r.onerror = -> + console.warn "4chan X failed to load: #{url}" unless r.status + if form + fd = new FormData() + for entry in form + fd.append(entry[0], entry[1]) + else + fd = null + r.send fd + , false + + document.addEventListener '4chanXAjaxAbort', (e) -> + return unless (r = window.FCX.requests[e.detail.id]) + r.abort() + , false + + $.on d, '4chanXAjaxProgress', (e) -> + return unless (req = requests[e.detail.id]) + req.upload.onprogress.call req.upload, e.detail + + $.on d, '4chanXAjaxLoadend', (e) -> + return unless (req = requests[e.detail.id]) + delete requests[e.detail.id] + if e.detail.status + for key in ['status', 'statusText', 'response', 'responseHeaderString'] + req[key] = e.detail[key] + if req.responseType is 'document' + req.response = new DOMParser().parseFromString(e.detail.response, 'text/html') + req.onloadend() + + $.ajaxPage = (url, options={}) -> + {onloadend, timeout, responseType, withCredentials, type, onprogress, form, headers} = options + id = requestID++ + requests[id] = req = new CrossOrigin.Request() + $.extend req, {responseType, onloadend} + req.upload = {onprogress} + req.abort = -> + $.event '4chanXAjaxAbort', {id} + form = Array.from(form.entries()) if form + $.event '4chanXAjax', {url, timeout, responseType, withCredentials, type, onprogress: !!onprogress, form, headers, id} + req +<% } %> + +# 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 = $.dict() +$.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 = $.dict() + if (t = $.lastModified[bucket]?[url0])? + headers['If-Modified-Since'] = t + r = (ajax or $.ajax) url, { + onloadend: -> + ($.lastModified[bucket] or= $.dict())[url0] = @getResponseHeader('Last-Modified') + cb.call @ + timeout + headers + } + r + +do -> + reqs = $.dict() + $.cache = (url, cb, options={}) -> + {ajax} = options + if (req = reqs[url]) + if req.callbacks req.callbacks.push cb + else + $.queueTask -> cb.call req, {isCached: true} return req - rm = -> delete reqs[url] - try - return unless req = $.ajax url, options - catch err - return - $.on req, 'load', (e) -> - @evt = e + onloadend = -> + unless @status + delete reqs[url] for cb in @callbacks - do (cb) => $.queueTask => cb.call @, e, false + do (cb) => $.queueTask => cb.call @, {isCached: false} delete @callbacks - $.on req, 'abort error', rm + req = (ajax or $.ajax) url, {onloadend} req.callbacks = [cb] reqs[url] = req $.cleanCache = (testf) -> @@ -98,11 +231,13 @@ do -> $.cb = checked: -> - $.set @name, @checked - Conf[@name] = @checked + if $.hasOwn(Conf, @name) + $.set @name, @checked + Conf[@name] = @checked value: -> - $.set @name, @value.trim() - Conf[@name] = @value + if $.hasOwn(Conf, @name) + $.set @name, @value.trim() + Conf[@name] = @value $.asap = (test, cb) -> if test() @@ -226,7 +361,7 @@ $.event = (event, detail, root=d) -> if detail? and typeof cloneInto is 'function' detail = cloneInto detail, d.defaultView <% } %> - root.dispatchEvent new CustomEvent event, {bubbles: true, detail} + root.dispatchEvent new CustomEvent event, {bubbles: true, cancelable: true, detail} <% if (type === 'userscript') { %> # XXX Make $.event work in Pale Moon with GM 3.x (no cloneInto function). @@ -247,16 +382,22 @@ do -> else obj $.event = (event, detail, root=d) -> - root.dispatchEvent new CustomEvent event, {bubbles: true, detail: clone detail} + root.dispatchEvent new CustomEvent event, {bubbles: true, cancelable: true, detail: clone detail} <% } %> -$.open = +$.modifiedClick = (e) -> + e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0 + <% if (type === 'userscript') { %> - if GM_openInTab? +$.open = + if GM?.openInTab? + GM.openInTab + else if GM_openInTab? GM_openInTab else (url) -> window.open url, '_blank' <% } else { %> +$.open = (url) -> window.open url, '_blank' <% } %> @@ -297,19 +438,19 @@ $.queueTask = do -> taskQueue.push arguments setTimeout execTask, 0 -$.globalEval = (code, data) -> - script = $.el 'script', - textContent: code - $.extend script.dataset, data if data - $.add (d.head or doc), script - $.rm script - $.global = (fn, data) -> if doc - $.globalEval "(#{fn})();", data + script = $.el 'script', + textContent: "(#{fn}).call(document.currentScript.dataset);" + $.extend script.dataset, data if data + $.add (d.head or doc), script + $.rm script + script.dataset else # XXX dwb - fn() + try + fn.call(data) + data $.bytesToString = (size) -> unit = 0 # Bytes @@ -341,6 +482,17 @@ $.minmax = (value, min, max) -> $.hasAudio = (video) -> video.mozHasAudio or !!video.webkitAudioDecodedByteCount +$.luma = (rgb) -> + rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114 + +$.unescape = (text) -> + return text unless text? + text.replace(/<[^>]*>/g, '').replace /&(amp|#039|quot|lt|gt|#44);/g, (c) -> + (({'&': '&', ''': "'", '"': '"', '<': '<', '>': '>', ',': ','})[c]) + +$.isImage = (url) -> /\.(jpe?g|jfif|png|gif|bmp|webp|avif|jxl)$/i.test url +$.isVideo = (url) -> /\.(webm|mp4|ogv)$/i.test url + $.engine = do -> return 'edge' if /Edge\//.test navigator.userAgent return 'blink' if /Chrome\//.test navigator.userAgent @@ -349,29 +501,42 @@ $.engine = do -> $.platform = '<%= type %>'; -try - localStorage.getItem 'x' - $.hasStorage = true -catch - $.hasStorage = false +$.hasStorage = do -> + try + return true if localStorage.getItem(g.NAMESPACE + 'hasStorage') is 'true' + localStorage.setItem(g.NAMESPACE + 'hasStorage', 'true') + return localStorage.getItem(g.NAMESPACE + 'hasStorage') is 'true' + catch + false $.item = (key, val) -> - item = {} + item = $.dict() item[key] = val item -$.syncing = {} +$.oneItemSugar = (fn) -> + (key, val, cb) -> + if typeof key is 'string' + fn $.item(key, val), cb + else + fn key, val + +$.syncing = $.dict() + +$.securityCheck = (data) -> + if location.protocol isnt 'https:' + delete data['Redirect to HTTPS'] <% if (type === 'crx') { %> # https://developer.chrome.com/extensions/storage.html $.oldValue = - local: {} - sync: {} + local: $.dict() + sync: $.dict() chrome.storage.onChanged.addListener (changes, area) -> for key of changes oldValue = $.oldValue.local[key] ? $.oldValue.sync[key] - $.oldValue[area][key] = changes[key].newValue + $.oldValue[area][key] = $.dict.clone(changes[key].newValue) newValue = $.oldValue.local[key] ? $.oldValue.sync[key] cb = $.syncing[key] if cb and JSON.stringify(newValue) isnt JSON.stringify(oldValue) @@ -381,18 +546,34 @@ $.sync = (key, cb) -> $.syncing[key] = cb $.forceSync = -> return -$.get = (key, val, cb) -> - if typeof cb is 'function' - data = $.item key, val - else - data = key - cb = val - +$.crxWorking = -> + try + if chrome.runtime.getManifest() + return true + unless $.crxWarningShown + msg = $.el 'div', + `<%= html('4chan X seems to have been updated. You will need to reload the page.') %>` + $.on $('a', msg), 'click', -> location.reload() + new Notice 'warning', msg + $.crxWarningShown = true + false + +$.get = $.oneItemSugar (data, cb) -> + return unless $.crxWorking() results = {} get = (area) -> - chrome.storage[area].get Object.keys(data), (result) -> + keys = Object.keys data + # XXX slow performance in Firefox + if $.engine is 'gecko' and area is 'sync' and keys.length > 3 + keys = null + chrome.storage[area].get keys, (result) -> + result = $.dict.clone(result) if chrome.runtime.lastError c.error chrome.runtime.lastError.message + if keys is null + result2 = $.dict() + result2[key] = val for key, val of result when $.hasOwn(data, key) + result = result2 for key of data $.oldValue[area][key] = result[key] results[area] = result @@ -405,14 +586,15 @@ $.get = (key, val, cb) -> do -> items = - local: {} - sync: {} + local: $.dict() + sync: $.dict() exceedsQuota = (key, value) -> # bytes in UTF-8 unescape(encodeURIComponent(JSON.stringify(key))).length + unescape(encodeURIComponent(JSON.stringify(value))).length > chrome.storage.sync.QUOTA_BYTES_PER_ITEM $.delete = (keys) -> + return unless $.crxWorking() if typeof keys is 'string' keys = [keys] for key in keys @@ -423,7 +605,7 @@ do -> timeout = {} setArea = (area, cb) -> - data = {} + data = $.dict() $.extend data, items[area] return if !Object.keys(data).length or timeout[area] > Date.now() chrome.storage[area].set data, -> @@ -439,24 +621,22 @@ do -> items.sync[key] = val for key, val of data when not exceedsQuota(key, val) setSync() else - chrome.storage.local.remove (key for key of data when key not of items.local) + chrome.storage.local.remove (key for key of data when not (key of items.local)) cb?() setSync = $.debounce $.SECOND, -> setArea 'sync' - $.set = (key, val, cb) -> - if typeof key is 'string' - data = $.item key, val - else - data = key - cb = val + $.set = $.oneItemSugar (data, cb) -> + return unless $.crxWorking() + $.securityCheck data $.extend items.local, data setArea 'local', cb $.clear = (cb) -> - items.local = {} - items.sync = {} + return unless $.crxWorking() + items.local = $.dict() + items.sync = $.dict() count = 2 err = null done = -> @@ -470,120 +650,163 @@ do -> # http://wiki.greasespot.net/Main_Page # https://tampermonkey.net/documentation.php -if GM_deleteValue? - $.getValue = GM_getValue - $.listValues = -> GM_listValues() # error when called if missing -else if $.hasStorage - $.getValue = (key) -> localStorage[key] - $.listValues = -> - key for key of localStorage when key[...g.NAMESPACE.length] is g.NAMESPACE -else - $.getValue = -> - $.listValues = -> [] - -if GM_addValueChangeListener? - $.setValue = GM_setValue - $.deleteValue = GM_deleteValue -else if GM_deleteValue? - $.oldValue = {} - $.setValue = (key, val) -> - GM_setValue key, val - if key of $.syncing - $.oldValue[key] = val - localStorage[key] = val if $.hasStorage # for `storage` events - $.deleteValue = (key) -> - GM_deleteValue key - if key of $.syncing - delete $.oldValue[key] - localStorage.removeItem key if $.hasStorage # for `storage` events - $.cantSync = true if !$.hasStorage -else if $.hasStorage - $.oldValue = {} - $.setValue = (key, val) -> - $.oldValue[key] = val if key of $.syncing - localStorage[key] = val - $.deleteValue = (key) -> - delete $.oldValue[key] if key of $.syncing - localStorage.removeItem key -else - $.setValue = -> - $.deleteValue = -> - $.cantSync = $.cantSet = true -if GM_addValueChangeListener? - $.sync = (key, cb) -> - $.syncing[key] = GM_addValueChangeListener g.NAMESPACE + key, (key2, oldValue, newValue, remote) -> - if remote - newValue = JSON.parse newValue unless newValue is undefined - cb newValue, key - $.forceSync = -> -else if GM_deleteValue? or $.hasStorage +if GM?.deleteValue? and window.BroadcastChannel and not GM_addValueChangeListener? + + $.syncChannel = new BroadcastChannel(g.NAMESPACE + 'sync') + + $.on $.syncChannel, 'message', (e) -> + for key, val of e.data when (cb = $.syncing[key]) + cb $.dict.json(JSON.stringify(val)), key + $.sync = (key, cb) -> - key = g.NAMESPACE + key $.syncing[key] = cb - $.oldValue[key] = $.getValue key - - do -> - onChange = ({key, newValue}) -> - return unless cb = $.syncing[key] - if newValue? - return if newValue is $.oldValue[key] - $.oldValue[key] = newValue - cb JSON.parse(newValue), key[g.NAMESPACE.length..] - else - return unless $.oldValue[key]? - delete $.oldValue[key] - cb undefined, key[g.NAMESPACE.length..] - $.on window, 'storage', onChange - $.forceSync = (key) -> - # Storage events don't work across origins - # e.g. http://boards.4chan.org and https://boards.4chan.org - # so force a check for changes to avoid lost data. - key = g.NAMESPACE + key - onChange {key, newValue: $.getValue key} -else - $.sync = -> $.forceSync = -> -$.delete = (keys) -> - unless keys instanceof Array - keys = [keys] - for key in keys - $.deleteValue g.NAMESPACE + key - return + $.delete = (keys, cb) -> + unless keys instanceof Array + keys = [keys] + Promise.all(GM.deleteValue(g.NAMESPACE + key) for key in keys).then -> + items = $.dict() + items[key] = undefined for key in keys + $.syncChannel.postMessage items + cb?() + + $.get = $.oneItemSugar (items, cb) -> + keys = Object.keys items + Promise.all(GM.getValue(g.NAMESPACE + key) for key in keys).then (values) -> + for val, i in values when val + items[keys[i]] = $.dict.json val + cb items + + $.set = $.oneItemSugar (items, cb) -> + $.securityCheck items + Promise.all(GM.setValue(g.NAMESPACE + key, JSON.stringify(val)) for key, val of items).then -> + $.syncChannel.postMessage items + cb?() + + $.clear = (cb) -> + GM.listValues().then((keys) -> + $.delete keys.map((key) -> key.replace g.NAMESPACE, ''), cb + ).catch( -> + $.delete Object.keys(Conf).concat(['previousversion', 'QR Size', 'QR.persona']), cb + ) +else -$.get = (key, val, cb) -> - if typeof cb is 'function' - items = $.item key, val + unless GM_deleteValue? + $.perProtocolSettings = true + + if GM_deleteValue? + $.getValue = GM_getValue + $.listValues = -> GM_listValues() # error when called if missing + else if $.hasStorage + $.getValue = (key) -> localStorage.getItem(key) + $.listValues = -> + key for key of localStorage when key[...g.NAMESPACE.length] is g.NAMESPACE else - items = key - cb = val - $.queueTask $.getSync, items, cb - -$.getSync = (items, cb) -> - for key of items when (val2 = $.getValue g.NAMESPACE + key) - items[key] = JSON.parse val2 - cb items - -$.set = (keys, val, cb) -> - if typeof keys is 'string' - $.setValue(g.NAMESPACE + keys, JSON.stringify val) + $.getValue = -> + $.listValues = -> [] + + if GM_addValueChangeListener? + $.setValue = GM_setValue + $.deleteValue = GM_deleteValue + else if GM_deleteValue? + $.oldValue = $.dict() + $.setValue = (key, val) -> + GM_setValue key, val + if key of $.syncing + $.oldValue[key] = val + localStorage.setItem(key, val) if $.hasStorage # for `storage` events + $.deleteValue = (key) -> + GM_deleteValue key + if key of $.syncing + delete $.oldValue[key] + localStorage.removeItem key if $.hasStorage # for `storage` events + $.cantSync = true if !$.hasStorage + else if $.hasStorage + $.oldValue = $.dict() + $.setValue = (key, val) -> + $.oldValue[key] = val if key of $.syncing + localStorage.setItem(key, val) + $.deleteValue = (key) -> + delete $.oldValue[key] if key of $.syncing + localStorage.removeItem key else - for key, value of keys - $.setValue(g.NAMESPACE + key, JSON.stringify value) - cb = val - cb?() - -$.clear = (cb) -> - # XXX https://github.com/greasemonkey/greasemonkey/issues/2033 - # Also support case where GM_listValues is not defined. - $.delete Object.keys(Conf) - $.delete ['previousversion', 'AutoWatch', 'QR Size', 'captchas', 'QR.persona', 'hiddenPSA'] - $.delete ("#{id}.position" for id in ['embedding', 'updater', 'thread-stats', 'thread-watcher', 'qr']) - try - $.delete $.listValues().map (key) -> key.replace g.NAMESPACE, '' - cb?() -<% } %> + $.setValue = -> + $.deleteValue = -> + $.cantSync = $.cantSet = true + + if GM_addValueChangeListener? + $.sync = (key, cb) -> + $.syncing[key] = GM_addValueChangeListener g.NAMESPACE + key, (key2, oldValue, newValue, remote) -> + if remote + newValue = $.dict.json newValue unless newValue is undefined + cb newValue, key + $.forceSync = -> + else if GM_deleteValue? or $.hasStorage + $.sync = (key, cb) -> + key = g.NAMESPACE + key + $.syncing[key] = cb + $.oldValue[key] = $.getValue key + + do -> + onChange = ({key, newValue}) -> + return if not (cb = $.syncing[key]) + if newValue? + return if newValue is $.oldValue[key] + $.oldValue[key] = newValue + cb $.dict.json(newValue), key[g.NAMESPACE.length..] + else + return unless $.oldValue[key]? + delete $.oldValue[key] + cb undefined, key[g.NAMESPACE.length..] + $.on window, 'storage', onChange + + $.forceSync = (key) -> + # Storage events don't work across origins + # e.g. http://boards.4chan.org and https://boards.4chan.org + # so force a check for changes to avoid lost data. + key = g.NAMESPACE + key + onChange {key, newValue: $.getValue key} + else + $.sync = -> + $.forceSync = -> -return $ + $.delete = (keys) -> + unless keys instanceof Array + keys = [keys] + for key in keys + $.deleteValue g.NAMESPACE + key + return + + $.get = $.oneItemSugar (items, cb) -> + $.queueTask $.getSync, items, cb + + $.getSync = (items, cb) -> + for key of items when (val2 = $.getValue g.NAMESPACE + key) + try + items[key] = $.dict.json val2 + catch err + # XXX https://github.com/ccd0/4chan-x/issues/2218 + unless /^(?:undefined)*$/.test(val2) + throw err + cb items + + $.set = $.oneItemSugar (items, cb) -> + $.securityCheck items + $.queueTask -> + for key, value of items + $.setValue(g.NAMESPACE + key, JSON.stringify value) + cb?() + + $.clear = (cb) -> + # XXX https://github.com/greasemonkey/greasemonkey/issues/2033 + # Also support case where GM_listValues is not defined. + $.delete Object.keys(Conf) + $.delete ['previousversion', 'QR Size', 'QR.persona'] + try + $.delete $.listValues().map (key) -> key.replace g.NAMESPACE, '' + cb?() + +<% } %> diff --git a/src/platform/CrossOrigin.coffee b/src/platform/CrossOrigin.coffee index d4e9bcd06c..2063d5f1f9 100644 --- a/src/platform/CrossOrigin.coffee +++ b/src/platform/CrossOrigin.coffee @@ -1,124 +1,182 @@ <% if (type === 'crx') { %> eventPageRequest = do -> callbacks = [] - chrome.runtime.onMessage.addListener (data) -> - callbacks[data.id] data - delete callbacks[data.id] - (url, responseType, cb) -> - chrome.runtime.sendMessage {url, responseType}, (id) -> + chrome.runtime.onMessage.addListener (response) -> + callbacks[response.id] response.data + delete callbacks[response.id] + (params, cb) -> + chrome.runtime.sendMessage params, (id) -> callbacks[id] = cb <% } %> CrossOrigin = - binary: (url, cb, headers={}) -> + binary: (url, cb, headers=$.dict()) -> # 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+\.)?(?:4chan|4channel|4cdn)\.org)\/adv\//, '$1//adv/' <% if (type === 'crx') { %> - if /^https:\/\//.test(url) or location.protocol is 'http:' - xhr = new XMLHttpRequest() - xhr.open 'GET', url, true - xhr.setRequestHeader key, value for key, value of headers - xhr.responseType = 'arraybuffer' - xhr.onload = -> - return cb null unless @readyState is @DONE and @status in [200, 206] - contentType = @getResponseHeader 'Content-Type' - contentDisposition = @getResponseHeader 'Content-Disposition' - cb new Uint8Array(@response), contentType, contentDisposition - xhr.onerror = xhr.onabort = -> - cb null - xhr.send() - else - eventPageRequest url, 'arraybuffer', ({response, contentType, contentDisposition, error}) -> - return cb null if error - cb new Uint8Array(response), contentType, contentDisposition + eventPageRequest {type: 'ajax', url, headers, responseType: 'arraybuffer'}, ({response, responseHeaderString}) -> + response = new Uint8Array(response) if response + cb response, responseHeaderString <% } %> <% 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). - 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 = + fallback = -> + $.ajax url, { + headers + responseType: 'arraybuffer' + onloadend: -> + if @status and @response + cb new Uint8Array(@response), @getAllResponseHeaders() + else + cb null + } + unless GM?.xmlHttpRequest? or GM_xmlhttpRequest? + fallback() + return + gmOptions = method: "GET" url: url headers: headers + responseType: 'arraybuffer' + overrideMimeType: 'text/plain; charset=x-user-defined' onload: (xhr) -> - if workaround + if xhr.response instanceof ArrayBuffer + data = new Uint8Array xhr.response + else r = xhr.responseText data = new Uint8Array r.length i = 0 while i < r.length data[i] = r.charCodeAt i i++ - else - 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 + cb data, xhr.responseHeaders onerror: -> cb null onabort: -> cb null - if workaround - options.overrideMimeType = 'text/plain; charset=x-user-defined' - else - options.responseType = 'arraybuffer' - GM_xmlhttpRequest options + try + (GM?.xmlHttpRequest or GM_xmlhttpRequest) gmOptions + catch + fallback() <% } %> file: (url, cb) -> - CrossOrigin.binary url, (data, contentType, contentDisposition) -> + CrossOrigin.binary url, (data, headers) -> 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' match = contentDisposition?.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)?[1] or contentType?.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)?[1] if match name = match.replace /\\"/g, '"' - if GM_info?.script?.includeJSB? - # 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' + if /^text\/plain;\s*charset=x-user-defined$/i.test(mime) + # In JS Blocker (Safari) content type comes back as 'text/plain; charset=x-user-defined'; guess from filename instead. + mime = $.getOwn(QR.typeFromExtension, name.match(/[^.]*$/)[0].toLowerCase()) or 'application/octet-stream' blob = new Blob([data], {type: mime}) blob.name = name cb blob - json: do -> - callbacks = {} - responses = {} - (url, cb) -> - <% if (type === 'crx') { %> - if /^https:\/\//.test(url) or location.protocol is 'http:' - return $.cache url, (-> cb @response), responseType: 'json' - <% } %> - if responses[url] - cb responses[url] - return - if callbacks[url] - callbacks[url].push cb - return - callbacks[url] = [cb] - <% if (type === 'userscript') { %> - GM_xmlhttpRequest - method: "GET" - url: url+'' - onload: (xhr) -> - response = JSON.parse xhr.responseText - cb response for cb in callbacks[url] - delete callbacks[url] - responses[url] = response - onerror: -> - delete callbacks[url] - onabort: -> - delete callbacks[url] - <% } %> - <% if (type === 'crx') { %> - eventPageRequest url, 'json', ({response, error}) -> - if error - delete callbacks[url] - else - cb response for cb in callbacks[url] - delete callbacks[url] - responses[url] = response - <% } %> + Request: class Request + status: 0 + statusText: '' + response: null + responseHeaderString: null + getResponseHeader: (headerName) -> + if !@responseHeaders? and @responseHeaderString? + @responseHeaders = $.dict() + 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?[headerName.toLowerCase()] ? null + abort: -> + onloadend: -> + + # Attempts to fetch `url` using cross-origin privileges, if available. + # Interface is a subset of that of $.ajax. + # Options: + # `onloadend` - called with the returned object as `this` on success or error/abort/timeout. + # `timeout` - time limit for request + # `responseType` - expected response type, 'json' by default; 'json' and 'text' supported + # `headers` - request headers + # Returned object properties: + # `status` - HTTP status (0 if connection not successful) + # `statusText` - HTTP status text + # `response` - decoded response body + # `abort` - function for aborting the request (silently fails on some platforms) + # `getResponseHeader` - function for reading response headers + ajax: (url, options={}) -> + {onloadend, timeout, responseType, headers} = options + responseType ?= 'json' + + <% if (type === 'userscript') { %> + unless GM?.xmlHttpRequest? or GM_xmlhttpRequest? + return $.ajax url, options + <% } %> + + req = new CrossOrigin.Request() + req.onloadend = onloadend + + <% if (type === 'userscript') { %> + gmOptions = { + method: 'GET' + url + headers + timeout + onload: (xhr) -> + try + response = switch responseType + when 'json' + if xhr.responseText then JSON.parse(xhr.responseText) else null + else + xhr.responseText + $.extend req, { + response + status: xhr.status + statusText: xhr.statusText + responseHeaderString: xhr.responseHeaders + } + req.onloadend() + onerror: -> req.onloadend() + onabort: -> req.onloadend() + ontimeout: -> req.onloadend() + } + try + gmReq = (GM?.xmlHttpRequest or GM_xmlhttpRequest) gmOptions + catch + return $.ajax url, options + + if gmReq and typeof gmReq.abort is 'function' + req.abort = -> + try + gmReq.abort() + <% } %> + + <% if (type === 'crx') { %> + eventPageRequest {type: 'ajax', url, responseType, headers, timeout}, (result) -> + if result.status + $.extend req, result + req.onloadend() + <% } %> + + req + + cache: (url, cb) -> + $.cache url, cb, + ajax: CrossOrigin.ajax -return CrossOrigin + <% if (type === 'crx') { %> + permission: (cb, cbFail, origins) -> + eventPageRequest {type: 'permission', origins}, (result) -> + if result + cb() + else + cbFail() + <% } %> + <% if (type === 'userscript') { %> + permission: (cb) -> + cb() + <% } %> diff --git a/src/site/SW.js b/src/site/SW.js new file mode 100644 index 0000000000..120a971ba3 --- /dev/null +++ b/src/site/SW.js @@ -0,0 +1 @@ +SW = {}; diff --git a/src/site/SW.tinyboard.coffee b/src/site/SW.tinyboard.coffee new file mode 100644 index 0000000000..4290193e34 --- /dev/null +++ b/src/site/SW.tinyboard.coffee @@ -0,0 +1,233 @@ +SW.tinyboard = + isOPContainerThread: true + mayLackJSON: true + threadModTimeIgnoresSage: true + + disabledFeatures: [ + 'Resurrect Quotes' + 'Quick Reply Personas' + 'Quick Reply' + 'Cooldown' + 'Report Link' + 'Delete Link' + 'Edit Link' + 'Quote Inlining' + 'Quote Previewing' + 'Quote Backlinks' + 'File Info Formatting' + 'Image Expansion' + 'Image Expansion (Menu)' + 'Comment Expansion' + 'Thread Expansion' + 'Favicon' + 'Quote Threading' + 'Thread Updater' + 'Banner' + 'Flash Features' + 'Reply Pruning' + ] + + detect: -> + for script in $$ 'script:not([src])', d.head + if (m = script.textContent.match(/\bvar configRoot=(".*?")/)) + properties = $.dict() + try + root = JSON.parse m[1] + if root[0] is '/' + properties.root = location.origin + root + else if /^https?:/.test(root) + properties.root = root + return properties + false + + awaitBoard: (cb) -> + if (reactUI = $.id('react-ui')) + s = @selectors = Object.create @selectors + s.boardFor = {index: '.page-container'} + s.thread = 'div[id^="thread_"]' + Main.mounted cb + else + cb() + + urls: + thread: ({siteID, boardID, threadID}, isArchived) -> + "#{Conf['siteProperties'][siteID]?.root or "http://#{siteID}/"}#{boardID}/#{if isArchived then 'archive/' else ''}res/#{threadID}.html" + post: ({postID}) -> "##{postID}" + index: ({siteID, boardID}) -> "#{Conf['siteProperties'][siteID]?.root or "http://#{siteID}/"}#{boardID}/" + catalog: ({siteID, boardID}) -> "#{Conf['siteProperties'][siteID]?.root or "http://#{siteID}/"}#{boardID}/catalog.html" + threadJSON: ({siteID, boardID, threadID}, isArchived) -> + root = Conf['siteProperties'][siteID]?.root + if root then "#{root}#{boardID}/#{if isArchived then 'archive/' else ''}res/#{threadID}.json" else '' + archivedThreadJSON: (thread) -> + SW.tinyboard.urls.threadJSON thread, true + threadsListJSON: ({siteID, boardID}) -> + root = Conf['siteProperties'][siteID]?.root + if root then "#{root}#{boardID}/threads.json" else '' + archiveListJSON: ({siteID, boardID}) -> + root = Conf['siteProperties'][siteID]?.root + if root then "#{root}#{boardID}/archive/archive.json" else '' + catalogJSON: ({siteID, boardID}) -> + root = Conf['siteProperties'][siteID]?.root + if root then "#{root}#{boardID}/catalog.json" else '' + file: ({siteID, boardID}, filename) -> + "#{Conf['siteProperties'][siteID]?.root or "http://#{siteID}/"}#{boardID}/#{filename}" + thumb: (board, filename) -> + SW.tinyboard.urls.file board, filename + + selectors: + board: 'form[name="postcontrols"]' + thread: 'input[name="board"] ~ div[id^="thread_"]' + threadDivider: 'div[id^="thread_"] > hr:last-child' + summary: '.omitted' + postContainer: 'div[id^="reply_"]:not(.hidden)' # postContainer is thread for OP + opBottom: '.op' + replyOriginal: 'div[id^="reply_"]:not(.hidden)' + infoRoot: '.intro' + info: + subject: '.subject' + name: '.name' + email: '.email' + tripcode: '.trip' + uniqueID: '.poster_id' + capcode: '.capcode' + flag: '.flag' + date: 'time' + nameBlock: 'label' + quote: 'a[href*="#q"]' + reply: 'a[href*="/res/"]:not([href*="#"])' + icons: + isSticky: '.fa-thumb-tack' + isClosed: '.fa-lock' + file: + text: '.fileinfo' + link: '.fileinfo > a' + thumb: 'a > .post-image' + thumbLink: '.file > a' + multifile: '.files > .file' + highlightable: + op: ' > .op' + reply: '.reply' + catalog: ' > .thread' + comment: '.body' + spoiler: '.spoiler' + quotelink: 'a[onclick*="highlightReply("]' + catalog: + board: '#Grid' + thread: '.mix' + thumb: '.thread-image' + boardList: '.boardlist' + boardListBottom: '.boardlist.bottom' + styleSheet: '#stylesheet' + psa: '.blotter' + nav: + prev: '.pages > form > [value=Previous]' + next: '.pages > form > [value=Next]' + + classes: + highlight: 'highlighted' + + xpath: + thread: 'div[starts-with(@id,"thread_")]' + postContainer: 'div[starts-with(@id,"reply_") or starts-with(@id,"thread_")]' + replyContainer: 'div[starts-with(@id,"reply_")]' + + regexp: + quotelink: + /// + / + ([^/]+) # boardID + /res/ + (\d+) # threadID + (?:\.\w+)?# + (\d+) # postID + $ + /// + quotelinkHTML: + /]*\bhref="[^"]*\/([^\/]+)\/res\/(\d+)(?:\.\w+)?#(\d+)"/g + + Build: + parseJSON: (data, board) -> + o = SW.yotsuba.Build.parseJSON(data, board) + if data.ext is 'deleted' + delete o.file + $.extend o, + files: [] + fileDeleted: true + filesDeleted: [0] + if data.extra_files + for extra_file, i in data.extra_files + if extra_file.ext is 'deleted' + o.filesDeleted.push i + else + file = SW.yotsuba.Build.parseJSONFile(data, board) + o.files.push file + if o.files.length + o.file = o.files[0] + o + + parseComment: (html) -> + html = html + .replace(//gi, '\n') + .replace(/<[^>]*>/g, '') + $.unescape html + + bgColoredEl: -> + $.el 'div', className: 'post reply' + + isFileURL: (url) -> + /\/src\/[^\/]+/.test(url.pathname) + + preParsingFixes: (board) -> + # fixes effects of unclosed link in announcement + if (broken = $('a > input[name="board"]', board)) + $.before broken.parentNode, broken + + parseNodes: (post, nodes) -> + # Add vichan's span.poster_id around the ID if not already present. + return if nodes.uniqueID + text = '' + node = nodes.nameBlock.nextSibling + while node and node.nodeType is 3 + text += node.textContent + node = node.nextSibling + if (m = text.match /(\s*ID:\s*)(\S+)/) + nodes.info.normalize() + {nextSibling} = nodes.nameBlock + nextSibling = nextSibling.splitText m[1].length + nextSibling.splitText m[2].length + nodes.uniqueID = uniqueID = $.el 'span', {className: 'poster_id'} + $.replace nextSibling, uniqueID + $.add uniqueID, nextSibling + + parseDate: (node) -> + date = Date.parse(node.getAttribute('datetime')?.trim()) + return new Date(date) unless isNaN(date) + date = Date.parse(node.textContent.trim() + ' UTC') # e.g. onesixtwo.club + return new Date(date) unless isNaN(date) + undefined + + parseFile: (post, file) -> + {text, link, thumb} = file + return false if $.x("ancestor::#{@xpath.postContainer}[1]", text) isnt post.nodes.root # file belongs to a reply + return false if not (infoNode = if '(' in link.nextSibling?.textContent then link.nextSibling else link.nextElementSibling) + return false if not (info = infoNode.textContent.match /\((.*,\s*)?([\d.]+ ?[KMG]?B).*\)/) + nameNode = $ '.postfilename', text + $.extend file, + name: if nameNode then (nameNode.title or nameNode.textContent) else link.pathname.match(/[^/]*$/)[0] + size: info[2] + dimensions: info[0].match(/\d+x\d+/)?[0] + if thumb + $.extend file, + thumbURL: if /\/static\//.test(thumb.src) and $.isImage(link.href) then link.href else thumb.src + isSpoiler: /^Spoiler/i.test(info[1] or '') or link.textContent is 'Spoiler Image' + true + + isThumbExpanded: (file) -> + # Detect old Tinyboard image expansion that changes src attribute on thumbnail. + $.hasClass(file.thumb.parentNode, 'expanded') or file.thumb.parentNode.dataset.expanded is 'true' + + isLinkified: (link) -> + /\bnofollow\b/.test(link.rel) + + catalogPin: (threadRoot) -> + threadRoot.dataset.sticky = 'true' diff --git a/src/site/SW.yotsuba.Build.coffee b/src/site/SW.yotsuba.Build.coffee new file mode 100644 index 0000000000..a13534dc81 --- /dev/null +++ b/src/site/SW.yotsuba.Build.coffee @@ -0,0 +1,271 @@ +Build = + staticPath: '//s.4cdn.org/image/' + gifIcon: if window.devicePixelRatio >= 2 then '@2x.gif' else '.gif' + spoilerRange: $.dict() + + shortFilename: (filename) -> + ext = filename.match(/\.?[^\.]*$/)[0] + if filename.length - ext.length > 30 + "#{filename.match(/(?:[\uD800-\uDBFF][\uDC00-\uDFFF]|[^]){0,25}/)[0]}(...)#{ext}" + else + filename + + spoilerThumb: (boardID) -> + if spoilerRange = Build.spoilerRange[boardID] + # Randomize the spoiler image. + "#{Build.staticPath}spoiler-#{boardID}#{Math.floor 1 + spoilerRange * Math.random()}.png" + else + "#{Build.staticPath}spoiler.png" + + sameThread: (boardID, threadID) -> + g.VIEW is 'thread' and g.BOARD.ID is boardID and g.THREADID is +threadID + + threadURL: (boardID, threadID) -> + if boardID isnt g.BOARD.ID + "//#{BoardConfig.domain(boardID)}/#{boardID}/thread/#{threadID}" + else if g.VIEW isnt 'thread' or +threadID isnt g.THREADID + "/#{boardID}/thread/#{threadID}" + else + '' + + postURL: (boardID, threadID, postID) -> + "#{Build.threadURL(boardID, threadID)}#p#{postID}" + + parseJSON: (data, {siteID, boardID}) -> + o = + # id + ID: data.no + postID: data.no + threadID: data.resto or data.no + boardID: boardID + siteID: siteID + isReply: !!data.resto + # thread status + isSticky: !!data.sticky + isClosed: !!data.closed + isArchived: !!data.archived + # file status + fileDeleted: !!data.filedeleted + filesDeleted: if data.filedeleted then [0] else [] + o.info = + subject: $.unescape data.sub + email: $.unescape data.email + name: $.unescape(data.name) or '' + tripcode: data.trip + pass: if data.since4pass? then "#{data.since4pass}" else undefined + uniqueID: data.id + flagCode: data.country + flagCodeTroll: data.board_flag + flag: $.unescape (data.country_name or data.flag_name) + dateUTC: data.time + dateText: data.now + commentHTML: {innerHTML: data.com or ''} + if data.capcode + o.info.capcode = data.capcode.replace(/_highlight$/, '').replace(/_/g, ' ').replace(/\b\w/g, (c) -> c.toUpperCase()) + o.capcodeHighlight = /_highlight$/.test data.capcode + delete o.info.uniqueID + o.files = [] + if data.ext + o.file = SW.yotsuba.Build.parseJSONFile(data, {siteID, boardID}) + o.files.push o.file + # Temporary JSON properties for events such as April 1 / Halloween + o.extra = $.dict() + for key of data when key[0] is 'x' + o.extra[key] = data[key] + o + + parseJSONFile: (data, {siteID, boardID}) -> + site = g.sites[siteID] + filename = if site.software is 'yotsuba' and boardID is 'f' + "#{encodeURIComponent data.filename}#{data.ext}" + else + "#{data.tim}#{data.ext}" + o = + name: ($.unescape data.filename) + data.ext + url: site.urls.file({siteID, boardID}, filename) + height: data.h + width: data.w + MD5: data.md5 + size: $.bytesToString data.fsize + thumbURL: site.urls.thumb({siteID, boardID}, "#{data.tim}s.jpg") + theight: data.tn_h + twidth: data.tn_w + isSpoiler: !!data.spoiler + tag: data.tag + hasDownscale: !!data.m_img + o.dimensions = "#{o.width}x#{o.height}" if data.h? and !/\.pdf$/.test(o.url) + o + + parseComment: (html) -> + html = html + .replace(//gi, '\n') + .replace(/\n\n]*>/g, '') + $.unescape html + + parseCommentDisplay: (html) -> + # Hide spoilers. + unless Conf['Remove Spoilers'] or Conf['Reveal Spoilers'] + while (html2 = html.replace /(?:(?!<\/?s>).)*<\/s>/g, '[spoiler]') isnt html + html = html2 + html = html + .replace(/^Rolled [^<]*<\/b>/i, '') # Rolls (/tg/, /qst/) + .replace(/ + o = Build.parseJSON data, {boardID, siteID: g.SITE.ID} + Build.post o + + post: (o) -> + {ID, threadID, boardID, file} = o + {subject, email, name, tripcode, capcode, pass, uniqueID, flagCode, flagCodeTroll, flag, dateUTC, dateText, commentHTML} = o.info + {staticPath, gifIcon} = Build + + ### Post Info ### + + if capcode + capcodeLC = capcode.toLowerCase() + if capcode is 'Founder' + capcodePlural = 'the Founder' + capcodeDescription = "4chan's Founder" + else if capcode is 'Verified' + capcodePlural = 'Verified Users' + capcodeDescription = '' + else + capcodeLong = $.getOwn({'Admin': 'Administrator', 'Mod': 'Moderator'}, capcode) or capcode + capcodePlural = "#{capcodeLong}s" + capcodeDescription = "a 4chan #{capcodeLong}" + + url = Build.threadURL boardID, threadID + postLink = "#{url}#p#{ID}" + quoteLink = if Build.sameThread boardID, threadID + "javascript:quote('#{+ID}');" + else + "#{url}#q#{ID}" + + postInfo = `<%= readHTML('PostInfo.html') %>` + + ### File Info ### + + if file + protocol = /^https?:(?=\/\/i\.4cdn\.org\/)/ + fileURL = file.url.replace protocol, '' + shortFilename = Build.shortFilename file.name + fileThumb = if file.isSpoiler then Build.spoilerThumb(boardID) else file.thumbURL.replace(protocol, '') + + fileBlock = `<%= readHTML('File.html') %>` + + ### Whole Post ### + + postClass = if o.isReply then 'reply' else 'op' + + wholePost = `<%= readHTML('Post.html') %>` + + container = $.el 'div', + className: "postContainer #{postClass}Container" + id: "pc#{ID}" + $.extend container, wholePost + + # Fix quotelinks + for quote in $$ '.quotelink', container + href = quote.getAttribute 'href' + if (href[0] is '#') + if !Build.sameThread(boardID, threadID) + quote.href = Build.threadURL(boardID, threadID) + href + else + if (match = quote.href.match SW.yotsuba.regexp.quotelink) and (Build.sameThread match[1], match[2]) + quote.href = href.match(/(#[^#]*)?$/)[0] or '#' + + container + + summaryText: (status, posts, files) -> + text = '' + text += "#{status} " if status + text += "#{posts} post#{if posts > 1 then 's' else ''}" + text += " and #{files} image repl#{if files > 1 then 'ies' else 'y'}" if +files + text += " #{if status is '-' then 'shown' else 'omitted'}." + + summary: (boardID, threadID, posts, files) -> + $.el 'a', + className: 'summary' + textContent: Build.summaryText '', posts, files + href: "/#{boardID}/thread/#{threadID}" + + thread: (thread, data, withReplies) -> + if (root = thread.nodes.root) + $.rmAll root + else + thread.nodes.root = root = $.el 'div', + className: 'thread' + id: "t#{data.no}" + $.add root, Build.hat.cloneNode(false) if Build.hat + $.add root, thread.OP.nodes.root + if data.omitted_posts or !withReplies and data.replies + [posts, files] = if withReplies + # XXX data.omitted_images is not accurate. + [data.omitted_posts, data.images - data.last_replies.filter((data) -> !!data.ext).length] + else + [data.replies, data.images] + summary = Build.summary thread.board.ID, data.no, posts, files + $.add root, summary + root + + catalogThread: (thread, data, pageCount) -> + {staticPath, gifIcon} = Build + {tn_w, tn_h} = data + + if data.spoiler and !Conf['Reveal Spoiler Thumbnails'] + src = "#{staticPath}spoiler" + if spoilerRange = Build.spoilerRange[thread.board] + # Randomize the spoiler image. + src += ("-#{thread.board}") + Math.floor 1 + spoilerRange * Math.random() + src += '.png' + imgClass = 'spoiler-file' + cssText = "--tn-w: 100; --tn-h: 100;" + else if data.filedeleted + src = "#{staticPath}filedeleted-res#{gifIcon}" + imgClass = 'deleted-file' + else if thread.OP.file + src = thread.OP.file.thumbURL + ratio = 250 / Math.max(tn_w, tn_h) + cssText = "--tn-w: #{tn_w * ratio}; --tn-h: #{tn_h * ratio};" + else + src = "#{staticPath}nofile.png" + imgClass = 'no-file' + + postCount = data.replies + 1 + fileCount = data.images + !!data.ext + + container = $.el 'div', `<%= readHTML('CatalogThread.html') %>` + $.before thread.OP.nodes.info, [container.childNodes...] + + for br in $$('br', thread.OP.nodes.comment) when br.previousSibling and br.previousSibling.nodeName is 'BR' + $.addClass br, 'extra-linebreak' + + root = $.el 'div', + className: 'thread catalog-thread' + id: "t#{thread}" + $.addClass root, thread.OP.highlights... if thread.OP.highlights + $.addClass root, 'noFile' unless thread.OP.file + root.style.cssText = cssText or '' + + root + + catalogReply: (thread, data) -> + excerpt = '' + if data.com + excerpt = Build.parseCommentDisplay(data.com).replace(/>>\d+/g, '').trim().replace(/\n+/g, ' // ') + if data.ext + excerpt or= "#{$.unescape data.filename}#{data.ext}" + if data.com + excerpt or= $.unescape data.com.replace(//gi, ' // ') + excerpt or= '\xA0' + excerpt = "#{excerpt[...70]}..." if excerpt.length > 73 + + link = Build.postURL thread.board.ID, thread.ID, data.no + $.el 'div', {className: 'catalog-reply'}, + `<%= readHTML('CatalogReply.html') %>` + +SW.yotsuba.Build = Build diff --git a/src/site/SW.yotsuba.Build/CatalogReply.html b/src/site/SW.yotsuba.Build/CatalogReply.html new file mode 100644 index 0000000000..6bd68c0139 --- /dev/null +++ b/src/site/SW.yotsuba.Build/CatalogReply.html @@ -0,0 +1,3 @@ +: +${excerpt} +... diff --git a/src/site/SW.yotsuba.Build/CatalogThread.html b/src/site/SW.yotsuba.Build/CatalogThread.html new file mode 100644 index 0000000000..70aacb3baa --- /dev/null +++ b/src/site/SW.yotsuba.Build/CatalogThread.html @@ -0,0 +1,14 @@ + + + +
              + + ${postCount} / + ${fileCount} / + ${pageCount} + + + ?{thread.isSticky}{} + ?{thread.isClosed}{} + +
              diff --git a/src/General/Build/File.html b/src/site/SW.yotsuba.Build/File.html similarity index 73% rename from src/General/Build/File.html rename to src/site/SW.yotsuba.Build/File.html index d48f11db29..504ae42946 100644 --- a/src/General/Build/File.html +++ b/src/site/SW.yotsuba.Build/File.html @@ -1,32 +1,33 @@ ?{file}{ -
              +
              ?{boardID === "f"}{ -
              +
              File: ${file.name} -(${file.size}, ${file.dimensions}?{file.tag}{, ${file.tag}})
              }{ -
              +
              File: ?{file.isSpoiler}{Spoiler Image}{${shortFilename}} (${file.size}, ${file.dimensions || "PDF"})
              - - + ${file.size} }
              }{ ?{o.fileDeleted}{ -
              +
              File deleted. diff --git a/src/site/SW.yotsuba.Build/Post.html b/src/site/SW.yotsuba.Build/Post.html new file mode 100644 index 0000000000..655c4ea45e --- /dev/null +++ b/src/site/SW.yotsuba.Build/Post.html @@ -0,0 +1,5 @@ +?{o.isReply}{
              >>
              } +
              + ?{o.isReply}{&{postInfo}&{fileBlock}}{&{fileBlock}&{postInfo}} +
              &{commentHTML}
              +
              diff --git a/src/General/Build/PostInfo.html b/src/site/SW.yotsuba.Build/PostInfo.html similarity index 63% rename from src/General/Build/PostInfo.html rename to src/site/SW.yotsuba.Build/PostInfo.html index a5a63851ba..9fc75a063c 100644 --- a/src/General/Build/PostInfo.html +++ b/src/site/SW.yotsuba.Build/PostInfo.html @@ -1,23 +1,25 @@ - Screenshot @@ -59,8 +60,12 @@ var engine = (function() { if (/Gecko\/|Goanna/.test(navigator.userAgent)) return 'gecko'; if (/Presto\//.test(navigator.userAgent)) return 'presto'; })(); -if (engine) { - var engines = {'firefox': 'gecko', 'chromium': 'blink presto', 'safari': 'webkit', 'webkitgtk': 'webkit', 'other-browsers': 'edge'}; +var engines = {'firefox': 'gecko', 'chromium': 'blink presto', 'safari': 'webkit', 'webkitgtk-qtwebkit-qtwebengine': 'webkit', 'ms-edge': 'edge', 'other-browsers': ''}; +if (location.hash.slice(1) in engines) { + for (browser in engines) { + document.getElementById(browser + '-hide').checked = (browser !== location.hash.slice(1)); + } +} else if (engine) { for (browser in engines) { document.getElementById(browser + '-hide').checked = (engines[browser].indexOf(engine) < 0); } diff --git a/tools/banners.py b/tools/banners.py index 192a884797..cfd3bef443 100755 --- a/tools/banners.py +++ b/tools/banners.py @@ -15,5 +15,5 @@ print(banner, status) if status == 200: banners.append(banner) -with open('src/Miscellaneous/Banner/banners.json', 'w') as f: +with open('src/config/banners.json', 'w') as f: f.write(json.dumps(banners)) diff --git a/tools/chain.js b/tools/chain.js index 504709ddb7..e3a02e44a6 100644 --- a/tools/chain.js +++ b/tools/chain.js @@ -1,6 +1,6 @@ var fs = require('fs'); var template = require('./template'); -var coffee = require('coffee-script'); +var coffee = require('coffeescript'); for (var name of process.argv.slice(2)) { try { @@ -10,8 +10,12 @@ for (var name of process.argv.slice(2)) { script = script.replace(/\r\n/g, '\n'); script = template(script, {type: parts[2]}, sourceName); if (parts[4] === 'coffee') { + var definesVar = /^[$A-Z][$\w]*$/.test(parts[3]); + if (definesVar) { + script = `${script}\nreturn ${parts[3]};\n`; + } script = coffee.compile(script); - if (/^[$A-Z][$\w]*$/.test(parts[3])) { + if (definesVar) { script = `${parts[3]} = ${script}`; } } diff --git a/tools/markdown.js b/tools/markdown.js index ef8001dfaf..2136d53e62 100644 --- a/tools/markdown.js +++ b/tools/markdown.js @@ -1,5 +1,5 @@ var fs = require('fs'); -var md = require('markdown-it')({linkify: true}).use(require('markdown-it-anchor')); +var md = require('markdown-it')({linkify: true}).use(require('markdown-it-anchor'), {slugify: s => String(s).trim().toLowerCase().replace(/\W+/g, '-')}); var template = require('lodash.template'); var readme = fs.readFileSync('README.md', 'utf8'); diff --git a/tools/pkgvars.js b/tools/pkgvars.js index 811cf73088..38259c2fb8 100644 --- a/tools/pkgvars.js +++ b/tools/pkgvars.js @@ -2,9 +2,17 @@ var fs = require('fs'); var pkg = JSON.parse(fs.readFileSync('package.json')); -console.log( -`$(eval name := ${pkg.name}) -$(eval meta_name := ${pkg.meta.name}) -$(eval meta_distBranch := ${pkg.meta.distBranch}) -$(eval meta_uploadPath := ${pkg.meta.uploadPath}) -`); +var vars = {}; +var k; + +vars.name = pkg.name; +for (k in pkg.meta) { + vars[`meta_${k}`] = pkg.meta[k]; +} +for (k in pkg.devDependencies) { + vars[`version_${k}`] = pkg.devDependencies[k]; +} + +for (k in vars) { + console.log(`\$(eval ${k} := ${vars[k]})`); +} diff --git a/tools/sign.js b/tools/sign.js deleted file mode 100644 index 04b151f859..0000000000 --- a/tools/sign.js +++ /dev/null @@ -1,14 +0,0 @@ -var fs = require('fs'); -var crx = require('crx'); - -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`); -var extension = new crx({privateKey, loaded: true}); -extension.pack(archive).then((data) => - fs.writeFileSync(`testbuilds/${pkg.name}${channel}.crx`, data) -).catch(function() { - process.exit(1); -}); diff --git a/tools/sign.sh b/tools/sign.sh new file mode 100755 index 0000000000..fc856cc371 --- /dev/null +++ b/tools/sign.sh @@ -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/' diff --git a/tools/template.js b/tools/template.js index cda83b9831..6f671a18c8 100644 --- a/tools/template.js +++ b/tools/template.js @@ -24,8 +24,8 @@ tools.multiline = function(text) { return text.replace(/\n+/g, '\n').split(/^/m).map(JSON.stringify).join('+').replace(/"\+"/g, '\\\n'); }; -// Convert JSON object to Coffeescript expression (via embedded JS). -var constExpression = data => '`' + JSON.stringify(data).replace(/`/g, '\\`') + '`'; +// Convert JSONify-able object to Javascript expression. +var constExpression = data => JSON.stringify(data).replace(/`/g, '\\`'); function TextStream(text) { this.text = text; @@ -151,11 +151,11 @@ Placeholder.prototype.build = function() { throw new Error(`JavaScript in template is not an expression (${expr})`); } switch(this.type) { - case '$': return `\`E(${expr})\``; // $ : escaped text - case '&': return `\`(${expr}).innerHTML\``; // & : contents of HTML element or template (of form {innerHTML: "safeHTML"}) - case '@': return `\`E.cat(${expr})\``; // @ : contents of array of HTML elements or templates (see src/General/Globals.coffee for E.cat) + case '$': return `E(${expr})`; // $ : escaped text + case '&': return `(${expr}).innerHTML`; // & : contents of HTML element or template (of form {innerHTML: "safeHTML"}) + case '@': return `E.cat(${expr})`; // @ : contents of array of HTML elements or templates (see src/General/Globals.coffee for E.cat) case '?': - return `(if \`(${expr})\` then ${this.args[1] || '""'} else ${this.args[2] || '""'})`; // ? : conditional expression + return `((${expr}) ? ${this.args[1] || '""'} : ${this.args[2] || '""'})`; // ? : conditional expression } throw new Error(`Unrecognized placeholder type (${this.type})`); }; @@ -168,11 +168,7 @@ tools.html = function(template) { if (stream.text) { throw new Error(`Unexpected characters in template (${stream.text}): ${template}`); } - return `(innerHTML: ${output})`; -}; - -tools.assert = function(statement) { - return `throw new Error 'Assertion failed: ' + ${constExpression(statement)} unless ${statement}`; + return `{innerHTML: ${output}}`; }; function includesDir(templateName) { diff --git a/tools/updcl.js b/tools/updcl.js index 0dfbb2ec61..5af377ede3 100644 --- a/tools/updcl.js +++ b/tools/updcl.js @@ -23,8 +23,12 @@ var line = `**v${version}** *(${today})* - [[Userscript](${ffLink})] [[Chrom var changelog = fs.readFileSync('CHANGELOG.md', 'utf8'); var breakPos = changelog.indexOf(separator); -if (breakPos < 0) throw new Error('Separator not found.'); -breakPos += separator.length; +if (breakPos >= 0) { + breakPos += separator.length; +} else { + breakPos = Math.max(changelog.indexOf('\n\n#'), 0); + line = `${separator}\n\n${line}`; +} var prevVersion = changelog.substr(breakPos).match(/\*\*v([\d\.]+)\*\*/)[1]; if (prevVersion.replace(/\.\d+$/, '') !== branch) { diff --git a/tools/webstore.js b/tools/webstore.js index d9faceaa22..19a72fb8df 100644 --- a/tools/webstore.js +++ b/tools/webstore.js @@ -1,35 +1,38 @@ var fs = require('fs'); var child_process = require('child_process'); -var webstore_upload = require('webstore-upload'); var request = require('request'); var pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); var v = JSON.parse(child_process.execSync('git show stable:version.json').toString()); var secrets = JSON.parse(fs.readFileSync(`../${pkg.meta.path}.keys/chrome-store.json`, 'utf8')); +var refresh = JSON.parse(fs.readFileSync(`../${pkg.meta.path}.keys/refresh-token.json`, 'utf8')); -request(`https://chrome.google.com/webstore/detail/${pkg.meta.chromeStoreID}`, function (error, response, body) { +import('chrome-webstore-upload').then(chromeWebstoreUpload => { + var webStore = chromeWebstoreUpload.default({ + extensionId: pkg.meta.chromeStoreID, + clientId: secrets.installed.client_id, + clientSecret: secrets.installed.client_secret, + refreshToken: refresh.refresh_token + }); - if (body && body.indexOf(``) > 0 && process.argv[2] !== 'force') { - console.log(`Version ${v.version} already uploaded.`); - return; - } + request(`https://chrome.google.com/webstore/detail/${pkg.meta.chromeStoreID}`, function (error, response, body) { - webstore_upload({ - accounts: { - default: { - publish: true, - client_id: secrets.installed.client_id, - client_secret: secrets.installed.client_secret, - } - }, - extensions: { - extension: { - appID: pkg.meta.chromeStoreID, - zip: `dist/builds/${pkg.name}.zip` - } + if (body && body.indexOf(``) > 0 && process.argv[2] !== 'force') { + console.log(`Version ${v.version} already uploaded.`); + return; } - }, 'default').catch(function() { - process.exit(1); - }); + var myZipFile = fs.createReadStream(`dist/builds/${pkg.name}.zip`); + var token; + webStore.fetchToken().then(t => { + token = t; + return webStore.uploadExisting(myZipFile, token); + }).then(() => + webStore.publish() + ).catch(res => { + console.error(res); + process.exit(1); + }); + + }); }); diff --git a/version.json b/version.json index a62d4e78a9..cb3ed552e0 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "version": "1.12.0.0", - "date": "2016-06-19T01:08:23.454Z" + "version": "1.14.23.1", + "date": "2024-11-20T19:48:52.968Z" } \ No newline at end of file diff --git a/web.css b/web.css index fec2a71dd1..a46a3c24df 100644 --- a/web.css +++ b/web.css @@ -20,7 +20,7 @@ h1 { #links > a { display: table-cell; vertical-align: middle; - width: 25%; + width: 20%; color: #000; text-decoration: none; }