Merge branch 'master' into v1
Conflicts: src/Posting/QR.coffee
This commit is contained in:
commit
2762363093
174
CHANGELOG.md
174
CHANGELOG.md
@ -2,8 +2,179 @@ Sometimes the changelog has notes (not comprehensive) acknowledging people's wor
|
|||||||
|
|
||||||
The links to individual versions below are to copies of the script with the update URL removed. If you want automatic updates, install the script from the links on the [main page](https://github.com/ccd0/4chan-x).
|
The links to individual versions below are to copies of the script with the update URL removed. If you want automatic updates, install the script from the links on the [main page](https://github.com/ccd0/4chan-x).
|
||||||
|
|
||||||
|
## v1.11.0
|
||||||
|
|
||||||
|
**v1.11.0.6** *(2015-06-20)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.0.6/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.0.6/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Support toggling images in the captcha with the number keys (as arranged in the numpad) and the UIOJKLM,. keys.
|
||||||
|
- Arrow key navigation now works in noscript captcha.
|
||||||
|
- Various captcha-related improvements/bugfixes.
|
||||||
|
- Support space bar, numbers in numpad, comma, and space in keybinds.
|
||||||
|
|
||||||
|
**v1.11.0.5** *(2015-06-20)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.0.5/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.0.5/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Add `Captcha Language` setting in the `Advanced` panel.
|
||||||
|
- Minor bugfixes.
|
||||||
|
|
||||||
|
**v1.11.0.4** *(2015-06-20)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.0.4/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.0.4/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Noscript captcha improvements, including the ability to click the image rather than the little checkbox.
|
||||||
|
|
||||||
|
**v1.11.0.3** *(2015-06-19)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.0.3/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.0.3/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Merge v1.10.14.4: Update script for new non-Javascript captcha using image selection.
|
||||||
|
|
||||||
|
**v1.11.0.2** *(2015-06-19)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.0.2/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.0.2/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Remove code that focused on first image of the captcha as Google now focuses on the refresh button.
|
||||||
|
|
||||||
|
**v1.11.0.1** *(2015-06-16)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.0.1/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.0.1/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Merge v1.10.14.3: Fix words being cut off in non-Javascript captcha.
|
||||||
|
|
||||||
|
**v1.11.0.0** *(2015-06-14)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.0.0/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.11.0.0/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Based on v1.10.14.2.
|
||||||
|
- Gallery now preloads the next image in sequence.
|
||||||
|
- `Stretch to Fit` option added to gallery.
|
||||||
|
- Various bug fixes.
|
||||||
|
- Drop workarounds for old versions of Chromium (< v34).
|
||||||
|
|
||||||
|
### v1.10.14
|
||||||
|
|
||||||
|
**v1.10.14.4** *(2015-06-19)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.14.4/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.14.4/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Update script for new non-Javascript captcha using image selection.
|
||||||
|
|
||||||
|
**v1.10.14.3** *(2015-06-16)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.14.3/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.14.3/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Fix words being cut off in non-Javascript captcha.
|
||||||
|
|
||||||
|
**v1.10.14.2** *(2015-06-11)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.14.2/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.14.2/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Bring back workaround for scrolling to top caused by captcha as some users are still reporting it happening.
|
||||||
|
|
||||||
|
**v1.10.14.1** *(2015-06-11)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.14.1/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.14.1/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Add ImgOps to default sauce examples: `//imgops.com/%URL;types:gif,jpg,png`
|
||||||
|
|
||||||
|
**v1.10.14.0** *(2015-06-07)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.14.0/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.14.0/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Based on v1.10.13.4.
|
||||||
|
- Fix board-specific filters in thread watcher (filters apply to unread posts quoting you).
|
||||||
|
|
||||||
|
### v1.10.13
|
||||||
|
|
||||||
|
**v1.10.13.4** *(2015-06-05)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.13.4/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.13.4/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Support selecting images in the image captcha with the space bar in addition to the enter key.
|
||||||
|
|
||||||
|
**v1.10.13.3** *(2015-06-03)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.13.3/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.13.3/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Merge v1.10.12.10: Revert workaround for scrolling to top as it seems to have been fixed on Google's end.
|
||||||
|
|
||||||
|
**v1.10.13.2** *(2015-06-03)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.13.2/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.13.2/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Merge v1.10.12.9: Update for captcha changes.
|
||||||
|
- Merge v1.10.12.9: Work around issue where the captcha causes scrolling to the top of the page.
|
||||||
|
|
||||||
|
**v1.10.13.1** *(2015-05-27)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.13.1/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.13.1/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Fix manual page number updating not working when `Updater and Stats in Header` is off.
|
||||||
|
|
||||||
|
**v1.10.13.0** *(2015-05-26)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.13.0/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.13.0/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Based on v1.10.12.8.
|
||||||
|
- You can now manually update the page number in thread stats by clicking on it.
|
||||||
|
- Page number in thread stats does not show on /f/.
|
||||||
|
- Change default gallery slideshow toggle keybind from `s` to `Ctrl+Right`.
|
||||||
|
- Add IJKL as an alternative to the arrow keys for the image captcha.
|
||||||
|
- Clicking on the thread watcher refresh button while it's loading threads aborts it.
|
||||||
|
- Make thread watcher a bit more efficient by fixing some cases where it wasn't sending `If-Modified-Since`.
|
||||||
|
- Restore clearing the unread count in the thread watcher when a thread 404's.
|
||||||
|
- Various minor bugfixes.
|
||||||
|
|
||||||
|
### v1.10.12
|
||||||
|
|
||||||
|
**v1.10.12.10** *(2015-06-03)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.10/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.10/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Revert workaround for scrolling to top as it seems to have been fixed on Google's end.
|
||||||
|
|
||||||
|
**v1.10.12.9** *(2015-06-03)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.9/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.9/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Update for captcha changes.
|
||||||
|
- Work around issue where the captcha causes scrolling to the top of the page.
|
||||||
|
|
||||||
|
**v1.10.12.8** *(2015-05-22)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.8/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.8/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Remove guard against dropping files into captcha due to continued reports of disabled captchas.
|
||||||
|
|
||||||
|
**v1.10.12.7** *(2015-05-15)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.7/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.7/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Fix bug where dragging and dropping disabled the captcha even after the drop was complete.
|
||||||
|
|
||||||
|
**v1.10.12.6** *(2015-05-15)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.6/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.6/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- (Hasumi) Update archive.moe: Add /an/.
|
||||||
|
|
||||||
|
**v1.10.12.5** *(2015-05-14)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.5/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.5/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Fix bug where submit button was still getting unwanted focus under certain conditions.
|
||||||
|
|
||||||
|
**v1.10.12.4** *(2015-05-10)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.4/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.4/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Improve reporting to archive functionality.
|
||||||
|
|
||||||
|
**v1.10.12.3** *(2015-05-09)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.3/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.3/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Fix some captcha-related bugs.
|
||||||
|
|
||||||
|
**v1.10.12.2** *(2015-05-08)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.2/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.2/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Fix sandboxed HTTP sauce links not loading from HTTPS pages.
|
||||||
|
|
||||||
|
**v1.10.12.1** *(2015-05-04)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.1/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.1/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Give paste area cursor pointer like the other QR buttons.
|
||||||
|
|
||||||
|
**v1.10.12.0** *(2015-05-04)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.0/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.12.0/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Based on v1.10.11.12.
|
||||||
|
- Fix files dropped on captcha causing navigation to the file.
|
||||||
|
- Fix size of report window when Javascript is disabled.
|
||||||
|
- QR character count now handles surrogate pairs correctly and turns red when the limit of 2000 is reached.
|
||||||
|
- Add `;sandbox` option to sauce links to open links without scripts or popups. Re-add swfchan.
|
||||||
|
|
||||||
|
### v1.10.11
|
||||||
|
|
||||||
|
**v1.10.11.12** *(2015-05-03)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.12/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.12/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Add scrollbars to captcha popup when larger than window.
|
||||||
|
|
||||||
|
**v1.10.11.11** *(2015-05-03)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.11/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.11/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Remove swfchan from default sauce list.
|
||||||
|
|
||||||
|
**v1.10.11.10** *(2015-05-03)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.10/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.10/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Make link to MDN in filter guide stand out better.
|
||||||
|
|
||||||
|
**v1.10.11.9** *(2015-05-02)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.9/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.9/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Fix loading archived /f/ posts from before tag was recorded.
|
||||||
|
|
||||||
|
**v1.10.11.8** *(2015-05-02)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.8/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.8/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Fix some /f/-specific quotelink bugs.
|
||||||
|
|
||||||
|
**v1.10.11.7** *(2015-05-02)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.7/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.7/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Fix "PDF" being parsed as a Flash tag.
|
||||||
|
|
||||||
|
**v1.10.11.6** *(2015-04-26)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.6/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.6/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Fix size of window for reporting to fgts archive.
|
||||||
|
|
||||||
|
**v1.10.11.5** *(2015-04-26)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.5/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.5/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Reduce unwanted scrolling from captcha.
|
||||||
|
|
||||||
|
**v1.10.11.4** *(2015-04-26)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.4/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.4/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Don't apply filters to the unread post count in the thread watcher, but do apply them to unread posts quoting you.
|
||||||
|
|
||||||
|
**v1.10.11.3** *(2015-04-26)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.3/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.3/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Merge v1.10.10.4: Possible fix for bug causing scrolling to the top of the page upon loading image captcha.
|
||||||
|
|
||||||
|
**v1.10.11.2** *(2015-04-25)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.2/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.2/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Posts hidden by filtering are no longer counted as unread posts in the thread watcher.
|
||||||
|
- Add Flash tag (`%g`) to File Info Formatting.
|
||||||
|
- Fix subject not being displayed in old non-OP posts loaded from the archives.
|
||||||
|
|
||||||
|
**v1.10.11.1** *(2015-04-24)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.1/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.1/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Merge v1.10.10.3: Fix original post form not showing when JS is disabled.
|
||||||
|
|
||||||
|
**v1.10.11.0** *(2015-04-24)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.0/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.11.0/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Based on v1.10.10.2.
|
||||||
|
- Fix whitespace being stripped from the comment before filtering. This makes it possible to filter whitespace spam.
|
||||||
|
|
||||||
### v1.10.10
|
### v1.10.10
|
||||||
|
|
||||||
|
**v1.10.10.4** *(2015-04-26)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.10.4/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.10.4/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Possible fix for bug causing scrolling to the top of the page upon loading image captcha.
|
||||||
|
|
||||||
|
**v1.10.10.3** *(2015-04-24)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.10.3/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.10.3/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Fix original post form not showing when JS is disabled.
|
||||||
|
|
||||||
|
**v1.10.10.2** *(2015-04-21)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.10.2/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.10.2/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Add focus indication to verify button in captcha popup.
|
||||||
|
|
||||||
|
**v1.10.10.1** *(2015-04-19)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.10.1/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.10.1/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- Merge v1.10.9.5: (thebladeee) Archive list: Transferred /w/ and /wg/ back to Nyafuu.
|
||||||
|
|
||||||
**v1.10.10.0** *(2015-04-18)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.10.0/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.10.0/builds/4chan-X-noupdate.crx "Chromium version")]
|
**v1.10.10.0** *(2015-04-18)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.10.0/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.10.0/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
- Based on v1.10.9.4.
|
- Based on v1.10.9.4.
|
||||||
- Make images in image captcha selectable with arrow keys.
|
- Make images in image captcha selectable with arrow keys.
|
||||||
@ -11,6 +182,9 @@ The links to individual versions below are to copies of the script with the upda
|
|||||||
|
|
||||||
### v1.10.9
|
### v1.10.9
|
||||||
|
|
||||||
|
**v1.10.9.5** *(2015-04-19)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.9.5/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.9.5/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
|
- (thebladeee) Archive list: Transferred /w/ and /wg/ back to Nyafuu.
|
||||||
|
|
||||||
**v1.10.9.4** *(2015-04-17)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.9.4/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.9.4/builds/4chan-X-noupdate.crx "Chromium version")]
|
**v1.10.9.4** *(2015-04-17)* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.9.4/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.10.9.4/builds/4chan-X-noupdate.crx "Chromium version")]
|
||||||
- (Hasumi) Update archive.moe: Add /gif/.
|
- (Hasumi) Update archive.moe: Add /gif/.
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,12 @@ module.exports = (grunt) ->
|
|||||||
json = (data) ->
|
json = (data) ->
|
||||||
"`#{JSON.stringify(data).replace(/`/g, '\\`')}`"
|
"`#{JSON.stringify(data).replace(/`/g, '\\`')}`"
|
||||||
|
|
||||||
|
importCSS = (filenames...) ->
|
||||||
|
grunt.template.process(
|
||||||
|
filenames.map((name) -> grunt.file.read "src/General/css/#{name}.css").join(''),
|
||||||
|
{data: grunt.config 'pkg'}
|
||||||
|
).trim().replace(/\n+/g, '\n').split(/^/m).map(JSON.stringify).join(' +\n').replace(/`/g, '\\`')
|
||||||
|
|
||||||
importHTML = (filename) ->
|
importHTML = (filename) ->
|
||||||
html grunt.template.process(grunt.file.read("src/General/html/#{filename}.html").replace(/^ +/gm, '').replace(/\r?\n/g, ''), data: grunt.config('pkg'))
|
html grunt.template.process(grunt.file.read("src/General/html/#{filename}.html").replace(/^ +/gm, '').replace(/\r?\n/g, ''), data: grunt.config('pkg'))
|
||||||
|
|
||||||
@ -16,13 +22,14 @@ module.exports = (grunt) ->
|
|||||||
parts = []
|
parts = []
|
||||||
text = template
|
text = template
|
||||||
while text
|
while text
|
||||||
if part = text.match /^[^{}]+(?!{)/
|
if part = text.match /^(?:[^{}\\]|\\.)+(?!{)/
|
||||||
text = text[part[0].length..]
|
text = text[part[0].length..]
|
||||||
context = (context + part[0])
|
unescaped = part[0].replace /\\(.)/g, '$1'
|
||||||
|
context = (context + unescaped)
|
||||||
.replace(/(=['"])[^'"<>]*/g, '$1')
|
.replace(/(=['"])[^'"<>]*/g, '$1')
|
||||||
.replace(/(<\w+)( [\w-]+((?=[ >])|=''|=""))*/g, '$1')
|
.replace(/(<\w+)( [\w-]+((?=[ >])|=''|=""))*/g, '$1')
|
||||||
.replace(/^([^'"<>]+|<\/?\w+>)*/, '')
|
.replace(/^([^'"<>]+|<\/?\w+>)*/, '')
|
||||||
parts.push json part[0]
|
parts.push json unescaped
|
||||||
else if part = text.match /^([^}]){([^}`]*)}/
|
else if part = text.match /^([^}]){([^}`]*)}/
|
||||||
text = text[part[0].length..]
|
text = text[part[0].length..]
|
||||||
unless context is '' or (part[1] is '$' and /\=['"]$/.test context) or part[1] is '?'
|
unless context is '' or (part[1] is '$' and /\=['"]$/.test context) or part[1] is '?'
|
||||||
@ -68,6 +75,7 @@ module.exports = (grunt) ->
|
|||||||
options: process: Object.create(null, data:
|
options: process: Object.create(null, data:
|
||||||
get: ->
|
get: ->
|
||||||
pkg = grunt.config 'pkg'
|
pkg = grunt.config 'pkg'
|
||||||
|
pkg.importCSS = importCSS
|
||||||
pkg.importHTML = importHTML
|
pkg.importHTML = importHTML
|
||||||
pkg.html = html
|
pkg.html = html
|
||||||
pkg.assert = assert
|
pkg.assert = assert
|
||||||
@ -184,6 +192,7 @@ module.exports = (grunt) ->
|
|||||||
""".split('\n').join('&&')
|
""".split('\n').join('&&')
|
||||||
stable:
|
stable:
|
||||||
command: """
|
command: """
|
||||||
|
git push . HEAD:bstable
|
||||||
git tag -af stable -m "<%= pkg.meta.name %> v<%= pkg.meta.version %>."
|
git tag -af stable -m "<%= pkg.meta.name %> v<%= pkg.meta.version %>."
|
||||||
git checkout gh-pages
|
git checkout gh-pages
|
||||||
git pull
|
git pull
|
||||||
|
|||||||
7
LICENSE
7
LICENSE
@ -72,9 +72,6 @@
|
|||||||
* audio/beep.wav from http://freesound.org/people/pierrecartoons1979/sounds/90112/
|
* audio/beep.wav from http://freesound.org/people/pierrecartoons1979/sounds/90112/
|
||||||
* cc-by-nc-3.0
|
* cc-by-nc-3.0
|
||||||
*
|
*
|
||||||
* 4chan/4chan-JS (https://github.com/4chan/4chan-JS)
|
* Font Awesome by Dave Gandy (http://fontawesome.io)
|
||||||
* Copyright (c) 2012-2013, 4chan LLC
|
* license: http://fontawesome.io/license/
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* license: https://github.com/4chan/4chan-JS/blob/master/LICENSE
|
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -28,7 +28,8 @@ Only the latest stable version of 4chan X is available.
|
|||||||
## Other browsers
|
## Other browsers
|
||||||
This fork of 4chan X is not guaranteed to work correctly in other browsers, but you are welcome to try your luck. Pull requests to fix the bugs you will likely find are always welcome. You may fare better with [loadletter's fork](https://github.com/loadletter/4chan-x), which has fewer features but less dependence on browser-specific APIs.
|
This fork of 4chan X is not guaranteed to work correctly in other browsers, but you are welcome to try your luck. Pull requests to fix the bugs you will likely find are always welcome. You may fare better with [loadletter's fork](https://github.com/loadletter/4chan-x), which has fewer features but less dependence on browser-specific APIs.
|
||||||
|
|
||||||
- [Installing 4chan X in dwb](https://github.com/ccd0/4chan-x/wiki/Installing-4chan-X-in-dwb)
|
- Some people have reported success in Safari using [JS Blocker](http://jsblocker.toggleable.com/) to install the Firefox/Greasemonkey version.
|
||||||
|
- Instructions are available for [installing 4chan X in dwb](https://github.com/ccd0/4chan-x/wiki/Installing-4chan-X-in-dwb).
|
||||||
|
|
||||||
## Beta version
|
## 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) 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.
|
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) 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.
|
||||||
|
|||||||
Binary file not shown.
@ -1,6 +1,6 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name 4chan X beta
|
// @name 4chan X beta
|
||||||
// @version 1.10.10.0
|
// @version 1.11.0.6
|
||||||
// @minGMVer 1.14
|
// @minGMVer 1.14
|
||||||
// @minFFVer 26
|
// @minFFVer 26
|
||||||
// @namespace 4chan-X
|
// @namespace 4chan-X
|
||||||
@ -10,9 +10,11 @@
|
|||||||
// @match *://sys.4chan.org/*
|
// @match *://sys.4chan.org/*
|
||||||
// @match *://a.4cdn.org/*
|
// @match *://a.4cdn.org/*
|
||||||
// @match *://i.4cdn.org/*
|
// @match *://i.4cdn.org/*
|
||||||
|
// @match *://www.4chan.org/banned
|
||||||
|
// @match *://www.4chan.org/feedback
|
||||||
// @match https://www.google.com/recaptcha/api2/anchor?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc*
|
// @match https://www.google.com/recaptcha/api2/anchor?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc*
|
||||||
// @match https://www.google.com/recaptcha/api2/frame?*&k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc*
|
// @match https://www.google.com/recaptcha/api2/frame?*&k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc*
|
||||||
// @match *://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc
|
// @match *://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc*
|
||||||
// @grant GM_getValue
|
// @grant GM_getValue
|
||||||
// @grant GM_setValue
|
// @grant GM_setValue
|
||||||
// @grant GM_deleteValue
|
// @grant GM_deleteValue
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1,6 +1,6 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name 4chan X
|
// @name 4chan X
|
||||||
// @version 1.10.10.0
|
// @version 1.11.0.6
|
||||||
// @minGMVer 1.14
|
// @minGMVer 1.14
|
||||||
// @minFFVer 26
|
// @minFFVer 26
|
||||||
// @namespace 4chan-X
|
// @namespace 4chan-X
|
||||||
@ -10,9 +10,11 @@
|
|||||||
// @match *://sys.4chan.org/*
|
// @match *://sys.4chan.org/*
|
||||||
// @match *://a.4cdn.org/*
|
// @match *://a.4cdn.org/*
|
||||||
// @match *://i.4cdn.org/*
|
// @match *://i.4cdn.org/*
|
||||||
|
// @match *://www.4chan.org/banned
|
||||||
|
// @match *://www.4chan.org/feedback
|
||||||
// @match https://www.google.com/recaptcha/api2/anchor?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc*
|
// @match https://www.google.com/recaptcha/api2/anchor?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc*
|
||||||
// @match https://www.google.com/recaptcha/api2/frame?*&k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc*
|
// @match https://www.google.com/recaptcha/api2/frame?*&k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc*
|
||||||
// @match *://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc
|
// @match *://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc*
|
||||||
// @grant GM_getValue
|
// @grant GM_getValue
|
||||||
// @grant GM_setValue
|
// @grant GM_setValue
|
||||||
// @grant GM_deleteValue
|
// @grant GM_deleteValue
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1,7 +1,7 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
|
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
|
||||||
<app appid='lacclbnghgdicfifcamcmcnilckjamag'>
|
<app appid='lacclbnghgdicfifcamcmcnilckjamag'>
|
||||||
<updatecheck codebase='https://ccd0.github.io/4chan-x/builds/4chan-X-beta.crx' version='1.10.10.0' />
|
<updatecheck codebase='https://ccd0.github.io/4chan-x/builds/4chan-X-beta.crx' version='1.11.0.6' />
|
||||||
</app>
|
</app>
|
||||||
</gupdate>
|
</gupdate>
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
|
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
|
||||||
<app appid='lacclbnghgdicfifcamcmcnilckjamag'>
|
<app appid='lacclbnghgdicfifcamcmcnilckjamag'>
|
||||||
<updatecheck codebase='https://ccd0.github.io/4chan-x/builds/4chan-X.crx' version='1.10.10.0' />
|
<updatecheck codebase='https://ccd0.github.io/4chan-x/builds/4chan-X.crx' version='1.11.0.6' />
|
||||||
</app>
|
</app>
|
||||||
</gupdate>
|
</gupdate>
|
||||||
|
|
||||||
|
|||||||
@ -37,7 +37,8 @@ Only the latest stable version of 4chan X is available.</p>
|
|||||||
<h2 id="other-browsers">Other browsers</h2>
|
<h2 id="other-browsers">Other browsers</h2>
|
||||||
<p>This fork of 4chan X is not guaranteed to work correctly in other browsers, but you are welcome to try your luck. Pull requests to fix the bugs you will likely find are always welcome. You may fare better with <a href="https://github.com/loadletter/4chan-x">loadletter's fork</a>, which has fewer features but less dependence on browser-specific APIs.</p>
|
<p>This fork of 4chan X is not guaranteed to work correctly in other browsers, but you are welcome to try your luck. Pull requests to fix the bugs you will likely find are always welcome. You may fare better with <a href="https://github.com/loadletter/4chan-x">loadletter's fork</a>, which has fewer features but less dependence on browser-specific APIs.</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="https://github.com/ccd0/4chan-x/wiki/Installing-4chan-X-in-dwb">Installing 4chan X in dwb</a></li>
|
<li>Some people have reported success in Safari using <a href="http://jsblocker.toggleable.com/">JS Blocker</a> to install the Firefox/Greasemonkey version.</li>
|
||||||
|
<li>Instructions are available for <a href="https://github.com/ccd0/4chan-x/wiki/Installing-4chan-X-in-dwb">installing 4chan X in dwb</a>.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h2 id="beta-version">Beta version</h2>
|
<h2 id="beta-version">Beta version</h2>
|
||||||
<p>New features and non-urgent bugfixes are released on the beta channel for further testing before they are moved the stable version. Please <a href="https://github.com/ccd0/4chan-x/issues">report</a> 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.</p>
|
<p>New features and non-urgent bugfixes are released on the beta channel for further testing before they are moved the stable version. Please <a href="https://github.com/ccd0/4chan-x/issues">report</a> 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.</p>
|
||||||
|
|||||||
3513
npm-shrinkwrap.json
generated
3513
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@ -3,8 +3,8 @@
|
|||||||
"description": "Cross-browser userscript for maximum lurking on 4chan.",
|
"description": "Cross-browser userscript for maximum lurking on 4chan.",
|
||||||
"meta": {
|
"meta": {
|
||||||
"name": "4chan X",
|
"name": "4chan X",
|
||||||
"version": "1.10.10.0",
|
"version": "1.11.0.6",
|
||||||
"date": "2015-04-18T07:45:01.778Z",
|
"date": "2015-06-21T06:05:51.745Z",
|
||||||
"repo": "https://github.com/ccd0/4chan-x/",
|
"repo": "https://github.com/ccd0/4chan-x/",
|
||||||
"page": "https://github.com/ccd0/4chan-x",
|
"page": "https://github.com/ccd0/4chan-x",
|
||||||
"downloads": "https://ccd0.github.io/4chan-x/builds/",
|
"downloads": "https://ccd0.github.io/4chan-x/builds/",
|
||||||
@ -21,9 +21,11 @@
|
|||||||
"*://sys.4chan.org/*",
|
"*://sys.4chan.org/*",
|
||||||
"*://a.4cdn.org/*",
|
"*://a.4cdn.org/*",
|
||||||
"*://i.4cdn.org/*",
|
"*://i.4cdn.org/*",
|
||||||
|
"*://www.4chan.org/banned",
|
||||||
|
"*://www.4chan.org/feedback",
|
||||||
"https://www.google.com/recaptcha/api2/anchor?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc*",
|
"https://www.google.com/recaptcha/api2/anchor?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc*",
|
||||||
"https://www.google.com/recaptcha/api2/frame?*&k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc*",
|
"https://www.google.com/recaptcha/api2/frame?*&k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc*",
|
||||||
"*://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc"
|
"*://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc*"
|
||||||
],
|
],
|
||||||
"suffix": {
|
"suffix": {
|
||||||
"stable": "",
|
"stable": "",
|
||||||
@ -38,7 +40,7 @@
|
|||||||
"dev": " dev"
|
"dev": " dev"
|
||||||
},
|
},
|
||||||
"min": {
|
"min": {
|
||||||
"chrome": "32",
|
"chrome": "34",
|
||||||
"firefox": "26",
|
"firefox": "26",
|
||||||
"greasemonkey": "1.14"
|
"greasemonkey": "1.14"
|
||||||
}
|
}
|
||||||
@ -52,14 +54,14 @@
|
|||||||
"grunt-contrib-coffee": "^0.13.0",
|
"grunt-contrib-coffee": "^0.13.0",
|
||||||
"grunt-contrib-concat": "^0.5.1",
|
"grunt-contrib-concat": "^0.5.1",
|
||||||
"grunt-contrib-copy": "^0.8.0",
|
"grunt-contrib-copy": "^0.8.0",
|
||||||
"grunt-contrib-jshint": "^0.11.1",
|
"grunt-contrib-jshint": "^0.11.2",
|
||||||
"grunt-contrib-watch": "^0.6.1",
|
"grunt-contrib-watch": "^0.6.1",
|
||||||
"grunt-markdown": "^0.7.0",
|
"grunt-markdown": "^0.7.0",
|
||||||
"grunt-shell": "^1.1.2",
|
"grunt-shell": "^1.1.2",
|
||||||
"grunt-webstore-upload": "^0.8.2",
|
"grunt-webstore-upload": "^0.8.2",
|
||||||
"jszip": "^2.5.0",
|
"jszip": "^2.5.0",
|
||||||
"load-grunt-tasks": "^3.1.0",
|
"load-grunt-tasks": "^3.2.0",
|
||||||
"npm-shrinkwrap": "^5.3.0"
|
"npm-shrinkwrap": "^5.4.0"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
"http": false,
|
"http": false,
|
||||||
"https": true,
|
"https": true,
|
||||||
"software": "foolfuuka",
|
"software": "foolfuuka",
|
||||||
"boards": ["a", "biz", "c", "co", "diy", "fit", "gd", "gif", "h", "i", "int", "jp", "k", "m", "mlp", "out", "po", "qa", "r9k", "s4s", "sci", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"],
|
"boards": ["a", "an", "biz", "c", "co", "diy", "fit", "gd", "gif", "h", "i", "int", "jp", "k", "m", "mlp", "out", "po", "qa", "r9k", "s4s", "sci", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"],
|
||||||
"files": ["a", "biz", "c", "co", "diy", "fit", "gd", "h", "i", "jp", "k", "m", "mlp", "po", "qa", "r9k", "s4s", "sci", "tg", "u", "v", "vg", "vp", "vr", "wsg"]
|
"files": ["a", "biz", "c", "co", "diy", "fit", "gd", "h", "i", "jp", "k", "m", "mlp", "po", "qa", "r9k", "s4s", "sci", "tg", "u", "v", "vg", "vp", "vr", "wsg"]
|
||||||
}, {
|
}, {
|
||||||
"uid": 3,
|
"uid": 3,
|
||||||
@ -32,8 +32,8 @@
|
|||||||
"http": true,
|
"http": true,
|
||||||
"https": true,
|
"https": true,
|
||||||
"software": "foolfuuka",
|
"software": "foolfuuka",
|
||||||
"boards": ["c", "d", "e", "i", "lgbt", "t", "u", "w", "wg"],
|
"boards": ["c", "d", "e", "i", "lgbt", "t", "u"],
|
||||||
"files": ["c", "d", "e", "i", "lgbt", "t", "u", "w", "wg"]
|
"files": ["c", "d", "e", "i", "lgbt", "t", "u"]
|
||||||
}, {
|
}, {
|
||||||
"uid": 8,
|
"uid": 8,
|
||||||
"name": "Rebecca Black Tech",
|
"name": "Rebecca Black Tech",
|
||||||
|
|||||||
@ -17,12 +17,10 @@ Filter =
|
|||||||
# Don't mix up filter flags with the regular expression.
|
# Don't mix up filter flags with the regular expression.
|
||||||
filter = line.replace regexp[0], ''
|
filter = line.replace regexp[0], ''
|
||||||
|
|
||||||
# Do not add this filter to the list if it's not a global one
|
# Comma-separated list of the boards this filter applies to.
|
||||||
# and it's not specifically applicable to the current board.
|
|
||||||
# Defaults to global.
|
# Defaults to global.
|
||||||
boards = filter.match(/boards:([^;]+)/)?[1].toLowerCase() or 'global'
|
boards = filter.match(/boards:([^;]+)/)?[1].toLowerCase() or 'global'
|
||||||
if boards isnt 'global' and g.BOARD.ID not in boards.split ','
|
boards = if boards is 'global' then null else boards.split(',')
|
||||||
continue
|
|
||||||
|
|
||||||
if key in ['uniqueID', 'MD5']
|
if key in ['uniqueID', 'MD5']
|
||||||
# MD5 filter will use strings instead of regular expressions.
|
# MD5 filter will use strings instead of regular expressions.
|
||||||
@ -66,7 +64,7 @@ Filter =
|
|||||||
top = filter.match(/top:(yes|no)/)?[1] or 'yes'
|
top = filter.match(/top:(yes|no)/)?[1] or 'yes'
|
||||||
top = top is 'yes' # Turn it into a boolean
|
top = top is 'yes' # Turn it into a boolean
|
||||||
|
|
||||||
@filters[key].push @createFilter regexp, op, stub, hl, top
|
@filters[key].push @createFilter regexp, boards, op, stub, hl, top
|
||||||
|
|
||||||
# Only execute filter types that contain valid filters.
|
# Only execute filter types that contain valid filters.
|
||||||
unless @filters[key].length
|
unless @filters[key].length
|
||||||
@ -77,7 +75,7 @@ Filter =
|
|||||||
name: 'Filter'
|
name: 'Filter'
|
||||||
cb: @node
|
cb: @node
|
||||||
|
|
||||||
createFilter: (regexp, op, stub, hl, top) ->
|
createFilter: (regexp, boards, op, stub, hl, top) ->
|
||||||
test =
|
test =
|
||||||
if typeof regexp is 'string'
|
if typeof regexp is 'string'
|
||||||
# MD5 checking
|
# MD5 checking
|
||||||
@ -91,7 +89,9 @@ Filter =
|
|||||||
class: hl
|
class: hl
|
||||||
top: top
|
top: top
|
||||||
|
|
||||||
(value, isReply) ->
|
(value, boardID, isReply) ->
|
||||||
|
if boards and boardID not in boards
|
||||||
|
return false
|
||||||
if isReply and op is 'only' or !isReply and op is 'no'
|
if isReply and op is 'only' or !isReply and op is 'no'
|
||||||
return false
|
return false
|
||||||
unless test value
|
unless test value
|
||||||
@ -103,7 +103,7 @@ Filter =
|
|||||||
for key of Filter.filters when (value = Filter[key] @)?
|
for key of Filter.filters when (value = Filter[key] @)?
|
||||||
# Continue if there's nothing to filter (no tripcode for example).
|
# Continue if there's nothing to filter (no tripcode for example).
|
||||||
|
|
||||||
for filter in Filter.filters[key] when result = filter value, @isReply
|
for filter in Filter.filters[key] when result = filter value, @board.ID, @isReply
|
||||||
# Hide
|
# Hide
|
||||||
if result.hide and not @isFetchedQuote
|
if result.hide and not @isFetchedQuote
|
||||||
if @isReply
|
if @isReply
|
||||||
@ -121,12 +121,18 @@ Filter =
|
|||||||
if !@isReply and result.top
|
if !@isReply and result.top
|
||||||
@thread.isOnTop = true
|
@thread.isOnTop = true
|
||||||
|
|
||||||
|
isHidden: (post) ->
|
||||||
|
for key of Filter.filters when (value = Filter[key] post)?
|
||||||
|
for filter in Filter.filters[key] when result = filter value, post.boardID, post.isReply
|
||||||
|
return true if result.hide
|
||||||
|
false
|
||||||
|
|
||||||
name: (post) -> post.info.name
|
name: (post) -> post.info.name
|
||||||
uniqueID: (post) -> post.info.uniqueID
|
uniqueID: (post) -> post.info.uniqueID
|
||||||
tripcode: (post) -> post.info.tripcode
|
tripcode: (post) -> post.info.tripcode
|
||||||
capcode: (post) -> post.info.capcode
|
capcode: (post) -> post.info.capcode
|
||||||
subject: (post) -> post.info.subject or undefined
|
subject: (post) -> post.info.subject
|
||||||
comment: (post) -> post.info.comment
|
comment: (post) -> post.info.comment ? Build.parseComment(post)
|
||||||
flag: (post) -> post.info.flag
|
flag: (post) -> post.info.flag
|
||||||
filename: (post) -> post.file?.name
|
filename: (post) -> post.file?.name
|
||||||
dimensions: (post) -> post.file?.dimensions
|
dimensions: (post) -> post.file?.dimensions
|
||||||
|
|||||||
@ -2,10 +2,12 @@ Build =
|
|||||||
staticPath: '//s.4cdn.org/image/'
|
staticPath: '//s.4cdn.org/image/'
|
||||||
gifIcon: if window.devicePixelRatio >= 2 then '@2x.gif' else '.gif'
|
gifIcon: if window.devicePixelRatio >= 2 then '@2x.gif' else '.gif'
|
||||||
spoilerRange: {}
|
spoilerRange: {}
|
||||||
|
|
||||||
unescape: (text) ->
|
unescape: (text) ->
|
||||||
return text unless text?
|
return text unless text?
|
||||||
text.replace(/<[^>]*>/g, '').replace /&(amp|#039|quot|lt|gt);/g, (c) ->
|
text.replace(/<[^>]*>/g, '').replace /&(amp|#039|quot|lt|gt|#44);/g, (c) ->
|
||||||
{'&': '&', ''': "'", '"': '"', '<': '<', '>': '>'}[c]
|
{'&': '&', ''': "'", '"': '"', '<': '<', '>': '>', ',': ','}[c]
|
||||||
|
|
||||||
shortFilename: (filename) ->
|
shortFilename: (filename) ->
|
||||||
threshold = 30
|
threshold = 30
|
||||||
ext = filename.match(/\.?[^\.]*$/)[0]
|
ext = filename.match(/\.?[^\.]*$/)[0]
|
||||||
@ -13,93 +15,100 @@ Build =
|
|||||||
"#{filename[...threshold - 5]}(...)#{ext}"
|
"#{filename[...threshold - 5]}(...)#{ext}"
|
||||||
else
|
else
|
||||||
filename
|
filename
|
||||||
|
|
||||||
spoilerThumb: (boardID) ->
|
spoilerThumb: (boardID) ->
|
||||||
if spoilerRange = Build.spoilerRange[boardID]
|
if spoilerRange = Build.spoilerRange[boardID]
|
||||||
# Randomize the spoiler image.
|
# Randomize the spoiler image.
|
||||||
"#{Build.staticPath}spoiler-#{boardID}#{Math.floor 1 + spoilerRange * Math.random()}.png"
|
"#{Build.staticPath}spoiler-#{boardID}#{Math.floor 1 + spoilerRange * Math.random()}.png"
|
||||||
else
|
else
|
||||||
"#{Build.staticPath}spoiler.png"
|
"#{Build.staticPath}spoiler.png"
|
||||||
|
|
||||||
sameThread: (boardID, threadID) ->
|
sameThread: (boardID, threadID) ->
|
||||||
g.VIEW is 'thread' and g.BOARD.ID is boardID and g.THREADID is +threadID
|
g.VIEW is 'thread' and g.BOARD.ID is boardID and g.THREADID is +threadID
|
||||||
|
|
||||||
postURL: (boardID, threadID, postID) ->
|
postURL: (boardID, threadID, postID) ->
|
||||||
if Build.sameThread boardID, threadID
|
if Build.sameThread boardID, threadID
|
||||||
"#p#{postID}"
|
"#p#{postID}"
|
||||||
else
|
else
|
||||||
"/#{boardID}/thread/#{threadID}#p#{postID}"
|
"/#{boardID}/thread/#{threadID}#p#{postID}"
|
||||||
postFromObject: (data, boardID, suppressThumb) ->
|
|
||||||
|
parseJSON: (data, boardID) ->
|
||||||
o =
|
o =
|
||||||
# id
|
# id
|
||||||
postID: data.no
|
postID: data.no
|
||||||
threadID: data.resto or data.no
|
threadID: data.resto or data.no
|
||||||
boardID: boardID
|
boardID: boardID
|
||||||
# info
|
isReply: !!data.resto
|
||||||
name: Build.unescape data.name
|
|
||||||
capcode: data.capcode
|
|
||||||
tripcode: data.trip
|
|
||||||
uniqueID: data.id
|
|
||||||
email: Build.unescape data.email
|
|
||||||
subject: Build.unescape data.sub
|
|
||||||
flagCode: data.country
|
|
||||||
flagName: Build.unescape data.country_name
|
|
||||||
date: data.now
|
|
||||||
dateUTC: data.time
|
|
||||||
comment: {innerHTML: data.com or ''}
|
|
||||||
# thread status
|
# thread status
|
||||||
isSticky: !!data.sticky
|
isSticky: !!data.sticky
|
||||||
isClosed: !!data.closed
|
isClosed: !!data.closed
|
||||||
isArchived: !!data.archived
|
isArchived: !!data.archived
|
||||||
# file
|
# file status
|
||||||
if data.filedeleted
|
fileDeleted: !!data.filedeleted
|
||||||
o.file =
|
o.info =
|
||||||
isDeleted: true
|
subject: Build.unescape data.sub
|
||||||
else if data.ext
|
email: Build.unescape data.email
|
||||||
|
name: Build.unescape(data.name) or ''
|
||||||
|
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 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
|
||||||
|
if data.ext
|
||||||
o.file =
|
o.file =
|
||||||
name: (Build.unescape data.filename) + data.ext
|
name: (Build.unescape data.filename) + data.ext
|
||||||
timestamp: "#{data.tim}#{data.ext}"
|
|
||||||
url: if boardID is 'f'
|
url: if boardID is 'f'
|
||||||
"//i.4cdn.org/#{boardID}/#{encodeURIComponent data.filename}#{data.ext}"
|
"#{location.protocol}//i.4cdn.org/#{boardID}/#{encodeURIComponent data.filename}#{data.ext}"
|
||||||
else
|
else
|
||||||
"//i.4cdn.org/#{boardID}/#{data.tim}#{data.ext}"
|
"#{location.protocol}//i.4cdn.org/#{boardID}/#{data.tim}#{data.ext}"
|
||||||
height: data.h
|
height: data.h
|
||||||
width: data.w
|
width: data.w
|
||||||
MD5: data.md5
|
MD5: data.md5
|
||||||
size: data.fsize
|
size: $.bytesToString data.fsize
|
||||||
turl: "//i.4cdn.org/#{boardID}/#{data.tim}s.jpg"
|
thumbURL: "#{location.protocol}//i.4cdn.org/#{boardID}/#{data.tim}s.jpg"
|
||||||
theight: data.tn_h
|
theight: data.tn_h
|
||||||
twidth: data.tn_w
|
twidth: data.tn_w
|
||||||
isSpoiler: !!data.spoiler
|
isSpoiler: !!data.spoiler
|
||||||
isDeleted: false
|
|
||||||
tag: data.tag
|
tag: data.tag
|
||||||
|
o.file.dimensions = "#{o.file.width}x#{o.file.height}" unless /\.pdf$/.test o.file.url
|
||||||
|
o
|
||||||
|
|
||||||
|
parseComment: (o) ->
|
||||||
|
html = o.info.commentHTML.innerHTML
|
||||||
|
.replace(/<br\b[^<]*>/gi, '\n')
|
||||||
|
.replace(/\n\n<span\b[^<]* class="abbr"[^]*$/i, '') # EXIF data (/p/)
|
||||||
|
.replace(/^<b\b[^<]*>Rolled [^<]*<\/b>/i, '') # Rolls (/tg/)
|
||||||
|
.replace(/<span\b[^<]* class="fortune"[^]*$/i, '') # Fortunes (/s4s/)
|
||||||
|
.replace(/<[^>]*>/g, '')
|
||||||
|
o.info.comment = Build.unescape html
|
||||||
|
|
||||||
|
postFromObject: (data, boardID, suppressThumb) ->
|
||||||
|
o = Build.parseJSON data, boardID
|
||||||
Build.post o, suppressThumb
|
Build.post o, suppressThumb
|
||||||
|
|
||||||
post: (o, suppressThumb) ->
|
post: (o, suppressThumb) ->
|
||||||
###
|
{postID, threadID, boardID, file} = o
|
||||||
This function contains code from 4chan-JS (https://github.com/4chan/4chan-JS).
|
{subject, email, name, tripcode, capcode, uniqueID, flagCode, flag, dateUTC, dateText, commentHTML} = o.info
|
||||||
@license: https://github.com/4chan/4chan-JS/blob/master/LICENSE
|
|
||||||
###
|
|
||||||
{
|
|
||||||
postID, threadID, boardID
|
|
||||||
name, capcode, tripcode, uniqueID, email, subject, flagCode, flagName, date, dateUTC
|
|
||||||
comment
|
|
||||||
file
|
|
||||||
} = o
|
|
||||||
name or= ''
|
|
||||||
subject or= ''
|
|
||||||
isOP = postID is threadID
|
|
||||||
{staticPath, gifIcon} = Build
|
{staticPath, gifIcon} = Build
|
||||||
|
|
||||||
### Post Info ###
|
### Post Info ###
|
||||||
|
|
||||||
if capcode
|
if capcode
|
||||||
capcodeLC = capcode.split('_')[0]
|
capcodeUC = capcode.split(' ')[0]
|
||||||
capcodeUC = capcodeLC[0].toUpperCase() + capcodeLC[1..]
|
capcodeLC = capcodeUC.toLowerCase()
|
||||||
capcodeText = capcodeUC
|
if capcode is 'Admin Emeritus'
|
||||||
capcodeLong = {'Admin': 'Administrator', 'Mod': 'Moderator'}[capcodeUC] or capcodeUC
|
|
||||||
capcodePlural = "#{capcodeLong}s"
|
|
||||||
capcodeDescription = "a 4chan #{capcodeLong}"
|
|
||||||
if capcode is 'admin_emeritus'
|
|
||||||
capcodeText = 'Admin Emeritus'
|
|
||||||
capcodePlural = 'the Administrator Emeritus'
|
capcodePlural = 'the Administrator Emeritus'
|
||||||
capcodeDescription = "4chan's founding Administrator"
|
capcodeDescription = "4chan's founding Administrator"
|
||||||
|
else
|
||||||
|
capcodeLong = {'Admin': 'Administrator', 'Mod': 'Moderator'}[capcode] or capcode
|
||||||
|
capcodePlural = "#{capcodeLong}s"
|
||||||
|
capcodeDescription = "a 4chan #{capcodeLong}"
|
||||||
|
|
||||||
postLink = Build.postURL boardID, threadID, postID
|
postLink = Build.postURL boardID, threadID, postID
|
||||||
quoteLink = if Build.sameThread boardID, threadID
|
quoteLink = if Build.sameThread boardID, threadID
|
||||||
@ -111,17 +120,17 @@ Build =
|
|||||||
|
|
||||||
### File Info ###
|
### File Info ###
|
||||||
|
|
||||||
if file and not file.isDeleted
|
if file
|
||||||
|
protocol = /^https?:(?=\/\/i\.4cdn\.org\/)/
|
||||||
|
fileURL = file.url.replace protocol, ''
|
||||||
shortFilename = Build.shortFilename file.name
|
shortFilename = Build.shortFilename file.name
|
||||||
fileSize = $.bytesToString file.size
|
fileThumb = if file.isSpoiler then Build.spoilerThumb(boardID) else file.thumbURL.replace(protocol, '')
|
||||||
fileDims = if file.url[-4..] is '.pdf' then 'PDF' else "#{file.width}x#{file.height}"
|
|
||||||
fileThumb = if file.isSpoiler then Build.spoilerThumb boardID else file.turl
|
|
||||||
|
|
||||||
fileBlock = <%= importHTML('Build/File') %>
|
fileBlock = <%= importHTML('Build/File') %>
|
||||||
|
|
||||||
### Whole Post ###
|
### Whole Post ###
|
||||||
|
|
||||||
postClass = if isOP then 'op' else 'reply'
|
postClass = if o.isReply then 'reply' else 'op'
|
||||||
|
|
||||||
wholePost = <%= importHTML('Build/Post') %>
|
wholePost = <%= importHTML('Build/Post') %>
|
||||||
|
|
||||||
@ -130,13 +139,15 @@ Build =
|
|||||||
id: "pc#{postID}"
|
id: "pc#{postID}"
|
||||||
$.extend container, wholePost
|
$.extend container, wholePost
|
||||||
|
|
||||||
# Fix pathnames
|
# Fix quotelinks
|
||||||
for quote in $$ '.quotelink', container
|
for quote in $$ '.quotelink', container
|
||||||
href = quote.getAttribute 'href'
|
href = quote.getAttribute 'href'
|
||||||
if (href[0] is '#') and !(Build.sameThread boardID, threadID)
|
if (href[0] is '#') and !(Build.sameThread boardID, threadID)
|
||||||
quote.href = "/#{boardID}/thread/#{threadID}" + href
|
quote.href = "/#{boardID}/thread/#{threadID}" + href
|
||||||
else if (match = href.match /^\/([^\/]+)\/thread\/(\d+)/) and (Build.sameThread match[1], match[2])
|
else if (match = href.match /^\/([^\/]+)\/thread\/(\d+)/) and (Build.sameThread match[1], match[2])
|
||||||
quote.href = href.match(/(#[^#]*)?$/)[0] or '#'
|
quote.href = href.match(/(#[^#]*)?$/)[0] or '#'
|
||||||
|
else if /^\d+(#|$)/.test(href) and not (g.VIEW is 'thread' and g.BOARD.ID is boardID) # used on /f/
|
||||||
|
quote.href = "/#{boardID}/thread/#{href}"
|
||||||
|
|
||||||
container
|
container
|
||||||
|
|
||||||
|
|||||||
@ -38,22 +38,37 @@ BuildTest =
|
|||||||
for postData in posts
|
for postData in posts
|
||||||
if postData.no is post.ID
|
if postData.no is post.ID
|
||||||
t1 = new Date().getTime()
|
t1 = new Date().getTime()
|
||||||
root = Build.postFromObject postData, post.board.ID
|
obj = Build.parseJSON postData, post.board.ID
|
||||||
|
root = Build.post obj
|
||||||
t2 = new Date().getTime()
|
t2 = new Date().getTime()
|
||||||
BuildTest.time += t2 - t1
|
BuildTest.time += t2 - t1
|
||||||
post2 = new Post root, post.thread, post.board
|
post2 = new Post root, post.thread, post.board
|
||||||
|
fail = false
|
||||||
|
|
||||||
x = post.normalizedOriginal
|
x = post.normalizedOriginal
|
||||||
y = post2.normalizedOriginal
|
y = post2.normalizedOriginal
|
||||||
if x.isEqualNode y
|
unless x.isEqualNode y
|
||||||
c.log "#{post.fullID} correct"
|
fail = true
|
||||||
else
|
|
||||||
c.log "#{post.fullID} differs"
|
c.log "#{post.fullID} differs"
|
||||||
BuildTest.postsFailed++
|
|
||||||
[x2, y2] = BuildTest.firstDiff x, y
|
[x2, y2] = BuildTest.firstDiff x, y
|
||||||
c.log x2
|
c.log x2
|
||||||
c.log y2
|
c.log y2
|
||||||
c.log x.outerHTML
|
c.log x.outerHTML
|
||||||
c.log y.outerHTML
|
c.log y.outerHTML
|
||||||
|
|
||||||
|
for key of Config.filter when not (key is 'MD5' and post.board.ID is 'f')
|
||||||
|
val1 = Filter[key] obj
|
||||||
|
val2 = Filter[key] post2
|
||||||
|
if val1 isnt val2
|
||||||
|
fail = true
|
||||||
|
c.log "#{post.fullID} has filter bug in #{key}"
|
||||||
|
c.log val1
|
||||||
|
c.log val2
|
||||||
|
|
||||||
|
if fail
|
||||||
|
BuildTest.postsFailed++
|
||||||
|
else
|
||||||
|
c.log "#{post.fullID} correct"
|
||||||
BuildTest.postsRemaining--
|
BuildTest.postsRemaining--
|
||||||
BuildTest.report() if BuildTest.postsRemaining is 0
|
BuildTest.report() if BuildTest.postsRemaining is 0
|
||||||
post2.isFetchedQuote = true
|
post2.isFetchedQuote = true
|
||||||
@ -61,8 +76,9 @@ BuildTest =
|
|||||||
|
|
||||||
testAll: ->
|
testAll: ->
|
||||||
g.posts.forEach (post) ->
|
g.posts.forEach (post) ->
|
||||||
unless post.isClone or post.isFetchedQuote or $ '.abbr', post.nodes.comment
|
unless post.isClone or post.isFetchedQuote
|
||||||
BuildTest.testOne post
|
unless (abbr = $ '.abbr', post.nodes.comment) and /Comment too long\./.test(abbr.textContent)
|
||||||
|
BuildTest.testOne post
|
||||||
return
|
return
|
||||||
|
|
||||||
postsRemaining: 0
|
postsRemaining: 0
|
||||||
|
|||||||
@ -421,13 +421,11 @@ Config =
|
|||||||
'Open new threads or replies to a thread from the index in a new tab.'
|
'Open new threads or replies to a thread from the index in a new tab.'
|
||||||
1
|
1
|
||||||
]
|
]
|
||||||
<% if (type === 'userscript') { %>
|
|
||||||
'Remember QR Size': [
|
'Remember QR Size': [
|
||||||
false
|
false
|
||||||
'Remember the size of the Quick reply.'
|
'Remember the size of the Quick reply.'
|
||||||
1
|
1
|
||||||
]
|
]
|
||||||
<% } %>
|
|
||||||
'Remember Spoiler': [
|
'Remember Spoiler': [
|
||||||
false
|
false
|
||||||
'Remember the spoiler state, instead of resetting after posting.'
|
'Remember the spoiler state, instead of resetting after posting.'
|
||||||
@ -485,7 +483,7 @@ Config =
|
|||||||
]
|
]
|
||||||
'Captcha Fixes': [
|
'Captcha Fixes': [
|
||||||
true
|
true
|
||||||
'Make captcha more keyboard-navigable.'
|
'Make captcha easier to use, especially with the keyboard.'
|
||||||
]
|
]
|
||||||
|
|
||||||
'Quote Links':
|
'Quote Links':
|
||||||
@ -592,6 +590,9 @@ Config =
|
|||||||
'Fit Height': [
|
'Fit Height': [
|
||||||
true
|
true
|
||||||
]
|
]
|
||||||
|
'Stretch to Fit': [
|
||||||
|
false
|
||||||
|
]
|
||||||
'Scroll to Post': [
|
'Scroll to Post': [
|
||||||
true
|
true
|
||||||
]
|
]
|
||||||
@ -674,12 +675,14 @@ Config =
|
|||||||
sauces: """
|
sauces: """
|
||||||
https://www.google.com/searchbyimage?image_url=%IMG
|
https://www.google.com/searchbyimage?image_url=%IMG
|
||||||
http://iqdb.org/?url=%IMG
|
http://iqdb.org/?url=%IMG
|
||||||
http://eye.swfchan.com/search/?q=%name;types:swf
|
http://eye.swfchan.com/search/?q=%name;types:swf;sandbox
|
||||||
#//tineye.com/search?url=%IMG
|
#//tineye.com/search?url=%IMG
|
||||||
#https://www.yandex.com/images/search?rpt=imageview&img_url=%IMG
|
#https://www.yandex.com/images/search?rpt=imageview&img_url=%IMG
|
||||||
#//saucenao.com/search.php?url=%IMG
|
#//saucenao.com/search.php?url=%IMG
|
||||||
#http://3d.iqdb.org/?url=%IMG
|
#http://3d.iqdb.org/?url=%IMG
|
||||||
|
# tools:
|
||||||
#http://regex.info/exif.cgi?imgurl=%URL
|
#http://regex.info/exif.cgi?imgurl=%URL
|
||||||
|
#//imgops.com/%URL;types:gif,jpg,png
|
||||||
# uploaders:
|
# uploaders:
|
||||||
#//imgur.com/upload?url=%URL;types:gif,jpg,png,pdf;text:Upload to imgur
|
#//imgur.com/upload?url=%URL;types:gif,jpg,png,pdf;text:Upload to imgur
|
||||||
# "View Same" in archives:
|
# "View Same" in archives:
|
||||||
@ -743,11 +746,13 @@ Config =
|
|||||||
#options:"sage";boards:jp;always
|
#options:"sage";boards:jp;always
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
captchaLanguage: ''
|
||||||
|
|
||||||
time: '%m/%d/%y(%a)%H:%M:%S'
|
time: '%m/%d/%y(%a)%H:%M:%S'
|
||||||
|
|
||||||
backlink: '>>%id'
|
backlink: '>>%id'
|
||||||
|
|
||||||
fileInfo: '%l (%p%s, %r)'
|
fileInfo: '%l (%p%s, %r%g)'
|
||||||
|
|
||||||
favicon: 'ferongr'
|
favicon: 'ferongr'
|
||||||
|
|
||||||
@ -830,7 +835,7 @@ Config =
|
|||||||
'Pause/play videos in the gallery.'
|
'Pause/play videos in the gallery.'
|
||||||
]
|
]
|
||||||
'Slideshow': [
|
'Slideshow': [
|
||||||
's'
|
'Ctrl+Right'
|
||||||
'Toggle the gallery slideshow mode.'
|
'Toggle the gallery slideshow mode.'
|
||||||
]
|
]
|
||||||
'fappeTyme': [
|
'fappeTyme': [
|
||||||
|
|||||||
@ -3,7 +3,7 @@ Get =
|
|||||||
{OP} = thread
|
{OP} = thread
|
||||||
excerpt = "/#{thread.board}/ - " + (
|
excerpt = "/#{thread.board}/ - " + (
|
||||||
OP.info.subject?.trim() or
|
OP.info.subject?.trim() or
|
||||||
OP.info.comment.replace(/\n+/g, ' // ') or
|
OP.info.commentDisplay.replace(/\n+/g, ' // ') or
|
||||||
OP.info.nameBlock)
|
OP.info.nameBlock)
|
||||||
return "#{excerpt[...70]}..." if excerpt.length > 73
|
return "#{excerpt[...70]}..." if excerpt.length > 73
|
||||||
excerpt
|
excerpt
|
||||||
|
|||||||
@ -19,3 +19,6 @@ E.cat = (templates) ->
|
|||||||
html = ''
|
html = ''
|
||||||
html += x.innerHTML for x in templates
|
html += x.innerHTML for x in templates
|
||||||
html
|
html
|
||||||
|
|
||||||
|
E.url = (content) ->
|
||||||
|
"data:text/html;charset=utf-8,<!doctype html>#{encodeURIComponent content.innerHTML}"
|
||||||
|
|||||||
@ -431,7 +431,7 @@ Index =
|
|||||||
Index.req = $.ajax "//a.4cdn.org/#{g.BOARD}/catalog.json",
|
Index.req = $.ajax "//a.4cdn.org/#{g.BOARD}/catalog.json",
|
||||||
onloadend: (e) -> Index.load e, state
|
onloadend: (e) -> Index.load e, state
|
||||||
,
|
,
|
||||||
whenModified: true
|
whenModified: 'Index'
|
||||||
$.addClass Index.button, 'fa-spin'
|
$.addClass Index.button, 'fa-spin'
|
||||||
|
|
||||||
load: (e, state) ->
|
load: (e, state) ->
|
||||||
|
|||||||
@ -2,11 +2,18 @@ Main =
|
|||||||
init: ->
|
init: ->
|
||||||
if location.hostname is 'www.google.com'
|
if location.hostname is 'www.google.com'
|
||||||
if location.pathname is '/recaptcha/api/fallback'
|
if location.pathname is '/recaptcha/api/fallback'
|
||||||
$.ready -> Captcha.noscript.initFrame()
|
$.ready -> Captcha.v2.initFrame()
|
||||||
else
|
$.get 'Captcha Fixes', true, ({'Captcha Fixes': enabled}) ->
|
||||||
$.get 'Captcha Fixes', true, ({'Captcha Fixes': enabled}) ->
|
if enabled
|
||||||
if enabled
|
$.ready -> Captcha.fixes.init()
|
||||||
$.ready -> Captcha.fixes.init()
|
return
|
||||||
|
|
||||||
|
if location.hostname is 'www.4chan.org'
|
||||||
|
$.onExists d.documentElement, 'body', false, -> $.addStyle Main.cssWWW
|
||||||
|
Conf = {'captchaLanguage': Config.captchaLanguage}
|
||||||
|
$.get Conf, (items) ->
|
||||||
|
$.extend Conf, items
|
||||||
|
Captcha.language.fixPage()
|
||||||
return
|
return
|
||||||
|
|
||||||
g.threads = new SimpleDict()
|
g.threads = new SimpleDict()
|
||||||
@ -57,6 +64,9 @@ Main =
|
|||||||
$.onExists doc, 'body', false, Main.initStyle
|
$.onExists doc, 'body', false, Main.initStyle
|
||||||
|
|
||||||
initFeatures: ->
|
initFeatures: ->
|
||||||
|
if location.hostname in ['boards.4chan.org', 'sys.4chan.org']
|
||||||
|
$.globalEval 'document.documentElement.classList.add("js-enabled");'
|
||||||
|
|
||||||
switch location.hostname
|
switch location.hostname
|
||||||
when 'a.4cdn.org'
|
when 'a.4cdn.org'
|
||||||
return
|
return
|
||||||
@ -298,17 +308,13 @@ Main =
|
|||||||
$.ready ->
|
$.ready ->
|
||||||
cb() if Main.isThisPageLegit()
|
cb() if Main.isThisPageLegit()
|
||||||
|
|
||||||
css: `<%=
|
css: `<%= importCSS('font-awesome', 'noscript', 'style', 'yotsuba', 'yotsuba-b', 'futaba', 'burichan', 'tomorrow', 'photon') %>`
|
||||||
grunt.template.process(
|
|
||||||
['font-awesome', 'style', 'yotsuba', 'yotsuba-b', 'futaba', 'burichan', 'tomorrow', 'photon'].map(function(name) {
|
cssWWW: `<%= importCSS('noscript', 'www') %>`
|
||||||
return grunt.file.read('src/General/css/'+name+'.css');
|
|
||||||
}).join(''),
|
|
||||||
{data: {type: type}}
|
|
||||||
).trim().replace(/\n+/g, '\n').split(/^/m).map(JSON.stringify).join(' +\n').replace(/`/g, '\\`')
|
|
||||||
%>`
|
|
||||||
|
|
||||||
features: [
|
features: [
|
||||||
['Polyfill', Polyfill]
|
['Polyfill', Polyfill]
|
||||||
|
['Captcha Language', Captcha.language]
|
||||||
['Redirect', Redirect]
|
['Redirect', Redirect]
|
||||||
['Header', Header]
|
['Header', Header]
|
||||||
['Catalog Links', CatalogLinks]
|
['Catalog Links', CatalogLinks]
|
||||||
|
|||||||
@ -103,6 +103,7 @@ Settings =
|
|||||||
description = arr[1]
|
description = arr[1]
|
||||||
div = $.el 'div',
|
div = $.el 'div',
|
||||||
<%= html('<label><input type="checkbox" name="${key}">${key}</label><span class="description">: ${description}</span>') %>
|
<%= html('<label><input type="checkbox" name="${key}">${key}</label><span class="description">: ${description}</span>') %>
|
||||||
|
div.hidden = true if chrome? and key is 'Remember QR Size' # XXX not supported
|
||||||
input = $ 'input', div
|
input = $ 'input', div
|
||||||
$.on input, 'change', ->
|
$.on input, 'change', ->
|
||||||
@parentNode.parentNode.dataset.checked = @checked
|
@parentNode.parentNode.dataset.checked = @checked
|
||||||
@ -314,7 +315,7 @@ Settings =
|
|||||||
|
|
||||||
items = {}
|
items = {}
|
||||||
inputs = {}
|
inputs = {}
|
||||||
for name in ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss', 'customCooldown']
|
for name in ['captchaLanguage', 'boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss', 'customCooldown']
|
||||||
input = $ "[name='#{name}']", section
|
input = $ "[name='#{name}']", section
|
||||||
items[name] = Conf[name]
|
items[name] = Conf[name]
|
||||||
inputs[name] = input
|
inputs[name] = input
|
||||||
@ -323,9 +324,9 @@ Settings =
|
|||||||
else if name is 'favicon'
|
else if name is 'favicon'
|
||||||
$.on input, 'change', $.cb.value
|
$.on input, 'change', $.cb.value
|
||||||
$.on input, 'change', Settings[name]
|
$.on input, 'change', Settings[name]
|
||||||
else
|
else
|
||||||
$.on input, 'input', $.cb.value
|
$.on input, 'input', $.cb.value
|
||||||
$.on input, 'input', Settings[name]
|
$.on input, 'input', Settings[name] if name of Settings
|
||||||
|
|
||||||
# Quick Reply Personas
|
# Quick Reply Personas
|
||||||
ta = $ '.personafield', section
|
ta = $ '.personafield', section
|
||||||
@ -338,8 +339,8 @@ Settings =
|
|||||||
for key, val of items
|
for key, val of items
|
||||||
input = inputs[key]
|
input = inputs[key]
|
||||||
input.value = val
|
input.value = val
|
||||||
continue if key in ['usercss', 'customCooldown']
|
if key of Settings and key isnt 'usercss'
|
||||||
Settings[key].call input
|
Settings[key].call input
|
||||||
return
|
return
|
||||||
|
|
||||||
interval = $ 'input[name="Interval"]', section
|
interval = $ 'input[name="Interval"]', section
|
||||||
@ -454,13 +455,15 @@ Settings =
|
|||||||
data =
|
data =
|
||||||
isReply: true
|
isReply: true
|
||||||
file:
|
file:
|
||||||
URL: '//i.4cdn.org/g/1334437723720.jpg'
|
url: '//i.4cdn.org/g/1334437723720.jpg'
|
||||||
name: 'd9bb2efc98dd0df141a94399ff5880b7.jpg'
|
name: 'd9bb2efc98dd0df141a94399ff5880b7.jpg'
|
||||||
size: '276 KB'
|
size: '276 KB'
|
||||||
sizeInBytes: 276 * 1024
|
sizeInBytes: 276 * 1024
|
||||||
dimensions: '1280x720'
|
dimensions: '1280x720'
|
||||||
isImage: true
|
isImage: true
|
||||||
|
isVideo: false
|
||||||
isSpoiler: true
|
isSpoiler: true
|
||||||
|
tag: 'Loop'
|
||||||
FileInfo.format @value, data, @nextElementSibling
|
FileInfo.format @value, data, @nextElementSibling
|
||||||
|
|
||||||
favicon: ->
|
favicon: ->
|
||||||
|
|||||||
9
src/General/css/noscript.css
Normal file
9
src/General/css/noscript.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
noscript > div, noscript > div > div {
|
||||||
|
height: 545px !important;
|
||||||
|
}
|
||||||
|
noscript > div > div > div:first-child, noscript iframe {
|
||||||
|
height: 423px !important;
|
||||||
|
}
|
||||||
|
:root:not(.js-enabled) #g-recaptcha {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
@ -105,10 +105,8 @@ hr + div.center:not(.ad-cnt):not(.topad):not(.middlead):not(.bottomad) {
|
|||||||
/* party hats */
|
/* party hats */
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
marquee,
|
:root:not(.js-enabled) #postForm {
|
||||||
.postMessage marquee + br,
|
display: table;
|
||||||
.postMessage marquee + br + br {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Anti-autoplay */
|
/* Anti-autoplay */
|
||||||
@ -166,6 +164,9 @@ audio.controls-added {
|
|||||||
#thread-watcher {
|
#thread-watcher {
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
:root.fixed:not(.gallery-open) #header-bar:not(.autohide) {
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
/* Header */
|
/* Header */
|
||||||
.fixed.top-header body {
|
.fixed.top-header body {
|
||||||
@ -532,6 +533,9 @@ div[data-checked="false"] > .suboption-list {
|
|||||||
.section-filter textarea {
|
.section-filter textarea {
|
||||||
height: 500px;
|
height: 500px;
|
||||||
}
|
}
|
||||||
|
.section-filter a, .section-advanced a {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
.section-sauce textarea {
|
.section-sauce textarea {
|
||||||
height: 350px;
|
height: 350px;
|
||||||
}
|
}
|
||||||
@ -867,12 +871,15 @@ span.hide-announcement {
|
|||||||
border: none;
|
border: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
:root.float #thread-stats > .move > span {
|
:root.float #thread-stats > .move > :not(#page-count) {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
:root.float #thread-stats {
|
:root.float #thread-stats {
|
||||||
padding: 0px 3px;
|
padding: 0px 3px;
|
||||||
}
|
}
|
||||||
|
#page-count {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
/* Quote */
|
/* Quote */
|
||||||
.catalog-thread > .comment > span.quote, #arc-list span.quote {
|
.catalog-thread > .comment > span.quote, #arc-list span.quote {
|
||||||
@ -1134,9 +1141,7 @@ input[name="Default Volume"] {
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
#qr select,
|
#qr select,
|
||||||
#url-button,
|
#qr-filename-container > a,
|
||||||
#custom-cooldown-button,
|
|
||||||
#dump-button,
|
|
||||||
.remove,
|
.remove,
|
||||||
.captcha-img {
|
.captcha-img {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -1148,6 +1153,10 @@ input[name="Default Volume"] {
|
|||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
border-radius: 3px 3px 0 0;
|
border-radius: 3px 3px 0 0;
|
||||||
}
|
}
|
||||||
|
#qr > form {
|
||||||
|
max-height: calc(100vh - 75px);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
#qrtab {
|
#qrtab {
|
||||||
border-radius: 3px 3px 0 0;
|
border-radius: 3px 3px 0 0;
|
||||||
}
|
}
|
||||||
@ -1158,13 +1167,6 @@ input[name="Default Volume"] {
|
|||||||
float: right;
|
float: right;
|
||||||
padding: 0 3px;
|
padding: 0 3px;
|
||||||
}
|
}
|
||||||
#qr .warning {
|
|
||||||
min-height: 1.6em;
|
|
||||||
vertical-align: middle;
|
|
||||||
padding: 0 1px;
|
|
||||||
border-width: 1px;
|
|
||||||
border-style: solid;
|
|
||||||
}
|
|
||||||
.qr-link-container {
|
.qr-link-container {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@ -1227,7 +1229,7 @@ input.field.tripped:not(:hover):not(:focus) {
|
|||||||
top: 2px;
|
top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Noscript Recaptcha */
|
/* Recaptcha v1 */
|
||||||
.captcha-img {
|
.captcha-img {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -1240,9 +1242,6 @@ input.field.tripped:not(:hover):not(:focus) {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 1px 0 0;
|
margin: 1px 0 0;
|
||||||
}
|
}
|
||||||
#qr-captcha-iframe {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Recaptcha v2 */
|
/* Recaptcha v2 */
|
||||||
#qr .captcha-root {
|
#qr .captcha-root {
|
||||||
@ -1268,6 +1267,21 @@ input.field.tripped:not(:hover):not(:focus) {
|
|||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
#qr-captcha-iframe {
|
||||||
|
width: 302px;
|
||||||
|
height: 423px;
|
||||||
|
border: 0;
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
.goog-bubble-content {
|
||||||
|
max-width: 100vw;
|
||||||
|
max-height: 100vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.goog-bubble-content iframe {
|
||||||
|
position: static !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* File Input, Submit Button */
|
/* File Input, Submit Button */
|
||||||
#file-n-submit {
|
#file-n-submit {
|
||||||
@ -1282,6 +1296,7 @@ input.field.tripped:not(:hover):not(:focus) {
|
|||||||
background: linear-gradient(to bottom, #F8F8F8, #DCDCDC) no-repeat;
|
background: linear-gradient(to bottom, #F8F8F8, #DCDCDC) no-repeat;
|
||||||
border: 1px solid #BBB;
|
border: 1px solid #BBB;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
#qr-file-button {
|
#qr-file-button {
|
||||||
width: 15%;
|
width: 15%;
|
||||||
@ -1462,6 +1477,8 @@ a:only-of-type > .remove {
|
|||||||
}
|
}
|
||||||
.textarea {
|
.textarea {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
:root.webkit .textarea {
|
:root.webkit .textarea {
|
||||||
margin-bottom: -2px;
|
margin-bottom: -2px;
|
||||||
@ -1475,6 +1492,9 @@ a:only-of-type > .remove {
|
|||||||
right: 1px;
|
right: 1px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
#char-count.warning {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
/* Menu */
|
/* Menu */
|
||||||
.menu-button:not(.fa-bars) {
|
.menu-button:not(.fa-bars) {
|
||||||
@ -1498,7 +1518,7 @@ a:only-of-type > .remove {
|
|||||||
height: 15px;
|
height: 15px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.menu-button + .container:not(:empty) {
|
.menu-button + .container :first-child {
|
||||||
margin-left: -5px;
|
margin-left: -5px;
|
||||||
}
|
}
|
||||||
#menu {
|
#menu {
|
||||||
@ -1745,13 +1765,6 @@ grunt.file.expand('src/General/img/links/*.png').map(function(file) {
|
|||||||
}
|
}
|
||||||
.gal-fit-height .gal-image img,
|
.gal-fit-height .gal-image img,
|
||||||
.gal-fit-height .gal-image video {
|
.gal-fit-height .gal-image video {
|
||||||
/*
|
|
||||||
Chrome doesn't support viewpoint units in calc()
|
|
||||||
http://bugs.chromium.org/168840
|
|
||||||
"It looks like the original author of viewport units in WebKit is not coming back to fix this stuff."
|
|
||||||
Well, fuck.
|
|
||||||
*/
|
|
||||||
max-height: 95vh;
|
|
||||||
max-height: calc(100vh - 25px);
|
max-height: calc(100vh - 25px);
|
||||||
}
|
}
|
||||||
.gal-image iframe {
|
.gal-image iframe {
|
||||||
|
|||||||
3
src/General/css/www.css
Normal file
3
src/General/css/www.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#captcha-cnt {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
@ -1,29 +1,35 @@
|
|||||||
?{file}{<div class="file" id="f${postID}">
|
?{file}{
|
||||||
?{file.isDeleted}{
|
<div class="file" id="f${postID}">
|
||||||
<span class="fileThumb">
|
?{boardID === "f"}{
|
||||||
<img src="${staticPath}filedeleted-res${gifIcon}" alt="File deleted." class="fileDeletedRes retina">
|
|
||||||
</span>
|
|
||||||
}{?{boardID === "f"}{
|
|
||||||
<div class="fileInfo"><span class="fileText" id="fT${postID}">
|
<div class="fileInfo"><span class="fileText" id="fT${postID}">
|
||||||
File:
|
File:
|
||||||
<a data-width="${file.width}" data-height="${file.height}" href="${file.url}" target="_blank">${file.name}</a>
|
<a data-width="${file.width}" data-height="${file.height}" href="${fileURL}" target="_blank">${file.name}</a>
|
||||||
-(${fileSize}, ${fileDims}, ${file.tag})
|
-(${file.size}, ${file.dimensions}?{file.tag}{, ${file.tag}})
|
||||||
</span></div>
|
</span></div>
|
||||||
}{
|
}{
|
||||||
<div class="fileText" id="fT${postID}"?{file.isSpoiler}{ title="${file.name}"}>
|
<div class="fileText" id="fT${postID}"?{file.isSpoiler}{ title="${file.name}"}>
|
||||||
File:
|
File:
|
||||||
<a?{file.name === shortFilename || file.isSpoiler}{}{ title="${file.name}"} href="${file.url}" target="_blank">
|
<a?{file.name === shortFilename || file.isSpoiler}{}{ title="${file.name}"} href="${fileURL}" target="_blank">
|
||||||
?{file.isSpoiler}{Spoiler Image}{${shortFilename}}
|
?{file.isSpoiler}{Spoiler Image}{${shortFilename}}
|
||||||
</a>
|
</a>
|
||||||
(${fileSize}, ${fileDims})
|
(${file.size}, ${file.dimensions || "PDF"})
|
||||||
</div>
|
</div>
|
||||||
<a class="fileThumb?{file.isSpoiler}{ imgspoiler}{}" href="${file.url}" target="_blank">
|
<a class="fileThumb?{file.isSpoiler}{ imgspoiler}{}" href="${fileURL}" target="_blank">
|
||||||
<img
|
<img
|
||||||
?{suppressThumb}{ data-src="${fileThumb}"}{ src="${fileThumb}"}
|
?{suppressThumb}{ data-src="${fileThumb}"}{ src="${fileThumb}"}
|
||||||
alt="${fileSize}"
|
alt="${file.size}"
|
||||||
data-md5="${file.MD5}"
|
data-md5="${file.MD5}"
|
||||||
style="height: ${file.isSpoiler ? 100 : file.theight}px; width: ${file.isSpoiler ? 100 : file.twidth}px;"
|
style="height: ${file.isSpoiler ? 100 : file.theight}px; width: ${file.isSpoiler ? 100 : file.twidth}px;"
|
||||||
>
|
>
|
||||||
</a>
|
</a>
|
||||||
}}
|
}
|
||||||
</div>}
|
</div>
|
||||||
|
}{
|
||||||
|
?{o.fileDeleted}{
|
||||||
|
<div class="file" id="f${postID}">
|
||||||
|
<span class="fileThumb">
|
||||||
|
<img src="${staticPath}filedeleted-res${gifIcon}" alt="File deleted." class="fileDeletedRes retina">
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
?{!isOP}{<div class="sideArrows" id="sa${postID}">>></div>}
|
?{o.isReply}{<div class="sideArrows" id="sa${postID}">>></div>}
|
||||||
<div id="p${postID}" class="post ${postClass}?{capcode === "admin_highlight"}{ highlightPost}">
|
<div id="p${postID}" class="post ${postClass}?{o.capcodeHighlight}{ highlightPost}">
|
||||||
?{isOP}{&{fileBlock}&{postInfo}}{&{postInfo}&{fileBlock}}
|
?{o.isReply}{&{postInfo}&{fileBlock}}{&{fileBlock}&{postInfo}}
|
||||||
<blockquote class="postMessage" id="m${postID}">&{comment}</blockquote>
|
<blockquote class="postMessage" id="m${postID}">&{commentHTML}</blockquote>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,24 +1,24 @@
|
|||||||
<div class="postInfo desktop" id="pi${postID}">
|
<div class="postInfo desktop" id="pi${postID}">
|
||||||
<input type="checkbox" name="${postID}" value="delete">
|
<input type="checkbox" name="${postID}" value="delete">
|
||||||
?{isOP || boardID === "f"}{<span class="subject">${subject}</span> }
|
?{!o.isReply || boardID === "f" || subject}{<span class="subject">${subject || ""}</span> }
|
||||||
<span class="nameBlock?{capcode}{ capcode${capcodeUC}}">
|
<span class="nameBlock?{capcode}{ capcode${capcodeUC}}">
|
||||||
?{email}{<a href="mailto:${encodeURIComponent(email).replace(/%40/g, "@")}" class="useremail">}
|
?{email}{<a href="mailto:${encodeURIComponent(email).replace(/%40/g, "@")}" class="useremail">}
|
||||||
<span class="name?{capcode}{ capcode}">${name}</span>
|
<span class="name?{capcode}{ capcode}">${name}</span>
|
||||||
?{tripcode}{ <span class="postertrip">${tripcode}</span>}
|
?{tripcode}{ <span class="postertrip">${tripcode}</span>}
|
||||||
?{capcode}{ <strong class="capcode hand id_${capcodeLC}" title="Highlight posts by ${capcodePlural}">## ${capcodeText}</strong>}
|
?{capcode}{ <strong class="capcode hand id_${capcodeLC}" title="Highlight posts by ${capcodePlural}">## ${capcode}</strong>}
|
||||||
?{email}{</a>}
|
?{email}{</a>}
|
||||||
?{boardID === "f" && isOP || capcode}{}{ }
|
?{boardID === "f" && !o.isReply || capcode}{}{ }
|
||||||
?{capcode}{ <img src="${staticPath}${capcodeLC}icon${gifIcon}" alt="${capcodeUC} Icon" title="This user is ${capcodeDescription}." class="identityIcon retina">}
|
?{capcode}{ <img src="${staticPath}${capcodeLC}icon${gifIcon}" alt="${capcodeUC} Icon" title="This user is ${capcodeDescription}." class="identityIcon retina">}
|
||||||
?{uniqueID && !capcode}{ <span class="posteruid id_${uniqueID}">(ID: <span class="hand" title="Highlight posts by this ID">${uniqueID}</span>)</span>}
|
?{uniqueID && !capcode}{ <span class="posteruid id_${uniqueID}">(ID: <span class="hand" title="Highlight posts by this ID">${uniqueID}</span>)</span>}
|
||||||
?{flagCode}{ <span title="${flagName}" class="flag flag-${flagCode.toLowerCase()}"></span>}
|
?{flagCode}{ <span title="${flag}" class="flag flag-${flagCode.toLowerCase()}"></span>}
|
||||||
</span>
|
</span>
|
||||||
<span class="dateTime" data-utc="${dateUTC}">${date}</span>
|
<span class="dateTime" data-utc="${dateUTC}">${dateText}</span>
|
||||||
<span class="postNum?{!(boardID === "f" && isOP)}{ desktop}">
|
<span class="postNum?{!(boardID === "f" && !o.isReply)}{ desktop}">
|
||||||
<a href="${postLink}" title="Link to this post">No.</a>
|
<a href="${postLink}" title="Link to this post">No.</a>
|
||||||
<a href="${quoteLink}" title="Reply to this post">${postID}</a>
|
<a href="${quoteLink}" title="Reply to this post">${postID}</a>
|
||||||
?{o.isSticky}{ <img src="${staticPath}sticky${gifIcon}" alt="Sticky" title="Sticky" class="stickyIcon retina">}
|
?{o.isSticky}{ <img src="${staticPath}sticky${gifIcon}" alt="Sticky" title="Sticky" class="stickyIcon retina">}
|
||||||
?{o.isClosed && !o.isArchived}{ <img src="${staticPath}closed${gifIcon}" alt="Closed" title="Closed" class="closedIcon retina">}
|
?{o.isClosed && !o.isArchived}{ <img src="${staticPath}closed${gifIcon}" alt="Closed" title="Closed" class="closedIcon retina">}
|
||||||
?{o.isArchived}{ <img src="${staticPath}archived${gifIcon}" alt="Archived" title="Archived" class="archivedIcon retina">}
|
?{o.isArchived}{ <img src="${staticPath}archived${gifIcon}" alt="Archived" title="Archived" class="archivedIcon retina">}
|
||||||
?{isOP && g.VIEW === "index"}{ <span>[<a href="/${boardID}/thread/${threadID}" class="replylink">Reply</a>]</span>}
|
?{!o.isReply && g.VIEW === "index"}{ <span>[<a href="/${boardID}/thread/${threadID}" class="replylink">Reply</a>]</span>}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
16
src/General/html/Features/Sandbox.html
Normal file
16
src/General/html/Features/Sandbox.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<html><head>
|
||||||
|
<title>[sb] ${url}</title>
|
||||||
|
<style>
|
||||||
|
iframe \{
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
border: 0;
|
||||||
|
\}
|
||||||
|
body \{
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
\}
|
||||||
|
</style>
|
||||||
|
</head><body>
|
||||||
|
<iframe sandbox="allow-forms" src="${url}"></iframe>
|
||||||
|
</body></html>
|
||||||
@ -12,6 +12,12 @@
|
|||||||
</table>
|
</table>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>Captcha Language</legend>
|
||||||
|
<div>Choose from <a href="https://developers.google.com/recaptcha/docs/language" target="_blank">list of language codes</a>. Leave blank to autoselect.</div>
|
||||||
|
<div><input name="captchaLanguage" class="field" spellcheck="false"></div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Custom Board Navigation</legend>
|
<legend>Custom Board Navigation</legend>
|
||||||
<div><textarea name="boardnav" class="field" spellcheck="false"></textarea></div>
|
<div><textarea name="boardnav" class="field" spellcheck="false"></textarea></div>
|
||||||
@ -41,7 +47,7 @@
|
|||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Time Formatting <span class="warning" data-feature="Time Formatting">is disabled.</span></legend>
|
<legend>Time Formatting <span class="warning" data-feature="Time Formatting">is disabled.</span></legend>
|
||||||
<div><input name="time" class="field" spellcheck="false">: <span class="time-preview"></span></div>
|
<div><input name="time" class="field" spellcheck="false">: <span class="time-preview"></span></div>
|
||||||
<div>Supported <a href="//en.wikipedia.org/wiki/Date_%28Unix%29#Formatting">format specifiers</a>:</div>
|
<div>Supported <a href="http://man7.org/linux/man-pages/man1/date.1.html" target="_blank">format specifiers</a>:</div>
|
||||||
<div>Day: <code>%a</code>, <code>%A</code>, <code>%d</code>, <code>%e</code></div>
|
<div>Day: <code>%a</code>, <code>%A</code>, <code>%d</code>, <code>%e</code></div>
|
||||||
<div>Month: <code>%m</code>, <code>%b</code>, <code>%B</code></div>
|
<div>Month: <code>%m</code>, <code>%b</code>, <code>%B</code></div>
|
||||||
<div>Year: <code>%y</code>, <code>%Y</code></div>
|
<div>Year: <code>%y</code>, <code>%Y</code></div>
|
||||||
@ -64,6 +70,7 @@
|
|||||||
<div>Spoiler indicator: <code>%p</code></div>
|
<div>Spoiler indicator: <code>%p</code></div>
|
||||||
<div>Size: <code>%B</code> (Bytes), <code>%K</code> (KB), <code>%M</code> (MB), <code>%s</code> (4chan default)</div>
|
<div>Size: <code>%B</code> (Bytes), <code>%K</code> (KB), <code>%M</code> (MB), <code>%s</code> (4chan default)</div>
|
||||||
<div>Resolution: <code>%r</code> (Displays 'PDF' for PDF files)</div>
|
<div>Resolution: <code>%r</code> (Displays 'PDF' for PDF files)</div>
|
||||||
|
<div>Tag: <code>%g</code>
|
||||||
<div>Literal <code>%</code>: <code>%%</code></div>
|
<div>Literal <code>%</code>: <code>%%</code></div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<div class="warning"><code>Filter</code> is disabled.</div>
|
<div class="warning"><code>Filter</code> is disabled.</div>
|
||||||
<p>
|
<p>
|
||||||
Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions">regular expressions</a>, one per line.<br>
|
Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions" target="_blank">regular expressions</a>, one per line.<br>
|
||||||
Lines starting with a <code>#</code> will be ignored.<br>
|
Lines starting with a <code>#</code> will be ignored.<br>
|
||||||
For example, <code>/weeaboo/i</code> will filter posts containing the string `<code>weeaboo</code>`, case-insensitive.<br>
|
For example, <code>/weeaboo/i</code> will filter posts containing the string `<code>weeaboo</code>`, case-insensitive.<br>
|
||||||
MD5 filtering uses exact string matching, not regular expressions.
|
MD5 filtering uses exact string matching, not regular expressions.
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
<div>You can specify a display text by appending <code>;text:[text]</code> to the URL.</div>
|
<div>You can specify a display text by appending <code>;text:[text]</code> to the URL.</div>
|
||||||
<div>You can specify the applicable boards by appending <code>;boards:[board1],[board2]</code>.</div>
|
<div>You can specify the applicable boards by appending <code>;boards:[board1],[board2]</code>.</div>
|
||||||
<div>You can specify the applicable file types by appending <code>;types:[extension1],[extension2]</code>.</div>
|
<div>You can specify the applicable file types by appending <code>;types:[extension1],[extension2]</code>.</div>
|
||||||
|
<div>You can open links with scripts and popups disabled by appending <code>;sandbox</code>.</div>
|
||||||
<ul>These parameters will be replaced by their corresponding values:
|
<ul>These parameters will be replaced by their corresponding values:
|
||||||
<li><code>%TURL</code>: Thumbnail URL.</li>
|
<li><code>%TURL</code>: Thumbnail URL.</li>
|
||||||
<li><code>%URL</code>: Full image URL.</li>
|
<li><code>%URL</code>: Full image URL.</li>
|
||||||
|
|||||||
@ -64,8 +64,8 @@ $.ajax = do ->
|
|||||||
options.onerror?()
|
options.onerror?()
|
||||||
return
|
return
|
||||||
if whenModified
|
if whenModified
|
||||||
r.setRequestHeader 'If-Modified-Since', lastModified[url] if url of lastModified
|
r.setRequestHeader 'If-Modified-Since', lastModified[whenModified][url] if lastModified[whenModified]?[url]?
|
||||||
$.on r, 'load', -> lastModified[url] = r.getResponseHeader 'Last-Modified'
|
$.on r, 'load', -> (lastModified[whenModified] or= {})[url] = r.getResponseHeader 'Last-Modified'
|
||||||
if /\.json$/.test url
|
if /\.json$/.test url
|
||||||
r.responseType = 'json'
|
r.responseType = 'json'
|
||||||
$.extend r, options
|
$.extend r, options
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
class Connection
|
class Connection
|
||||||
constructor: (@target, @origin, @cb) ->
|
constructor: (@target, @origin, @cb={}) ->
|
||||||
$.on window, 'message', @onMessage
|
$.on window, 'message', @onMessage
|
||||||
|
|
||||||
send: (data) =>
|
send: (data) =>
|
||||||
|
|||||||
@ -126,41 +126,41 @@ class Fetcher
|
|||||||
|
|
||||||
@threadID = +data.thread_num
|
@threadID = +data.thread_num
|
||||||
o =
|
o =
|
||||||
# id
|
|
||||||
postID: @postID
|
postID: @postID
|
||||||
threadID: @threadID
|
threadID: @threadID
|
||||||
boardID: @boardID
|
boardID: @boardID
|
||||||
# info
|
isReply: @postID isnt @threadID
|
||||||
name: data.name
|
o.info =
|
||||||
capcode: switch data.capcode
|
|
||||||
when 'M' then 'mod'
|
|
||||||
when 'A' then 'admin'
|
|
||||||
when 'D' then 'developer'
|
|
||||||
tripcode: data.trip
|
|
||||||
uniqueID: data.poster_hash
|
|
||||||
email: data.email or ''
|
|
||||||
subject: data.title
|
subject: data.title
|
||||||
|
email: data.email
|
||||||
|
name: data.name or ''
|
||||||
|
tripcode: data.trip
|
||||||
|
capcode: switch data.capcode
|
||||||
|
when 'M' then 'Mod'
|
||||||
|
when 'A' then 'Admin'
|
||||||
|
when 'D' then 'Developer'
|
||||||
|
uniqueID: data.poster_hash
|
||||||
flagCode: data.poster_country
|
flagCode: data.poster_country
|
||||||
flagName: data.poster_country_name
|
flag: data.poster_country_name
|
||||||
date: data.fourchan_date
|
|
||||||
dateUTC: data.timestamp
|
dateUTC: data.timestamp
|
||||||
comment: comment
|
dateText: data.fourchan_date
|
||||||
# file
|
commentHTML: comment
|
||||||
|
delete o.info.uniqueID if o.info.capcode
|
||||||
if data.media?.media_filename
|
if data.media?.media_filename
|
||||||
o.file =
|
o.file =
|
||||||
name: data.media.media_filename
|
name: data.media.media_filename
|
||||||
timestamp: data.media.media_orig
|
|
||||||
url: data.media.media_link or data.media.remote_media_link or
|
url: data.media.media_link or data.media.remote_media_link or
|
||||||
"//i.4cdn.org/#{@boardID}/#{encodeURIComponent data.media[if @boardID is 'f' then 'media_filename' else 'media_orig']}"
|
"#{location.protocol}//i.4cdn.org/#{@boardID}/#{encodeURIComponent data.media[if @boardID is 'f' then 'media_filename' else 'media_orig']}"
|
||||||
height: data.media.media_h
|
height: data.media.media_h
|
||||||
width: data.media.media_w
|
width: data.media.media_w
|
||||||
MD5: data.media.media_hash
|
MD5: data.media.media_hash
|
||||||
size: data.media.media_size
|
size: $.bytesToString data.media.media_size
|
||||||
turl: data.media.thumb_link or "//i.4cdn.org/#{@boardID}/#{data.media.preview_orig}"
|
thumbURL: data.media.thumb_link or "#{location.protocol}//i.4cdn.org/#{@boardID}/#{data.media.preview_orig}"
|
||||||
theight: data.media.preview_h
|
theight: data.media.preview_h
|
||||||
twidth: data.media.preview_w
|
twidth: data.media.preview_w
|
||||||
isSpoiler: data.media.spoiler is '1'
|
isSpoiler: data.media.spoiler is '1'
|
||||||
o.file.tag = JSON.parse(data.media.exif).Tag if @boardID is 'f'
|
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
|
||||||
|
|
||||||
board = g.boards[@boardID] or
|
board = g.boards[@boardID] or
|
||||||
new Board @boardID
|
new Board @boardID
|
||||||
@ -168,7 +168,7 @@ class Fetcher
|
|||||||
new Thread @threadID, board
|
new Thread @threadID, board
|
||||||
post = new Post Build.post(o), thread, board
|
post = new Post Build.post(o), thread, board
|
||||||
post.kill()
|
post.kill()
|
||||||
post.file.thumbURL = o.file.turl if post.file
|
post.file.thumbURL = o.file.thumbURL if post.file
|
||||||
post.isFetchedQuote = true
|
post.isFetchedQuote = true
|
||||||
Main.callbackNodes Post, [post]
|
Main.callbackNodes Post, [post]
|
||||||
@insert post
|
@insert post
|
||||||
|
|||||||
@ -1,19 +1,6 @@
|
|||||||
Polyfill =
|
Polyfill =
|
||||||
init: ->
|
init: ->
|
||||||
@notificationPermission()
|
|
||||||
@toBlob()
|
@toBlob()
|
||||||
@visibility()
|
|
||||||
notificationPermission: ->
|
|
||||||
return if !window.Notification or 'permission' of Notification or !window.webkitNotifications
|
|
||||||
Object.defineProperty Notification, 'permission',
|
|
||||||
get: ->
|
|
||||||
switch webkitNotifications.checkPermission()
|
|
||||||
when 0
|
|
||||||
'granted'
|
|
||||||
when 1
|
|
||||||
'default'
|
|
||||||
when 2
|
|
||||||
'denied'
|
|
||||||
toBlob: ->
|
toBlob: ->
|
||||||
HTMLCanvasElement::toBlob or= (cb) ->
|
HTMLCanvasElement::toBlob or= (cb) ->
|
||||||
data = atob @toDataURL()[22..]
|
data = atob @toDataURL()[22..]
|
||||||
@ -23,12 +10,3 @@ Polyfill =
|
|||||||
for i in [0...l] by 1
|
for i in [0...l] by 1
|
||||||
ui8a[i] = data.charCodeAt i
|
ui8a[i] = data.charCodeAt i
|
||||||
cb new Blob [ui8a], type: 'image/png'
|
cb new Blob [ui8a], type: 'image/png'
|
||||||
visibility: ->
|
|
||||||
# page visibility API
|
|
||||||
return if 'visibilityState' of d
|
|
||||||
Object.defineProperties HTMLDocument.prototype,
|
|
||||||
visibilityState:
|
|
||||||
get: -> @webkitVisibilityState
|
|
||||||
hidden:
|
|
||||||
get: -> @webkitHidden
|
|
||||||
$.on d, 'webkitvisibilitychange', -> $.event 'visibilitychange'
|
|
||||||
|
|||||||
@ -56,7 +56,7 @@ class Post
|
|||||||
@nodes.nameBlock.textContent.trim()
|
@nodes.nameBlock.textContent.trim()
|
||||||
if subject = $ '.subject', info
|
if subject = $ '.subject', info
|
||||||
@nodes.subject = subject
|
@nodes.subject = subject
|
||||||
@info.subject = subject.textContent
|
@info.subject = subject.textContent or undefined
|
||||||
if name = $ '.name', info
|
if name = $ '.name', info
|
||||||
@nodes.name = name
|
@nodes.name = name
|
||||||
@info.name = name.textContent
|
@info.name = name.textContent
|
||||||
@ -106,22 +106,28 @@ class Post
|
|||||||
# 'Comment too long'...
|
# 'Comment too long'...
|
||||||
# EXIF data. (/p/)
|
# EXIF data. (/p/)
|
||||||
# Rolls. (/tg/)
|
# Rolls. (/tg/)
|
||||||
# Marquees. (/pol/)
|
# Fortunes. (/s4s/)
|
||||||
|
bq = @nodes.comment.cloneNode true
|
||||||
|
for node in $$ '.abbr + br, .exif, b, .fortune', bq
|
||||||
|
$.rm node
|
||||||
|
if abbr = $ '.abbr', bq
|
||||||
|
$.rm abbr
|
||||||
|
@info.comment = @nodesToText bq
|
||||||
|
if abbr
|
||||||
|
@info.comment = @info.comment.replace /\n\n$/, ''
|
||||||
|
|
||||||
|
# Hide spoilers.
|
||||||
|
# Remove:
|
||||||
# Preceding and following new lines.
|
# Preceding and following new lines.
|
||||||
# Trailing spaces.
|
# Trailing spaces.
|
||||||
bq = @nodes.comment.cloneNode true
|
commentDisplay = @info.comment
|
||||||
for node in $$ '.abbr, .exif, b, marquee', bq
|
unless Conf['Remove Spoilers'] or Conf['Reveal Spoilers']
|
||||||
$.rm node
|
spoilers = $$ 's', bq
|
||||||
@info.comment = @nodesToText bq
|
if spoilers.length
|
||||||
|
for node in spoilers
|
||||||
# Get the comment's text with spoilers hidden.
|
$.replace node, $.tn '[spoiler]'
|
||||||
spoilers = $$ 's', bq
|
commentDisplay = @nodesToText bq
|
||||||
@info.commentSpoilered = if spoilers.length
|
@info.commentDisplay = commentDisplay.trim().replace /\s+$/gm, ''
|
||||||
for node in spoilers
|
|
||||||
$.replace node, $.tn '[spoiler]'
|
|
||||||
@nodesToText bq
|
|
||||||
else
|
|
||||||
@info.comment
|
|
||||||
|
|
||||||
nodesToText: (bq) ->
|
nodesToText: (bq) ->
|
||||||
text = ""
|
text = ""
|
||||||
@ -129,7 +135,7 @@ class Post
|
|||||||
i = 0
|
i = 0
|
||||||
while node = nodes.snapshotItem i++
|
while node = nodes.snapshotItem i++
|
||||||
text += node.data or '\n'
|
text += node.data or '\n'
|
||||||
text.trim().replace /\s+$/gm, ''
|
text
|
||||||
|
|
||||||
parseQuotes: ->
|
parseQuotes: ->
|
||||||
@quotes = []
|
@quotes = []
|
||||||
@ -170,12 +176,13 @@ class Post
|
|||||||
@file =
|
@file =
|
||||||
text: fileText
|
text: fileText
|
||||||
link: link
|
link: link
|
||||||
URL: link.href
|
url: link.href
|
||||||
name: fileText.title or link.title or link.textContent
|
name: fileText.title or link.title or link.textContent
|
||||||
size: info[1]
|
size: info[1]
|
||||||
isImage: /(jpg|png|gif)$/i.test link.href
|
isImage: /(jpg|png|gif)$/i.test link.href
|
||||||
isVideo: /webm$/i.test link.href
|
isVideo: /webm$/i.test link.href
|
||||||
dimensions: info[0].match(/\d+x\d+/)?[0]
|
dimensions: info[0].match(/\d+x\d+/)?[0]
|
||||||
|
tag: info[0].match(/,[^,]*, ([a-z]+)\)/i)?[1]
|
||||||
size = +@file.size.match(/[\d.]+/)[0]
|
size = +@file.size.match(/[\d.]+/)[0]
|
||||||
unit = ['B', 'KB', 'MB', 'GB'].indexOf @file.size.match(/\w+$/)[0]
|
unit = ['B', 'KB', 'MB', 'GB'].indexOf @file.size.match(/\w+$/)[0]
|
||||||
size *= 1024 while unit-- > 0
|
size *= 1024 while unit-- > 0
|
||||||
@ -183,7 +190,7 @@ class Post
|
|||||||
if (thumb = $ '.fileThumb > [data-md5]', fileEl)
|
if (thumb = $ '.fileThumb > [data-md5]', fileEl)
|
||||||
$.extend @file,
|
$.extend @file,
|
||||||
thumb: thumb
|
thumb: thumb
|
||||||
thumbURL: "#{location.protocol}//i.4cdn.org/#{@board}/#{link.href.match(/(\d+)\./)[1]}s.jpg"
|
thumbURL: if m = link.href.match(/\d+(?=\.\w+$)/) then "#{location.protocol}//i.4cdn.org/#{@board}/#{m[0]}s.jpg"
|
||||||
MD5: thumb.dataset.md5
|
MD5: thumb.dataset.md5
|
||||||
isSpoiler: $.hasClass thumb.parentNode, 'imgspoiler'
|
isSpoiler: $.hasClass thumb.parentNode, 'imgspoiler'
|
||||||
|
|
||||||
|
|||||||
@ -112,7 +112,7 @@ Gallery =
|
|||||||
|
|
||||||
thumb = $.el 'a',
|
thumb = $.el 'a',
|
||||||
className: 'gal-thumb'
|
className: 'gal-thumb'
|
||||||
href: post.file.URL
|
href: post.file.url
|
||||||
target: '_blank'
|
target: '_blank'
|
||||||
title: post.file.name
|
title: post.file.name
|
||||||
|
|
||||||
@ -128,53 +128,66 @@ Gallery =
|
|||||||
Gallery.images.push thumb
|
Gallery.images.push thumb
|
||||||
$.add Gallery.nodes.thumbs, thumb
|
$.add Gallery.nodes.thumbs, thumb
|
||||||
|
|
||||||
|
load: (thumb, errorCB) ->
|
||||||
|
ext = thumb.href.match /\w*$/
|
||||||
|
elType = {'webm': 'video', 'pdf': 'iframe'}[ext] or 'img'
|
||||||
|
file = $.el elType,
|
||||||
|
title: thumb.title
|
||||||
|
$.extend file.dataset, thumb.dataset
|
||||||
|
$.on file, 'error', errorCB
|
||||||
|
file.src = thumb.href
|
||||||
|
file
|
||||||
|
|
||||||
open: (thumb) ->
|
open: (thumb) ->
|
||||||
{nodes} = Gallery
|
{nodes} = Gallery
|
||||||
{name} = nodes
|
|
||||||
oldID = +nodes.current.dataset.id
|
oldID = +nodes.current.dataset.id
|
||||||
newID = +thumb.dataset.id
|
newID = +thumb.dataset.id
|
||||||
slideshow = Gallery.slideshow and (newID > oldID or (oldID is Gallery.images.length-1 and newID is 0))
|
|
||||||
|
|
||||||
$.rmClass el, 'gal-highlight' if el = $ '.gal-highlight', nodes.thumbs
|
# Highlight, center selected thumbnail
|
||||||
|
$.rmClass el, 'gal-highlight' if el = Gallery.images[oldID]
|
||||||
$.addClass thumb, 'gal-highlight'
|
$.addClass thumb, 'gal-highlight'
|
||||||
|
nodes.thumbs.scrollTop = thumb.offsetTop + thumb.offsetHeight/2 - nodes.thumbs.clientHeight/2
|
||||||
|
|
||||||
elType = if /\.webm$/.test(thumb.href)
|
# Load image or use preloaded image
|
||||||
'video'
|
if Gallery.cache?.dataset.id is ''+newID
|
||||||
else if /\.pdf$/.test(thumb.href)
|
file = Gallery.cache
|
||||||
'iframe'
|
$.off file, 'error', Gallery.cacheError
|
||||||
|
$.on file, 'error', Gallery.error
|
||||||
else
|
else
|
||||||
'img'
|
file = Gallery.load thumb, Gallery.error
|
||||||
|
|
||||||
$[if elType is 'iframe' then 'addClass' else 'rmClass'] doc, 'gal-pdf'
|
|
||||||
file = $.el elType,
|
|
||||||
title: name.download = name.textContent = thumb.title
|
|
||||||
$.extend file.dataset, thumb.dataset
|
|
||||||
$.on file, 'error', Gallery.error
|
|
||||||
file.src = name.href = thumb.href
|
|
||||||
|
|
||||||
|
# Replace old image with new one
|
||||||
$.off nodes.current, 'error', Gallery.error
|
$.off nodes.current, 'error', Gallery.error
|
||||||
ImageCommon.pause nodes.current
|
ImageCommon.pause nodes.current
|
||||||
$.replace nodes.current, file
|
$.replace nodes.current, file
|
||||||
if elType is 'video'
|
nodes.current = file
|
||||||
|
|
||||||
|
if file.nodeName is 'VIDEO'
|
||||||
file.loop = true
|
file.loop = true
|
||||||
Volume.setup file
|
Volume.setup file
|
||||||
file.play() if Conf['Autoplay']
|
file.play() if Conf['Autoplay']
|
||||||
ImageCommon.addControls file if Conf['Show Controls']
|
ImageCommon.addControls file if Conf['Show Controls']
|
||||||
|
|
||||||
|
doc.classList.toggle 'gal-pdf', file.nodeName is 'IFRAME'
|
||||||
|
Gallery.cb.setHeight()
|
||||||
nodes.count.textContent = +thumb.dataset.id + 1
|
nodes.count.textContent = +thumb.dataset.id + 1
|
||||||
nodes.current = file
|
nodes.name.download = nodes.name.textContent = thumb.title
|
||||||
|
nodes.name.href = thumb.href
|
||||||
nodes.frame.scrollTop = 0
|
nodes.frame.scrollTop = 0
|
||||||
nodes.next.focus()
|
nodes.next.focus()
|
||||||
if slideshow
|
|
||||||
|
# Continue slideshow if moving forward, stop otherwise
|
||||||
|
if Gallery.slideshow and (newID > oldID or (oldID is Gallery.images.length-1 and newID is 0))
|
||||||
Gallery.setupTimer()
|
Gallery.setupTimer()
|
||||||
else
|
else
|
||||||
Gallery.cb.stop()
|
Gallery.cb.stop()
|
||||||
|
|
||||||
# Scroll to post
|
# Scroll to post
|
||||||
if Conf['Scroll to Post'] and post = (post = g.posts[file.dataset.post])?.nodes.root
|
if Conf['Scroll to Post'] and (post = g.posts[file.dataset.post])
|
||||||
Header.scrollTo post
|
Header.scrollTo post.nodes.root
|
||||||
|
|
||||||
# Center selected thumbnail
|
# Preload next image
|
||||||
nodes.thumbs.scrollTop = thumb.offsetTop + thumb.offsetHeight/2 - nodes.thumbs.clientHeight/2
|
Gallery.cache = Gallery.load Gallery.images[(newID + 1) % Gallery.images.length], Gallery.cacheError
|
||||||
|
|
||||||
error: ->
|
error: ->
|
||||||
if @error?.code is MediaError.MEDIA_ERR_DECODE
|
if @error?.code is MediaError.MEDIA_ERR_DECODE
|
||||||
@ -185,6 +198,9 @@ Gallery =
|
|||||||
Gallery.images[@dataset.id].href = url
|
Gallery.images[@dataset.id].href = url
|
||||||
@src = url if Gallery.nodes.current is @
|
@src = url if Gallery.nodes.current is @
|
||||||
|
|
||||||
|
cacheError: ->
|
||||||
|
delete Gallery.cache
|
||||||
|
|
||||||
cleanupTimer: ->
|
cleanupTimer: ->
|
||||||
clearTimeout Gallery.timeoutID
|
clearTimeout Gallery.timeoutID
|
||||||
{current} = Gallery.nodes
|
{current} = Gallery.nodes
|
||||||
@ -301,6 +317,14 @@ Gallery =
|
|||||||
setFitness: ->
|
setFitness: ->
|
||||||
(if @checked then $.addClass else $.rmClass) doc, "gal-#{@name.toLowerCase().replace /\s+/g, '-'}"
|
(if @checked then $.addClass else $.rmClass) doc, "gal-#{@name.toLowerCase().replace /\s+/g, '-'}"
|
||||||
|
|
||||||
|
setHeight: ->
|
||||||
|
{current, frame} = Gallery.nodes
|
||||||
|
current.style.minHeight = if Conf['Stretch to Fit'] and (dim = g.posts[current.dataset.post]?.file.dimensions)
|
||||||
|
[width, height] = dim.split 'x'
|
||||||
|
Math.min(doc.clientHeight - 25, height / width * frame.clientWidth) + 'px'
|
||||||
|
else
|
||||||
|
null
|
||||||
|
|
||||||
setDelay: -> Gallery.delay = +@value
|
setDelay: -> Gallery.delay = +@value
|
||||||
|
|
||||||
menu:
|
menu:
|
||||||
@ -319,14 +343,14 @@ Gallery =
|
|||||||
createSubEntry: (name) ->
|
createSubEntry: (name) ->
|
||||||
label = UI.checkbox name, name
|
label = UI.checkbox name, name
|
||||||
input = label.firstElementChild
|
input = label.firstElementChild
|
||||||
if name in ['Fit Width', 'Fit Height', 'Hide Thumbnails']
|
$.on input, 'change', Gallery.cb.setFitness if name in ['Hide Thumbnails', 'Fit Width', 'Fit Height']
|
||||||
$.on input, 'change', Gallery.cb.setFitness
|
|
||||||
$.event 'change', null, input
|
$.event 'change', null, input
|
||||||
$.on input, 'change', $.cb.checked
|
$.on input, 'change', $.cb.checked
|
||||||
|
$.on input, 'change', Gallery.cb.setHeight if name in ['Hide Thumbnails', 'Fit Width', 'Fit Height', 'Stretch to Fit']
|
||||||
el: label
|
el: label
|
||||||
|
|
||||||
createSubEntries: ->
|
createSubEntries: ->
|
||||||
subEntries = (Gallery.menu.createSubEntry item for item in ['Hide Thumbnails', 'Fit Width', 'Fit Height', 'Scroll to Post'])
|
subEntries = (Gallery.menu.createSubEntry item for item in ['Hide Thumbnails', 'Fit Width', 'Fit Height', 'Stretch to Fit', 'Scroll to Post'])
|
||||||
|
|
||||||
delayLabel = $.el 'label', <%= html('Slide Delay: <input type="number" name="Slide Delay" min="0" step="any" class="field">') %>
|
delayLabel = $.el 'label', <%= html('Slide Delay: <input type="number" name="Slide Delay" min="0" step="any" class="field">') %>
|
||||||
delayInput = delayLabel.firstElementChild
|
delayInput = delayLabel.firstElementChild
|
||||||
|
|||||||
@ -34,7 +34,7 @@ ImageCommon =
|
|||||||
return true
|
return true
|
||||||
|
|
||||||
error: (file, post, delay, cb) ->
|
error: (file, post, delay, cb) ->
|
||||||
src = post.file.URL.split '/'
|
src = post.file.url.split '/'
|
||||||
URL = Redirect.to 'file',
|
URL = Redirect.to 'file',
|
||||||
boardID: post.board.ID
|
boardID: post.board.ID
|
||||||
filename: src[src.length - 1]
|
filename: src[src.length - 1]
|
||||||
@ -51,10 +51,10 @@ ImageCommon =
|
|||||||
cb URL
|
cb URL
|
||||||
|
|
||||||
<% if (type === 'crx') { %>
|
<% if (type === 'crx') { %>
|
||||||
$.ajax post.file.URL,
|
$.ajax post.file.url,
|
||||||
onloadend: ->
|
onloadend: ->
|
||||||
if @status is 200
|
if @status is 200
|
||||||
URL = post.file.URL
|
URL = post.file.url
|
||||||
else
|
else
|
||||||
post.kill true if @status is 404
|
post.kill true if @status is 404
|
||||||
redirect()
|
redirect()
|
||||||
@ -74,7 +74,7 @@ ImageCommon =
|
|||||||
post.kill true
|
post.kill true
|
||||||
redirect()
|
redirect()
|
||||||
else
|
else
|
||||||
URL = post.file.URL
|
URL = post.file.url
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
# Add controls, but not until the mouse is moved over the video.
|
# Add controls, but not until the mouse is moved over the video.
|
||||||
|
|||||||
@ -122,7 +122,7 @@ ImageExpand =
|
|||||||
$.rmClass post.nodes.root, 'expanded-image'
|
$.rmClass post.nodes.root, 'expanded-image'
|
||||||
$.rmClass file.thumb, 'expanding'
|
$.rmClass file.thumb, 'expanding'
|
||||||
$.rm file.videoControls if file.videoControls
|
$.rm file.videoControls if file.videoControls
|
||||||
file.thumb.parentNode.href = file.URL
|
file.thumb.parentNode.href = file.url
|
||||||
file.thumb.parentNode.target = '_blank'
|
file.thumb.parentNode.target = '_blank'
|
||||||
for x in ['isExpanding', 'isExpanded', 'videoControls', 'wasPlaying', 'scrollIntoView']
|
for x in ['isExpanding', 'isExpanded', 'videoControls', 'wasPlaying', 'scrollIntoView']
|
||||||
delete file[x]
|
delete file[x]
|
||||||
@ -175,7 +175,7 @@ ImageExpand =
|
|||||||
el = file.fullImage = $.el (if isVideo then 'video' else 'img')
|
el = file.fullImage = $.el (if isVideo then 'video' else 'img')
|
||||||
el.dataset.fullID = post.fullID
|
el.dataset.fullID = post.fullID
|
||||||
$.on el, 'error', ImageExpand.error
|
$.on el, 'error', ImageExpand.error
|
||||||
el.src = src or file.URL
|
el.src = src or file.url
|
||||||
|
|
||||||
el.className = 'full-image'
|
el.className = 'full-image'
|
||||||
$.after thumb, el
|
$.after thumb, el
|
||||||
|
|||||||
@ -32,7 +32,7 @@ ImageHover =
|
|||||||
el = $.el (if isVideo then 'video' else 'img')
|
el = $.el (if isVideo then 'video' else 'img')
|
||||||
el.dataset.fullID = post.fullID
|
el.dataset.fullID = post.fullID
|
||||||
$.on el, 'error', error
|
$.on el, 'error', error
|
||||||
el.src = file.URL
|
el.src = file.url
|
||||||
|
|
||||||
if Conf['Restart when Opened']
|
if Conf['Restart when Opened']
|
||||||
ImageCommon.rewind el
|
ImageCommon.rewind el
|
||||||
|
|||||||
@ -44,7 +44,7 @@ ImageLoader =
|
|||||||
video.setAttribute 'muted', 'muted'
|
video.setAttribute 'muted', 'muted'
|
||||||
video.dataset.md5 = thumb.dataset.md5
|
video.dataset.md5 = thumb.dataset.md5
|
||||||
video.style[attr] = thumb.style[attr] for attr in ['height', 'width', 'maxHeight', 'maxWidth']
|
video.style[attr] = thumb.style[attr] for attr in ['height', 'width', 'maxHeight', 'maxWidth']
|
||||||
video.src = file.URL
|
video.src = file.url
|
||||||
$.replace thumb, video
|
$.replace thumb, video
|
||||||
file.thumb = video
|
file.thumb = video
|
||||||
file.videoThumb = true
|
file.videoThumb = true
|
||||||
@ -52,9 +52,9 @@ ImageLoader =
|
|||||||
prefetch: (post) ->
|
prefetch: (post) ->
|
||||||
{file} = post
|
{file} = post
|
||||||
return unless file
|
return unless file
|
||||||
{isImage, isVideo, thumb, URL} = file
|
{isImage, isVideo, thumb, url} = file
|
||||||
return if file.isPrefetched or !(isImage or isVideo) or post.isHidden or post.thread.isHidden
|
return if file.isPrefetched or !(isImage or isVideo) or post.isHidden or post.thread.isHidden
|
||||||
type = if (match = URL.match(/\.([^.]+)$/)[1].toUpperCase()) is 'JPEG' then 'JPG' else match
|
type = if (match = url.match(/\.([^.]+)$/)[1].toUpperCase()) is 'JPEG' then 'JPG' else match
|
||||||
replace = Conf["Replace #{type}"] and !/spoiler/.test(thumb.src or thumb.dataset.src)
|
replace = Conf["Replace #{type}"] and !/spoiler/.test(thumb.src or thumb.dataset.src)
|
||||||
return unless replace or Conf['prefetch']
|
return unless replace or Conf['prefetch']
|
||||||
return unless [post, post.clones...].some (clone) -> doc.contains clone.nodes.root
|
return unless [post, post.clones...].some (clone) -> doc.contains clone.nodes.root
|
||||||
@ -70,11 +70,11 @@ ImageLoader =
|
|||||||
el = $.el if isImage then 'img' else 'video'
|
el = $.el if isImage then 'img' else 'video'
|
||||||
if replace and isImage
|
if replace and isImage
|
||||||
$.on el, 'load', ->
|
$.on el, 'load', ->
|
||||||
clone.file.thumb.src = URL for clone in post.clones
|
clone.file.thumb.src = url for clone in post.clones
|
||||||
thumb.src = URL
|
thumb.src = url
|
||||||
# XXX https://bugzilla.mozilla.org/show_bug.cgi?id=1021289
|
# XXX https://bugzilla.mozilla.org/show_bug.cgi?id=1021289
|
||||||
thumb.removeAttribute 'data-src'
|
thumb.removeAttribute 'data-src'
|
||||||
el.src = URL
|
el.src = url
|
||||||
|
|
||||||
toggle: ->
|
toggle: ->
|
||||||
if Conf['prefetch'] = @checked
|
if Conf['prefetch'] = @checked
|
||||||
|
|||||||
@ -7,7 +7,7 @@ Metadata =
|
|||||||
cb: @node
|
cb: @node
|
||||||
|
|
||||||
node: ->
|
node: ->
|
||||||
return unless @file and /webm$/i.test @file.URL
|
return unless @file and /webm$/i.test @file.url
|
||||||
if @isClone
|
if @isClone
|
||||||
el = $ '.webm-title', @file.text
|
el = $ '.webm-title', @file.text
|
||||||
else
|
else
|
||||||
@ -21,7 +21,7 @@ Metadata =
|
|||||||
load: ->
|
load: ->
|
||||||
$.rmClass @parentNode, 'error'
|
$.rmClass @parentNode, 'error'
|
||||||
$.addClass @parentNode, 'loading'
|
$.addClass @parentNode, 'loading'
|
||||||
CrossOrigin.binary Get.postFromNode(@).file.URL, (data) =>
|
CrossOrigin.binary Get.postFromNode(@).file.url, (data) =>
|
||||||
$.rmClass @parentNode, 'loading'
|
$.rmClass @parentNode, 'loading'
|
||||||
if data?
|
if data?
|
||||||
title = Metadata.parse data
|
title = Metadata.parse data
|
||||||
|
|||||||
@ -16,26 +16,35 @@ Sauce =
|
|||||||
name: 'Sauce'
|
name: 'Sauce'
|
||||||
cb: @node
|
cb: @node
|
||||||
|
|
||||||
|
sandbox: (url) ->
|
||||||
|
E.url <%= importHTML('Features/Sandbox') %>
|
||||||
|
|
||||||
|
rmOrigin: (e) ->
|
||||||
|
return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0
|
||||||
|
# Work around mixed content restrictions (data: URIs have inherited origin).
|
||||||
|
$.open @href
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
createSauceLink: (link, post) ->
|
createSauceLink: (link, post) ->
|
||||||
return null unless link = link.trim()
|
return null unless link = link.trim()
|
||||||
|
|
||||||
parts = {}
|
parts = {}
|
||||||
for part, i in link.split /;(?=(?:text|boards|types):)/
|
for part, i in link.split /;(?=(?:text|boards|types|sandbox):?)/
|
||||||
if i is 0
|
if i is 0
|
||||||
parts['url'] = part
|
parts['url'] = part
|
||||||
else
|
else
|
||||||
m = part.match /^(\w*):(.*)$/
|
m = part.match /^(\w*):?(.*)$/
|
||||||
parts[m[1]] = m[2]
|
parts[m[1]] = m[2]
|
||||||
parts['text'] or= parts['url'].match(/(\w+)\.\w+\//)?[1] or '?'
|
parts['text'] or= parts['url'].match(/(\w+)\.\w+\//)?[1] or '?'
|
||||||
ext = post.file.URL.match(/[^.]*$/)[0]
|
ext = post.file.url.match(/[^.]*$/)[0]
|
||||||
|
|
||||||
skip = false
|
skip = false
|
||||||
for key of parts
|
for key of parts
|
||||||
parts[key] = parts[key].replace /%(T?URL|IMG|MD5|board|name|%|semi)/g, (parameter) ->
|
parts[key] = parts[key].replace /%(T?URL|IMG|MD5|board|name|%|semi)/g, (parameter) ->
|
||||||
type = {
|
type = {
|
||||||
'%TURL': post.file.thumbURL
|
'%TURL': post.file.thumbURL
|
||||||
'%URL': post.file.URL
|
'%URL': post.file.url
|
||||||
'%IMG': if ext in ['gif', 'jpg', 'png'] then post.file.URL else post.file.thumbURL
|
'%IMG': if ext in ['gif', 'jpg', 'png'] then post.file.url else post.file.thumbURL
|
||||||
'%MD5': post.file.MD5
|
'%MD5': post.file.MD5
|
||||||
'%board': post.board.ID
|
'%board': post.board.ID
|
||||||
'%name': post.file.name
|
'%name': post.file.name
|
||||||
@ -55,10 +64,14 @@ Sauce =
|
|||||||
return null unless !parts['boards'] or post.board.ID in parts['boards'].split ','
|
return null unless !parts['boards'] or post.board.ID in parts['boards'].split ','
|
||||||
return null unless !parts['types'] or ext in parts['types'].split ','
|
return null unless !parts['types'] or ext in parts['types'].split ','
|
||||||
|
|
||||||
|
url = parts['url']
|
||||||
|
url = Sauce.sandbox url if parts['sandbox']?
|
||||||
|
|
||||||
a = Sauce.link.cloneNode true
|
a = Sauce.link.cloneNode true
|
||||||
a.href = parts['url']
|
a.href = url
|
||||||
a.textContent = parts['text']
|
a.textContent = parts['text']
|
||||||
a.removeAttribute 'target' if /^javascript:/i.test parts['url']
|
a.removeAttribute 'target' if /^javascript:/i.test parts['url']
|
||||||
|
$.on a, 'click', Sauce.rmOrigin if parts['sandbox']?
|
||||||
a
|
a
|
||||||
|
|
||||||
node: ->
|
node: ->
|
||||||
|
|||||||
@ -70,6 +70,7 @@ Volume =
|
|||||||
$.on @nodes.thumb, 'wheel', Volume.wheel.bind(Header.hover)
|
$.on @nodes.thumb, 'wheel', Volume.wheel.bind(Header.hover)
|
||||||
|
|
||||||
wheel: (e) ->
|
wheel: (e) ->
|
||||||
|
return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey
|
||||||
return unless el = $ 'video:not([data-md5])', @
|
return unless el = $ 'video:not([data-md5])', @
|
||||||
return if el.muted or not $.hasAudio el
|
return if el.muted or not $.hasAudio el
|
||||||
volume = el.volume + 0.1
|
volume = el.volume + 0.1
|
||||||
|
|||||||
@ -185,7 +185,7 @@ Embedding =
|
|||||||
el = $.el 'iframe'
|
el = $.el 'iframe'
|
||||||
el.setAttribute 'sandbox', 'allow-scripts'
|
el.setAttribute 'sandbox', 'allow-scripts'
|
||||||
content = <%= html('<html><head><title>${a.dataset.uid}</title></head><body><script src="https://gist.github.com/${a.dataset.uid}.js"></script></body></html>') %>
|
content = <%= html('<html><head><title>${a.dataset.uid}</title></head><body><script src="https://gist.github.com/${a.dataset.uid}.js"></script></body></html>') %>
|
||||||
el.src = "data:text/html;charset=utf-8,<!doctype html>#{encodeURIComponent content.innerHTML}"
|
el.src = E.url content
|
||||||
el
|
el
|
||||||
title:
|
title:
|
||||||
api: (uid) -> "https://api.github.com/gists/#{uid}"
|
api: (uid) -> "https://api.github.com/gists/#{uid}"
|
||||||
|
|||||||
@ -14,6 +14,6 @@ DownloadLink =
|
|||||||
order: 100
|
order: 100
|
||||||
open: ({file}) ->
|
open: ({file}) ->
|
||||||
return false unless file
|
return false unless file
|
||||||
a.href = file.URL
|
a.href = file.url
|
||||||
a.download = file.name
|
a.download = file.name
|
||||||
true
|
true
|
||||||
|
|||||||
@ -5,20 +5,26 @@ ReportLink =
|
|||||||
a = $.el 'a',
|
a = $.el 'a',
|
||||||
className: 'report-link'
|
className: 'report-link'
|
||||||
href: 'javascript:;'
|
href: 'javascript:;'
|
||||||
textContent: 'Report this post'
|
|
||||||
$.on a, 'click', ReportLink.report
|
$.on a, 'click', ReportLink.report
|
||||||
|
|
||||||
Menu.menu.addEntry
|
Menu.menu.addEntry
|
||||||
el: a
|
el: a
|
||||||
order: 10
|
order: 10
|
||||||
open: (post) ->
|
open: (post) ->
|
||||||
ReportLink.url = unless post.isDead
|
unless post.isDead or (post.thread.isDead and not post.thread.isArchived)
|
||||||
"//sys.4chan.org/#{post.board}/imgboard.php?mode=report&no=#{post}"
|
a.textContent = 'Report this post'
|
||||||
|
ReportLink.url = "//sys.4chan.org/#{post.board}/imgboard.php?mode=report&no=#{post}"
|
||||||
|
ReportLink.height = 200
|
||||||
else if Conf['Archive Report']
|
else if Conf['Archive Report']
|
||||||
Redirect.to 'report', {boardID: post.board.ID, postID: post.ID}
|
a.textContent = 'Report to archive'
|
||||||
|
ReportLink.url = Redirect.to 'report', {boardID: post.board.ID, postID: post.ID}
|
||||||
|
ReportLink.height = 350
|
||||||
|
else
|
||||||
|
ReportLink.url = ''
|
||||||
!!ReportLink.url
|
!!ReportLink.url
|
||||||
|
|
||||||
report: ->
|
report: ->
|
||||||
{url} = ReportLink
|
{url, height} = ReportLink
|
||||||
id = Date.now()
|
id = Date.now()
|
||||||
set = "toolbar=0,scrollbars=1,location=0,status=1,menubar=0,resizable=1,width=685,height=200"
|
set = "toolbar=0,scrollbars=1,location=0,status=1,menubar=0,resizable=1,width=700,height=#{height}"
|
||||||
window.open url, id, set
|
window.open url, id, set
|
||||||
|
|||||||
@ -28,10 +28,10 @@ FileInfo =
|
|||||||
$.extend outputNode, <%= html('@{output}') %>
|
$.extend outputNode, <%= html('@{output}') %>
|
||||||
|
|
||||||
formatters:
|
formatters:
|
||||||
t: -> <%= html('${this.file.URL.match(/[^\/]*$/)[0]}') %>
|
t: -> <%= html('${this.file.url.match(/[^\/]*$/)[0]}') %>
|
||||||
T: -> <%= html('<a href="${this.file.URL}" target="_blank">&{FileInfo.formatters.t.call(this)}</a>') %>
|
T: -> <%= html('<a href="${this.file.url}" target="_blank">&{FileInfo.formatters.t.call(this)}</a>') %>
|
||||||
l: -> <%= html('<a href="${this.file.URL}" target="_blank">&{FileInfo.formatters.n.call(this)}</a>') %>
|
l: -> <%= html('<a href="${this.file.url}" target="_blank">&{FileInfo.formatters.n.call(this)}</a>') %>
|
||||||
L: -> <%= html('<a href="${this.file.URL}" target="_blank">&{FileInfo.formatters.N.call(this)}</a>') %>
|
L: -> <%= html('<a href="${this.file.url}" target="_blank">&{FileInfo.formatters.N.call(this)}</a>') %>
|
||||||
n: ->
|
n: ->
|
||||||
fullname = @file.name
|
fullname = @file.name
|
||||||
shortname = Build.shortFilename @file.name, @isReply
|
shortname = Build.shortFilename @file.name, @isReply
|
||||||
@ -46,4 +46,5 @@ FileInfo =
|
|||||||
K: -> <%= html('${Math.round(this.file.sizeInBytes/1024)} KB') %>
|
K: -> <%= html('${Math.round(this.file.sizeInBytes/1024)} KB') %>
|
||||||
M: -> <%= html('${Math.round(this.file.sizeInBytes/1048576*100)/100} MB') %>
|
M: -> <%= html('${Math.round(this.file.sizeInBytes/1048576*100)/100} MB') %>
|
||||||
r: -> <%= html('${this.file.dimensions || "PDF"}') %>
|
r: -> <%= html('${this.file.dimensions || "PDF"}') %>
|
||||||
|
g: -> <%= html('?{this.file.tag}{, ${this.file.tag}}{}') %>
|
||||||
'%': -> <%= html('%') %>
|
'%': -> <%= html('%') %>
|
||||||
|
|||||||
@ -60,9 +60,10 @@ Fourchan =
|
|||||||
|
|
||||||
code: ->
|
code: ->
|
||||||
return if @isClone
|
return if @isClone
|
||||||
for pre, i in $$('.prettyprint', @nodes.comment) when not $.hasClass(pre, 'prettyprinted')
|
$.ready =>
|
||||||
$.event 'prettyprint', {ID: @fullID, i: i, html: pre.innerHTML}, window
|
for pre, i in $$('.prettyprint', @nodes.comment) when not $.hasClass(pre, 'prettyprinted')
|
||||||
return
|
$.event 'prettyprint', {ID: @fullID, i: i, html: pre.innerHTML}, window
|
||||||
|
return
|
||||||
|
|
||||||
math: ->
|
math: ->
|
||||||
return if (@isClone and doc.contains @origin.nodes.root) or !$ '.math', @nodes.comment
|
return if (@isClone and doc.contains @origin.nodes.root) or !$ '.math', @nodes.comment
|
||||||
|
|||||||
@ -204,6 +204,8 @@ Keybinds =
|
|||||||
'Enter'
|
'Enter'
|
||||||
when 27
|
when 27
|
||||||
'Esc'
|
'Esc'
|
||||||
|
when 32
|
||||||
|
'Space'
|
||||||
when 37
|
when 37
|
||||||
'Left'
|
'Left'
|
||||||
when 38
|
when 38
|
||||||
@ -212,9 +214,15 @@ Keybinds =
|
|||||||
'Right'
|
'Right'
|
||||||
when 40
|
when 40
|
||||||
'Down'
|
'Down'
|
||||||
|
when 188
|
||||||
|
'Comma'
|
||||||
|
when 190
|
||||||
|
'Period'
|
||||||
else
|
else
|
||||||
if 48 <= kc <= 57 or 65 <= kc <= 90 # 0-9, A-Z
|
if 48 <= kc <= 57 or 65 <= kc <= 90 # 0-9, A-Z
|
||||||
String.fromCharCode(kc).toLowerCase()
|
String.fromCharCode(kc).toLowerCase()
|
||||||
|
else if 96 <= kc <= 105 # numpad 0-9
|
||||||
|
String.fromCharCode(kc - 48).toLowerCase()
|
||||||
else
|
else
|
||||||
null
|
null
|
||||||
if key
|
if key
|
||||||
|
|||||||
@ -1,19 +1,26 @@
|
|||||||
Report =
|
Report =
|
||||||
|
css: `<%= importCSS('noscript') %>`
|
||||||
|
|
||||||
init: ->
|
init: ->
|
||||||
return unless /\bmode=report\b/.test(location.search) and match = location.search.match /\bno=(\d+)/
|
return unless /\bmode=report\b/.test(location.search) and match = location.search.match /\bno=(\d+)/
|
||||||
|
Captcha.language.fixPage()
|
||||||
@postID = +match[1]
|
@postID = +match[1]
|
||||||
$.ready @ready
|
$.ready @ready
|
||||||
|
|
||||||
ready: ->
|
ready: ->
|
||||||
new MutationObserver(Report.resize).observe d.body,
|
$.addStyle Report.css
|
||||||
childList: true
|
|
||||||
attributes: true
|
|
||||||
subtree: true
|
|
||||||
Report.archive() if Conf['Archive Report']
|
Report.archive() if Conf['Archive Report']
|
||||||
|
if $.hasClass doc, 'js-enabled'
|
||||||
|
new MutationObserver(-> Report.fit '.gc-bubbleDefault').observe d.body,
|
||||||
|
childList: true
|
||||||
|
attributes: true
|
||||||
|
subtree: true
|
||||||
|
else
|
||||||
|
Report.fit 'body'
|
||||||
|
|
||||||
resize: ->
|
fit: (selector) ->
|
||||||
return unless bubble = $ '.gc-bubbleDefault'
|
return unless el = $ selector, doc
|
||||||
dy = bubble.getBoundingClientRect().bottom - doc.clientHeight
|
dy = el.getBoundingClientRect().bottom - doc.clientHeight + 8
|
||||||
window.resizeBy 0, dy if dy > 0
|
window.resizeBy 0, dy if dy > 0
|
||||||
|
|
||||||
archive: ->
|
archive: ->
|
||||||
@ -21,14 +28,20 @@ Report =
|
|||||||
return unless url = Redirect.to 'report', {boardID: g.BOARD.ID, postID: Report.postID}
|
return unless url = Redirect.to 'report', {boardID: g.BOARD.ID, postID: Report.postID}
|
||||||
|
|
||||||
if (message = $ 'h3') and /Report submitted!/.test(message.textContent)
|
if (message = $ 'h3') and /Report submitted!/.test(message.textContent)
|
||||||
$.globalEval 'self.close = function(){};'
|
if location.hash is '#redirect'
|
||||||
window.resizeTo 685, 320
|
$.globalEval 'self.close = function(){};'
|
||||||
location.replace url
|
window.resizeBy 0, 350 - doc.clientHeight
|
||||||
|
location.replace url
|
||||||
return
|
return
|
||||||
|
|
||||||
link = $.el 'a',
|
link = $.el 'a',
|
||||||
href: url
|
href: url
|
||||||
textContent: 'Report to fgts'
|
textContent: 'Report to archive'
|
||||||
$.on link, 'click', (e) ->
|
$.on link, 'click', (e) ->
|
||||||
unless e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0
|
unless e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0
|
||||||
window.resizeTo 685, 320
|
window.resizeBy 0, 350 - doc.clientHeight
|
||||||
$.add d.body, [$.tn(' ['), link, $.tn(']')]
|
$.add d.body, [$.tn(' ['), link, $.tn(']')]
|
||||||
|
|
||||||
|
if types = $.id('reportTypes')
|
||||||
|
$.on types, 'change', (e) ->
|
||||||
|
$('form').action = if e.target.value in ['illegal', 'spam'] then '#redirect' else ''
|
||||||
|
|||||||
@ -5,11 +5,11 @@ ThreadStats =
|
|||||||
statsHTML = <%= html(
|
statsHTML = <%= html(
|
||||||
'<span id="post-count">?</span> / <span id="file-count">?</span>' +
|
'<span id="post-count">?</span> / <span id="file-count">?</span>' +
|
||||||
'?{Conf["IP Count in Stats"]}{ / <span id="ip-count">?</span>}' +
|
'?{Conf["IP Count in Stats"]}{ / <span id="ip-count">?</span>}' +
|
||||||
'?{Conf["Page Count in Stats"]}{ / <span id="page-count">?</span>}'
|
'?{Conf["Page Count in Stats"] && g.BOARD.ID !== "f"}{ / <span id="page-count">?</span>}'
|
||||||
) %>
|
) %>
|
||||||
statsTitle = 'Posts / Files'
|
statsTitle = 'Posts / Files'
|
||||||
statsTitle += ' / IPs' if Conf['IP Count in Stats']
|
statsTitle += ' / IPs' if Conf['IP Count in Stats']
|
||||||
statsTitle += ' / Page' if Conf['Page Count in Stats']
|
statsTitle += ' / Page' if Conf['Page Count in Stats'] and g.BOARD.ID isnt 'f'
|
||||||
|
|
||||||
if Conf['Updater and Stats in Header']
|
if Conf['Updater and Stats in Header']
|
||||||
@dialog = sc = $.el 'span',
|
@dialog = sc = $.el 'span',
|
||||||
@ -31,6 +31,8 @@ ThreadStats =
|
|||||||
@ipCountEl = $ '#ip-count', sc
|
@ipCountEl = $ '#ip-count', sc
|
||||||
@pageCountEl = $ '#page-count', sc
|
@pageCountEl = $ '#page-count', sc
|
||||||
|
|
||||||
|
$.on @pageCountEl, 'click', ThreadStats.fetchPage if @pageCountEl
|
||||||
|
|
||||||
Thread.callbacks.push
|
Thread.callbacks.push
|
||||||
name: 'Thread Stats'
|
name: 'Thread Stats'
|
||||||
cb: @node
|
cb: @node
|
||||||
@ -41,7 +43,7 @@ ThreadStats =
|
|||||||
@posts.forEach (post) ->
|
@posts.forEach (post) ->
|
||||||
postCount++
|
postCount++
|
||||||
fileCount++ if post.file
|
fileCount++ if post.file
|
||||||
ThreadStats.lastPost = post.info.date if Conf["Page Count in Stats"]
|
ThreadStats.lastPost = post.info.date if ThreadStats.pageCountEl
|
||||||
ThreadStats.thread = @
|
ThreadStats.thread = @
|
||||||
ThreadStats.fetchPage()
|
ThreadStats.fetchPage()
|
||||||
ThreadStats.update postCount, fileCount, @ipCount
|
ThreadStats.update postCount, fileCount, @ipCount
|
||||||
@ -51,23 +53,23 @@ ThreadStats =
|
|||||||
return if e.detail[404]
|
return if e.detail[404]
|
||||||
{postCount, fileCount, ipCount, newPosts} = e.detail
|
{postCount, fileCount, ipCount, newPosts} = e.detail
|
||||||
ThreadStats.update postCount, fileCount, ipCount
|
ThreadStats.update postCount, fileCount, ipCount
|
||||||
return unless Conf["Page Count in Stats"]
|
return unless ThreadStats.pageCountEl
|
||||||
if newPosts.length
|
if newPosts.length
|
||||||
ThreadStats.lastPost = g.posts[newPosts[newPosts.length - 1]].info.date
|
ThreadStats.lastPost = g.posts[newPosts[newPosts.length - 1]].info.date
|
||||||
if ThreadStats.lastPost > ThreadStats.lastPageUpdate and ThreadStats.pageCountEl?.textContent isnt '1'
|
if ThreadStats.pageCountEl?.textContent isnt '1'
|
||||||
ThreadStats.fetchPage()
|
ThreadStats.fetchPage()
|
||||||
|
|
||||||
update: (postCount, fileCount, ipCount) ->
|
update: (postCount, fileCount, ipCount) ->
|
||||||
{thread, postCountEl, fileCountEl, ipCountEl} = ThreadStats
|
{thread, postCountEl, fileCountEl, ipCountEl} = ThreadStats
|
||||||
postCountEl.textContent = postCount
|
postCountEl.textContent = postCount
|
||||||
fileCountEl.textContent = fileCount
|
fileCountEl.textContent = fileCount
|
||||||
if ipCount? and Conf["IP Count in Stats"]
|
if ipCount? and ipCountEl
|
||||||
ipCountEl.textContent = ipCount
|
ipCountEl.textContent = ipCount
|
||||||
(if thread.postLimit and !thread.isSticky then $.addClass else $.rmClass) postCountEl, 'warning'
|
(if thread.postLimit and !thread.isSticky then $.addClass else $.rmClass) postCountEl, 'warning'
|
||||||
(if thread.fileLimit and !thread.isSticky then $.addClass else $.rmClass) fileCountEl, 'warning'
|
(if thread.fileLimit and !thread.isSticky then $.addClass else $.rmClass) fileCountEl, 'warning'
|
||||||
|
|
||||||
fetchPage: ->
|
fetchPage: ->
|
||||||
return if !Conf["Page Count in Stats"]
|
return unless ThreadStats.pageCountEl
|
||||||
clearTimeout ThreadStats.timeout
|
clearTimeout ThreadStats.timeout
|
||||||
if ThreadStats.thread.isDead
|
if ThreadStats.thread.isDead
|
||||||
ThreadStats.pageCountEl.textContent = 'Dead'
|
ThreadStats.pageCountEl.textContent = 'Dead'
|
||||||
@ -75,14 +77,22 @@ ThreadStats =
|
|||||||
return
|
return
|
||||||
ThreadStats.timeout = setTimeout ThreadStats.fetchPage, 2 * $.MINUTE
|
ThreadStats.timeout = setTimeout ThreadStats.fetchPage, 2 * $.MINUTE
|
||||||
$.ajax "//a.4cdn.org/#{ThreadStats.thread.board}/threads.json", onload: ThreadStats.onThreadsLoad,
|
$.ajax "//a.4cdn.org/#{ThreadStats.thread.board}/threads.json", onload: ThreadStats.onThreadsLoad,
|
||||||
whenModified: true
|
whenModified: 'ThreadStats'
|
||||||
|
|
||||||
onThreadsLoad: ->
|
onThreadsLoad: ->
|
||||||
return unless Conf["Page Count in Stats"] and @status is 200
|
if @status is 200
|
||||||
for page in @response
|
for page in @response
|
||||||
for thread in page.threads when thread.no is ThreadStats.thread.ID
|
for thread in page.threads when thread.no is ThreadStats.thread.ID
|
||||||
ThreadStats.pageCountEl.textContent = page.page
|
ThreadStats.pageCountEl.textContent = page.page
|
||||||
(if page.page is @response.length then $.addClass else $.rmClass) ThreadStats.pageCountEl, 'warning'
|
(if page.page is @response.length then $.addClass else $.rmClass) ThreadStats.pageCountEl, 'warning'
|
||||||
# Thread data may be stale (modification date given < time of last post). If so, try again on next thread update.
|
ThreadStats.lastPageUpdate = new Date thread.last_modified * $.SECOND
|
||||||
ThreadStats.lastPageUpdate = new Date thread.last_modified * $.SECOND
|
ThreadStats.retry()
|
||||||
return
|
return
|
||||||
|
else if @status is 304
|
||||||
|
ThreadStats.retry()
|
||||||
|
|
||||||
|
retry: ->
|
||||||
|
# If thread data is stale (modification date given < time of last post), try again.
|
||||||
|
if ThreadStats.lastPost > ThreadStats.lastPageUpdate and ThreadStats.pageCountEl?.textContent isnt '1'
|
||||||
|
clearTimeout ThreadStats.timeout
|
||||||
|
ThreadStats.timeout = setTimeout ThreadStats.fetchPage, 5 * $.SECOND
|
||||||
|
|||||||
@ -241,7 +241,7 @@ ThreadUpdater =
|
|||||||
onloadend: ThreadUpdater.cb.load
|
onloadend: ThreadUpdater.cb.load
|
||||||
timeout: $.MINUTE
|
timeout: $.MINUTE
|
||||||
,
|
,
|
||||||
whenModified: true
|
whenModified: 'ThreadUpdater'
|
||||||
|
|
||||||
updateThreadStatus: (type, status) ->
|
updateThreadStatus: (type, status) ->
|
||||||
return unless hasChanged = ThreadUpdater.thread["is#{type}"] isnt status
|
return unless hasChanged = ThreadUpdater.thread["is#{type}"] isnt status
|
||||||
|
|||||||
@ -20,7 +20,7 @@ ThreadWatcher =
|
|||||||
|
|
||||||
$.on d, 'QRPostSuccessful', @cb.post
|
$.on d, 'QRPostSuccessful', @cb.post
|
||||||
$.on sc, 'click', @toggleWatcher
|
$.on sc, 'click', @toggleWatcher
|
||||||
$.on @refreshButton, 'click', @fetchAllStatus
|
$.on @refreshButton, 'click', @buttonFetchAll
|
||||||
$.on @closeButton, 'click', @toggleWatcher
|
$.on @closeButton, 'click', @toggleWatcher
|
||||||
|
|
||||||
$.on d, '4chanXInitFinished', @ready
|
$.on d, '4chanXInitFinished', @ready
|
||||||
@ -84,6 +84,9 @@ ThreadWatcher =
|
|||||||
return unless e.button is 0 and e.altKey
|
return unless e.button is 0 and e.altKey
|
||||||
ThreadWatcher.toggle @thread
|
ThreadWatcher.toggle @thread
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
$.on @nodes.thumb.parentNode, 'mousedown', (e) ->
|
||||||
|
# Prevent highlighting thumbnail in Firefox.
|
||||||
|
e.preventDefault() if e.button is 0 and e.altKey
|
||||||
|
|
||||||
ready: ->
|
ready: ->
|
||||||
$.off d, '4chanXInitFinished', ThreadWatcher.ready
|
$.off d, '4chanXInitFinished', ThreadWatcher.ready
|
||||||
@ -136,11 +139,13 @@ ThreadWatcher =
|
|||||||
boardID = g.BOARD.ID
|
boardID = g.BOARD.ID
|
||||||
db.forceSync()
|
db.forceSync()
|
||||||
for threadID, data of db.data.boards[boardID] when not data?.isDead and threadID not of g.BOARD.threads
|
for threadID, data of db.data.boards[boardID] when not data?.isDead and threadID not of g.BOARD.threads
|
||||||
if Conf['Auto Prune'] or not (data and typeof data is 'object')
|
if Conf['Auto Prune'] or not (data and typeof data is 'object') # corrupt data
|
||||||
ThreadWatcher.db.delete {boardID, threadID}
|
db.delete {boardID, threadID}
|
||||||
else
|
else
|
||||||
|
if Conf['Show Unread Count']
|
||||||
|
ThreadWatcher.fetchStatus {boardID, threadID, data}
|
||||||
data.isDead = true
|
data.isDead = true
|
||||||
ThreadWatcher.db.set {boardID, threadID, val: data}
|
db.set {boardID, threadID, val: data}
|
||||||
ThreadWatcher.refresh()
|
ThreadWatcher.refresh()
|
||||||
onThreadRefresh: (e) ->
|
onThreadRefresh: (e) ->
|
||||||
thread = g.threads[e.detail.threadID]
|
thread = g.threads[e.detail.threadID]
|
||||||
@ -148,9 +153,19 @@ ThreadWatcher =
|
|||||||
# Update dead status.
|
# Update dead status.
|
||||||
ThreadWatcher.add thread
|
ThreadWatcher.add thread
|
||||||
|
|
||||||
fetchCount:
|
requests: []
|
||||||
fetched: 0
|
fetched: 0
|
||||||
fetching: 0
|
|
||||||
|
clearRequests: ->
|
||||||
|
ThreadWatcher.requests = []
|
||||||
|
ThreadWatcher.fetched = 0
|
||||||
|
ThreadWatcher.status.textContent = ''
|
||||||
|
$.rmClass ThreadWatcher.refreshButton, 'fa-spin'
|
||||||
|
|
||||||
|
abort: ->
|
||||||
|
for req in ThreadWatcher.requests when req.readyState isnt 4 # DONE
|
||||||
|
req.abort()
|
||||||
|
ThreadWatcher.clearRequests()
|
||||||
|
|
||||||
fetchAuto: ->
|
fetchAuto: ->
|
||||||
clearTimeout ThreadWatcher.timeout
|
clearTimeout ThreadWatcher.timeout
|
||||||
@ -164,6 +179,12 @@ ThreadWatcher =
|
|||||||
db.save()
|
db.save()
|
||||||
ThreadWatcher.timeout = setTimeout ThreadWatcher.fetchAuto, interval
|
ThreadWatcher.timeout = setTimeout ThreadWatcher.fetchAuto, interval
|
||||||
|
|
||||||
|
buttonFetchAll: ->
|
||||||
|
if ThreadWatcher.requests.length
|
||||||
|
ThreadWatcher.abort()
|
||||||
|
else
|
||||||
|
ThreadWatcher.fetchAllStatus()
|
||||||
|
|
||||||
fetchAllStatus: ->
|
fetchAllStatus: ->
|
||||||
ThreadWatcher.db.forceSync()
|
ThreadWatcher.db.forceSync()
|
||||||
ThreadWatcher.unreaddb.forceSync()
|
ThreadWatcher.unreaddb.forceSync()
|
||||||
@ -176,26 +197,23 @@ ThreadWatcher =
|
|||||||
fetchStatus: (thread, force) ->
|
fetchStatus: (thread, force) ->
|
||||||
{boardID, threadID, data} = thread
|
{boardID, threadID, data} = thread
|
||||||
return if data.isDead and not force
|
return if data.isDead and not force
|
||||||
{fetchCount} = ThreadWatcher
|
if ThreadWatcher.requests.length is 0
|
||||||
if fetchCount.fetching is 0
|
|
||||||
ThreadWatcher.status.textContent = '...'
|
ThreadWatcher.status.textContent = '...'
|
||||||
$.addClass ThreadWatcher.refreshButton, 'fa-spin'
|
$.addClass ThreadWatcher.refreshButton, 'fa-spin'
|
||||||
fetchCount.fetching++
|
req = $.ajax "//a.4cdn.org/#{boardID}/thread/#{threadID}.json",
|
||||||
$.ajax "//a.4cdn.org/#{boardID}/thread/#{threadID}.json",
|
|
||||||
onloadend: ->
|
onloadend: ->
|
||||||
ThreadWatcher.parseStatus.call @, thread
|
ThreadWatcher.parseStatus.call @, thread
|
||||||
|
timeout: $.MINUTE
|
||||||
|
,
|
||||||
|
whenModified: if force then false else 'ThreadWatcher'
|
||||||
|
ThreadWatcher.requests.push req
|
||||||
|
|
||||||
parseStatus: ({boardID, threadID, data}) ->
|
parseStatus: ({boardID, threadID, data}) ->
|
||||||
{fetchCount} = ThreadWatcher
|
ThreadWatcher.fetched++
|
||||||
fetchCount.fetched++
|
if ThreadWatcher.fetched is ThreadWatcher.requests.length
|
||||||
if fetchCount.fetched is fetchCount.fetching
|
ThreadWatcher.clearRequests()
|
||||||
fetchCount.fetched = 0
|
|
||||||
fetchCount.fetching = 0
|
|
||||||
status = ''
|
|
||||||
$.rmClass ThreadWatcher.refreshButton, 'fa-spin'
|
|
||||||
else
|
else
|
||||||
status = "#{Math.round fetchCount.fetched / fetchCount.fetching * 100}%"
|
ThreadWatcher.status.textContent = "#{Math.round(ThreadWatcher.fetched / ThreadWatcher.requests.length * 100)}%"
|
||||||
ThreadWatcher.status.textContent = status
|
|
||||||
|
|
||||||
if @status is 200 and @response
|
if @status is 200 and @response
|
||||||
isDead = !!@response.posts[0].archived
|
isDead = !!@response.posts[0].archived
|
||||||
@ -214,17 +232,23 @@ ThreadWatcher =
|
|||||||
for postObj in @response.posts
|
for postObj in @response.posts
|
||||||
continue unless postObj.no > lastReadPost
|
continue unless postObj.no > lastReadPost
|
||||||
continue if QR.db?.get {boardID, threadID, postID: postObj.no}
|
continue if QR.db?.get {boardID, threadID, postID: postObj.no}
|
||||||
|
|
||||||
unread++
|
unread++
|
||||||
|
|
||||||
continue unless QR.db and postObj.com
|
continue unless QR.db and postObj.com
|
||||||
regexp = /<a [^>]*\bhref="(?:\/([^\/]+)\/thread\/(\d+))?(?:#p(\d+))?"/g
|
|
||||||
|
quotesYou = false
|
||||||
|
regexp = /<a [^>]*\bhref="(?:\/([^\/]+)\/thread\/)?(\d+)?(?:#p(\d+))?"/g
|
||||||
while match = regexp.exec postObj.com
|
while match = regexp.exec postObj.com
|
||||||
if QR.db.get {
|
if QR.db.get {
|
||||||
boardID: match[1] or boardID
|
boardID: match[1] or boardID
|
||||||
threadID: match[2] or threadID
|
threadID: match[2] or threadID
|
||||||
postID: match[3] or match[2] or threadID
|
postID: match[3] or match[2] or threadID
|
||||||
}
|
}
|
||||||
quotingYou++
|
quotesYou = true
|
||||||
continue
|
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
|
if isDead isnt data.isDead or unread isnt data.unread or quotingYou isnt data.quotingYou
|
||||||
data.isDead = isDead
|
data.isDead = isDead
|
||||||
@ -238,6 +262,8 @@ ThreadWatcher =
|
|||||||
ThreadWatcher.db.delete {boardID, threadID}
|
ThreadWatcher.db.delete {boardID, threadID}
|
||||||
else
|
else
|
||||||
data.isDead = true
|
data.isDead = true
|
||||||
|
delete data.unread
|
||||||
|
delete data.quotingYou
|
||||||
ThreadWatcher.db.set {boardID, threadID, val: data}
|
ThreadWatcher.db.set {boardID, threadID, val: data}
|
||||||
|
|
||||||
ThreadWatcher.refresh()
|
ThreadWatcher.refresh()
|
||||||
|
|||||||
@ -128,7 +128,7 @@ Unread =
|
|||||||
openNotification: (post) ->
|
openNotification: (post) ->
|
||||||
return unless Header.areNotificationsEnabled
|
return unless Header.areNotificationsEnabled
|
||||||
notif = new Notification "#{post.info.nameBlock} replied to you",
|
notif = new Notification "#{post.info.nameBlock} replied to you",
|
||||||
body: post.info[if Conf['Remove Spoilers'] or Conf['Reveal Spoilers'] then 'comment' else 'commentSpoilered']
|
body: post.info.commentDisplay
|
||||||
icon: Favicon.logo
|
icon: Favicon.logo
|
||||||
notif.onclick = ->
|
notif.onclick = ->
|
||||||
Header.scrollToIfNeeded post.nodes.root, true
|
Header.scrollToIfNeeded post.nodes.root, true
|
||||||
@ -154,6 +154,10 @@ Unread =
|
|||||||
Unread.update()
|
Unread.update()
|
||||||
|
|
||||||
read: $.debounce 100, (e) ->
|
read: $.debounce 100, (e) ->
|
||||||
|
# Update the lastReadPost when hidden posts are added to the thread.
|
||||||
|
if !Unread.posts.size and Unread.readCount isnt Unread.thread.posts.keys.length
|
||||||
|
Unread.saveLastReadPost()
|
||||||
|
|
||||||
return if d.hidden or !Unread.posts.size
|
return if d.hidden or !Unread.posts.size
|
||||||
height = doc.clientHeight
|
height = doc.clientHeight
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,38 @@
|
|||||||
Captcha.fixes =
|
Captcha.fixes =
|
||||||
selectors:
|
imageKeys: '789456123uiojklm'.split('').concat(['Comma', 'Period'])
|
||||||
image: '.rc-imageselect-target > .rc-imageselect-tile > img'
|
|
||||||
|
css: '''
|
||||||
|
.rc-imageselect-target > div:focus {
|
||||||
|
outline: 2px solid #4a90e2;
|
||||||
|
}
|
||||||
|
.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;}
|
||||||
|
'''
|
||||||
|
|
||||||
init: ->
|
init: ->
|
||||||
switch location.pathname.split('/')[3]
|
switch location.pathname.split('/')[3]
|
||||||
when 'anchor' then @initMain()
|
when 'anchor' then @initMain()
|
||||||
when 'frame' then @initPopup()
|
when 'frame' then @initPopup()
|
||||||
|
when 'fallback' then @initNoscript()
|
||||||
|
|
||||||
initMain: ->
|
initMain: ->
|
||||||
$.onExists d.body, '#recaptcha-anchor', true, (checkbox) ->
|
$.onExists d.body, '#recaptcha-anchor', true, (checkbox) ->
|
||||||
@ -17,39 +44,71 @@ Captcha.fixes =
|
|||||||
$.queueTask focus
|
$.queueTask focus
|
||||||
|
|
||||||
initPopup: ->
|
initPopup: ->
|
||||||
$.addStyle "#{@selectors.image}:focus {outline: 2px solid #4a90e2;}"
|
$.addStyle @css
|
||||||
@fixImages()
|
@fixImages()
|
||||||
new MutationObserver(=> @fixImages()).observe d.body, {childList: true, subtree: true}
|
new MutationObserver(=> @fixImages()).observe d.body, {childList: true, subtree: true}
|
||||||
$.on d, 'keydown', @keybinds.bind(@)
|
$.on d, 'keydown', @keybinds.bind(@)
|
||||||
|
|
||||||
|
initNoscript: ->
|
||||||
|
@noscript = true
|
||||||
|
@images = $$ '.fbc-payload-imageselect > input'
|
||||||
|
return unless @images.length
|
||||||
|
|
||||||
|
$.addStyle @cssNoscript
|
||||||
|
@addLabels()
|
||||||
|
$.on d, 'keydown', @keybinds.bind(@)
|
||||||
|
$.on $('.fbc-imageselect-challenge > form'), 'submit', @checkForm.bind(@)
|
||||||
|
|
||||||
fixImages: ->
|
fixImages: ->
|
||||||
return unless (@images = $$ @selectors.image).length
|
@images = $$ '.rc-imageselect-target > div'
|
||||||
focus = @images[0].tabIndex isnt 0
|
|
||||||
for img in @images
|
for img in @images
|
||||||
img.tabIndex = 0
|
img.tabIndex = 0
|
||||||
@focusImage() if focus
|
@addTooltips @images if @images.length
|
||||||
|
|
||||||
focusImage: ->
|
addLabels: ->
|
||||||
# XXX Image is not focusable at first in Firefox; to be refactored when I figure out why.
|
imageSelect = $ '.fbc-payload-imageselect'
|
||||||
img = @images[0]
|
labels = for checkbox, i in @images
|
||||||
$.asap ->
|
checkbox.id = "checkbox-#{i}"
|
||||||
return true unless doc.contains img
|
label = $.el 'label',
|
||||||
img.focus()
|
htmlFor: checkbox.id
|
||||||
d.activeElement is img
|
label.dataset.row = i // 3
|
||||||
, ->
|
label.dataset.col = i % 3
|
||||||
|
label
|
||||||
|
$.add imageSelect, labels
|
||||||
|
@addTooltips labels
|
||||||
|
|
||||||
|
addTooltips: (nodes) ->
|
||||||
|
for node, i in nodes
|
||||||
|
node.title = "#{@imageKeys[i]} or #{@imageKeys[i+9][0].toUpperCase()}#{@imageKeys[i+9][1..]}"
|
||||||
|
return
|
||||||
|
|
||||||
|
checkForm: (e) ->
|
||||||
|
n = 0
|
||||||
|
n++ for checkbox in @images when checkbox.checked
|
||||||
|
e.preventDefault() if n is 0
|
||||||
|
|
||||||
keybinds: (e) ->
|
keybinds: (e) ->
|
||||||
return unless @images and doc.contains(@images[0]) and d.activeElement
|
return unless @images and doc.contains(@images[0])
|
||||||
reload = $.id 'recaptcha-reload-button'
|
|
||||||
verify = $.id 'recaptcha-verify-button'
|
reload = $ '#recaptcha-reload-button, .fbc-button-reload'
|
||||||
|
verify = $ '#recaptcha-verify-button, .fbc-button-verify > input'
|
||||||
x = @images.indexOf d.activeElement
|
x = @images.indexOf d.activeElement
|
||||||
if x < 0
|
if x < 0
|
||||||
return unless $('.rc-controls').contains d.activeElement
|
|
||||||
x = if d.activeElement is verify then 11 else 9
|
x = if d.activeElement is verify then 11 else 9
|
||||||
return unless dx = {38: 9, 40: 3, 37: 11, 39: 1}[e.keyCode] # Up, Down, Left, Right
|
key = Keybinds.keyCode e
|
||||||
x = (x + dx) % 12
|
|
||||||
if x is 10
|
if !@noscript and key is 'Space' and x < 9
|
||||||
x = if dx is 11 then 9 else 11
|
@images[x].click()
|
||||||
(@images[x] or {9: reload, 11: verify}[x]).focus()
|
else if (i = @imageKeys.indexOf key) >= 0
|
||||||
|
@images[i % 9].click()
|
||||||
|
verify.focus()
|
||||||
|
else if dx = {'Up': 9, 'Down': 3, 'Left': 11, 'Right': 1}[key]
|
||||||
|
x = (x + dx) % 12
|
||||||
|
if x is 10
|
||||||
|
x = if dx is 11 then 9 else 11
|
||||||
|
(@images[x] or {9: reload, 11: verify}[x]).focus()
|
||||||
|
else
|
||||||
|
return
|
||||||
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|||||||
17
src/Posting/Captcha.language.coffee
Normal file
17
src/Posting/Captcha.language.coffee
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
Captcha.language =
|
||||||
|
init: ->
|
||||||
|
return unless Conf['captchaLanguage'].trim() and d.cookie.indexOf('pass_enabled=1') < 0 and !Conf['Hide Original Post Form']
|
||||||
|
$.onExists doc, '#captchaFormPart', true, (node) ->
|
||||||
|
$.onExists node, 'iframe', true, Captcha.language.fixIframe
|
||||||
|
|
||||||
|
fixPage: ->
|
||||||
|
return unless Conf['captchaLanguage'].trim() and d.cookie.indexOf('pass_enabled=1') < 0
|
||||||
|
$.onExists doc, 'iframe', true, Captcha.language.fixIframe
|
||||||
|
|
||||||
|
fixIframe: (el) ->
|
||||||
|
return unless lang = Conf['captchaLanguage'].trim()
|
||||||
|
src = if /[?&]hl=/.test el.src
|
||||||
|
el.src.replace(/([?&]hl=)[^&]*/, '$1' + encodeURIComponent lang)
|
||||||
|
else
|
||||||
|
el.src + "&hl=#{encodeURIComponent lang}"
|
||||||
|
el.src = src unless el.src is src
|
||||||
@ -1,226 +0,0 @@
|
|||||||
Captcha.noscript =
|
|
||||||
lifetime: 2 * $.MINUTE
|
|
||||||
iframeURL: '//www.google.com/recaptcha/api/fallback?k=<%= meta.recaptchaKey %>'
|
|
||||||
|
|
||||||
init: ->
|
|
||||||
return if d.cookie.indexOf('pass_enabled=1') >= 0
|
|
||||||
return unless @isEnabled = !!$.id 'g-recaptcha'
|
|
||||||
|
|
||||||
container = $.el 'div',
|
|
||||||
className: 'captcha-img'
|
|
||||||
title: 'Reload reCAPTCHA'
|
|
||||||
|
|
||||||
input = $.el 'input',
|
|
||||||
className: 'captcha-input field'
|
|
||||||
title: 'Verification'
|
|
||||||
autocomplete: 'off'
|
|
||||||
spellcheck: false
|
|
||||||
@nodes = {container, input}
|
|
||||||
|
|
||||||
$.on input, 'keydown', @keydown.bind @
|
|
||||||
$.on @nodes.container, 'click', =>
|
|
||||||
@reload()
|
|
||||||
@nodes.input.focus()
|
|
||||||
|
|
||||||
@conn = new Connection null, "#{location.protocol}//www.google.com",
|
|
||||||
challenge: @load.bind @
|
|
||||||
token: @save.bind @
|
|
||||||
error: @error.bind @
|
|
||||||
|
|
||||||
$.addClass QR.nodes.el, 'has-captcha'
|
|
||||||
$.after QR.nodes.com.parentNode, [container, input]
|
|
||||||
|
|
||||||
@captchas = []
|
|
||||||
$.get 'captchas', [], ({captchas}) ->
|
|
||||||
QR.captcha.sync captchas
|
|
||||||
QR.captcha.clear()
|
|
||||||
$.sync 'captchas', @sync
|
|
||||||
|
|
||||||
@beforeSetup()
|
|
||||||
@setup()
|
|
||||||
|
|
||||||
initFrame: ->
|
|
||||||
conn = new Connection window.parent, "#{location.protocol}//boards.4chan.org",
|
|
||||||
response: (response) ->
|
|
||||||
$.id('response').value = response
|
|
||||||
$('.fbc-challenge > form').submit()
|
|
||||||
conn.send
|
|
||||||
token: $('.fbc-verification-token > textarea')?.value
|
|
||||||
error: $('.fbc-error')?.textContent
|
|
||||||
return unless img = $ '.fbc-payload > img'
|
|
||||||
cb = ->
|
|
||||||
canvas = $.el 'canvas'
|
|
||||||
canvas.width = img.width
|
|
||||||
canvas.height = img.height
|
|
||||||
canvas.getContext('2d').drawImage(img, 0, 0)
|
|
||||||
conn.send {challenge: canvas.toDataURL()}
|
|
||||||
if img.complete
|
|
||||||
cb()
|
|
||||||
else
|
|
||||||
$.on img, 'load', cb
|
|
||||||
|
|
||||||
timers: {}
|
|
||||||
|
|
||||||
cb:
|
|
||||||
focus: -> QR.captcha.setup false, true
|
|
||||||
|
|
||||||
beforeSetup: ->
|
|
||||||
{container, input} = @nodes
|
|
||||||
container.hidden = true
|
|
||||||
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 (@needed() or force)
|
|
||||||
if !@nodes.iframe
|
|
||||||
@nodes.iframe = $.el 'iframe',
|
|
||||||
id: 'qr-captcha-iframe'
|
|
||||||
src: @iframeURL
|
|
||||||
$.add d.body, @nodes.iframe
|
|
||||||
@conn.target = @nodes.iframe.contentWindow
|
|
||||||
else if !@occupied or force
|
|
||||||
@nodes.iframe.src = @iframeURL
|
|
||||||
@occupied = true
|
|
||||||
@nodes.input.focus() if focus
|
|
||||||
|
|
||||||
afterSetup: ->
|
|
||||||
{container, input} = @nodes
|
|
||||||
container.hidden = false
|
|
||||||
input.placeholder = 'Verification'
|
|
||||||
@count()
|
|
||||||
$.off input, 'focus click', @cb.focus
|
|
||||||
|
|
||||||
if QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight
|
|
||||||
QR.nodes.el.style.top = ''
|
|
||||||
QR.nodes.el.style.bottom = '0px'
|
|
||||||
|
|
||||||
destroy: ->
|
|
||||||
return unless @isEnabled
|
|
||||||
$.rm @nodes.img if @nodes.img
|
|
||||||
delete @nodes.img
|
|
||||||
$.rm @nodes.iframe if @nodes.iframe
|
|
||||||
delete @nodes.iframe
|
|
||||||
delete @occupied
|
|
||||||
@beforeSetup()
|
|
||||||
|
|
||||||
sync: (captchas=[]) ->
|
|
||||||
QR.captcha.captchas = captchas
|
|
||||||
QR.captcha.count()
|
|
||||||
|
|
||||||
getOne: ->
|
|
||||||
@clear()
|
|
||||||
if captcha = @captchas.shift()
|
|
||||||
@count()
|
|
||||||
$.set 'captchas', @captchas
|
|
||||||
captcha.response
|
|
||||||
else if /\S/.test @nodes.input.value
|
|
||||||
(cb) =>
|
|
||||||
@submitCB = cb
|
|
||||||
@sendResponse()
|
|
||||||
else
|
|
||||||
null
|
|
||||||
|
|
||||||
sendResponse: ->
|
|
||||||
response = @nodes.input.value
|
|
||||||
if /\S/.test response
|
|
||||||
@conn.send {response}
|
|
||||||
|
|
||||||
save: (token) ->
|
|
||||||
delete @occupied
|
|
||||||
@nodes.input.value = ''
|
|
||||||
if @submitCB
|
|
||||||
@submitCB token
|
|
||||||
delete @submitCB
|
|
||||||
if @needed() then @reload() else @destroy()
|
|
||||||
else
|
|
||||||
$.forceSync 'captchas'
|
|
||||||
@captchas.push
|
|
||||||
response: token
|
|
||||||
timeout: @timeout
|
|
||||||
@count()
|
|
||||||
$.set 'captchas', @captchas
|
|
||||||
@reload()
|
|
||||||
|
|
||||||
error: (message) ->
|
|
||||||
@occupied = true
|
|
||||||
@nodes.input.value = ''
|
|
||||||
if @submitCB
|
|
||||||
@submitCB()
|
|
||||||
delete @submitCB
|
|
||||||
QR.error "Captcha Error: #{message}"
|
|
||||||
|
|
||||||
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: (src) ->
|
|
||||||
{container, input, img} = @nodes
|
|
||||||
@occupied = true
|
|
||||||
@timeout = Date.now() + @lifetime
|
|
||||||
unless img
|
|
||||||
img = @nodes.img = new Image()
|
|
||||||
$.one img, 'load', @afterSetup.bind @
|
|
||||||
$.on img, 'load', -> @hidden = false
|
|
||||||
$.add container, img
|
|
||||||
img.src = src
|
|
||||||
input.value = ''
|
|
||||||
@clear()
|
|
||||||
clearTimeout @timers.expire
|
|
||||||
@timers.expire = setTimeout @expire.bind(@), @lifetime
|
|
||||||
|
|
||||||
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 @timers.clear
|
|
||||||
if @captchas.length
|
|
||||||
@timers.clear = setTimeout @clear.bind(@), @captchas[0].timeout - Date.now()
|
|
||||||
|
|
||||||
expire: ->
|
|
||||||
return unless @nodes.iframe
|
|
||||||
if not d.hidden and (@needed() or d.activeElement is @nodes.input)
|
|
||||||
@reload()
|
|
||||||
else
|
|
||||||
@destroy()
|
|
||||||
|
|
||||||
reload: ->
|
|
||||||
@nodes.iframe.src = @iframeURL
|
|
||||||
@occupied = true
|
|
||||||
@nodes.img?.hidden = true
|
|
||||||
|
|
||||||
keydown: (e) ->
|
|
||||||
if e.keyCode is 8 and not @nodes.input.value
|
|
||||||
if @nodes.iframe then @reload() else @setup()
|
|
||||||
else if e.keyCode is 13 and e.shiftKey
|
|
||||||
@sendResponse()
|
|
||||||
else
|
|
||||||
return
|
|
||||||
e.preventDefault()
|
|
||||||
@ -5,6 +5,11 @@ Captcha.v2 =
|
|||||||
return if d.cookie.indexOf('pass_enabled=1') >= 0
|
return if d.cookie.indexOf('pass_enabled=1') >= 0
|
||||||
return unless @isEnabled = !!$.id 'g-recaptcha'
|
return unless @isEnabled = !!$.id 'g-recaptcha'
|
||||||
|
|
||||||
|
if @noscript = Conf['Force Noscript Captcha'] or not $.hasClass doc, 'js-enabled'
|
||||||
|
@conn = new Connection null, "#{location.protocol}//www.google.com",
|
||||||
|
token: (token) => @save true, token
|
||||||
|
$.addClass QR.nodes.el, 'noscript-captcha'
|
||||||
|
|
||||||
@captchas = []
|
@captchas = []
|
||||||
$.get 'captchas', [], ({captchas}) ->
|
$.get 'captchas', [], ({captchas}) ->
|
||||||
QR.captcha.sync captchas
|
QR.captcha.sync captchas
|
||||||
@ -25,10 +30,21 @@ Captcha.v2 =
|
|||||||
# XXX Greasemonkey 1.x workaround to gain access to GM_* functions.
|
# XXX Greasemonkey 1.x workaround to gain access to GM_* functions.
|
||||||
$.queueTask => @save false
|
$.queueTask => @save false
|
||||||
|
|
||||||
|
initFrame: ->
|
||||||
|
if token = $('.fbc-verification-token > textarea')?.value
|
||||||
|
conn = new Connection window.parent, "#{location.protocol}//boards.4chan.org"
|
||||||
|
conn.send {token}
|
||||||
|
|
||||||
shouldFocus: false
|
shouldFocus: false
|
||||||
timeouts: {}
|
timeouts: {}
|
||||||
postsCount: 0
|
postsCount: 0
|
||||||
|
|
||||||
|
noscriptURL: ->
|
||||||
|
url = '//www.google.com/recaptcha/api/fallback?k=<%= meta.recaptchaKey %>'
|
||||||
|
if lang = Conf['captchaLanguage'].trim()
|
||||||
|
url += "&hl=#{encodeURIComponent lang}"
|
||||||
|
url
|
||||||
|
|
||||||
needed: ->
|
needed: ->
|
||||||
captchaCount = @captchas.length
|
captchaCount = @captchas.length
|
||||||
captchaCount++ if QR.req
|
captchaCount++ if QR.req
|
||||||
@ -51,7 +67,7 @@ Captcha.v2 =
|
|||||||
|
|
||||||
setup: (focus, force) ->
|
setup: (focus, force) ->
|
||||||
return unless @isEnabled and (@needed() or force)
|
return unless @isEnabled and (@needed() or force)
|
||||||
@shouldFocus = true if focus
|
@shouldFocus = true if focus and not QR.inBubble()
|
||||||
if @timeouts.destroy
|
if @timeouts.destroy
|
||||||
clearTimeout @timeouts.destroy
|
clearTimeout @timeouts.destroy
|
||||||
delete @timeouts.destroy
|
delete @timeouts.destroy
|
||||||
@ -60,6 +76,7 @@ Captcha.v2 =
|
|||||||
if @nodes.container
|
if @nodes.container
|
||||||
if @shouldFocus and iframe = $ 'iframe', @nodes.container
|
if @shouldFocus and iframe = $ 'iframe', @nodes.container
|
||||||
iframe.focus()
|
iframe.focus()
|
||||||
|
QR.focus() # Event handler not fired in Firefox
|
||||||
delete @shouldFocus
|
delete @shouldFocus
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -69,6 +86,19 @@ Captcha.v2 =
|
|||||||
childList: true
|
childList: true
|
||||||
subtree: true
|
subtree: true
|
||||||
|
|
||||||
|
if @noscript
|
||||||
|
@setupNoscript()
|
||||||
|
else
|
||||||
|
@setupJS()
|
||||||
|
|
||||||
|
setupNoscript: ->
|
||||||
|
iframe = $.el 'iframe',
|
||||||
|
id: 'qr-captcha-iframe'
|
||||||
|
src: @noscriptURL()
|
||||||
|
$.add @nodes.container, iframe
|
||||||
|
@conn.target = iframe.contentWindow
|
||||||
|
|
||||||
|
setupJS: ->
|
||||||
$.globalEval '''
|
$.globalEval '''
|
||||||
(function() {
|
(function() {
|
||||||
function render() {
|
function render() {
|
||||||
@ -101,7 +131,7 @@ Captcha.v2 =
|
|||||||
return
|
return
|
||||||
|
|
||||||
setupIFrame: (iframe) ->
|
setupIFrame: (iframe) ->
|
||||||
@setupTime = Date.now()
|
Captcha.language.fixIframe iframe
|
||||||
$.addClass QR.nodes.el, 'captcha-open'
|
$.addClass QR.nodes.el, 'captcha-open'
|
||||||
if QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight
|
if QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight
|
||||||
QR.nodes.el.style.top = null
|
QR.nodes.el.style.top = null
|
||||||
@ -138,22 +168,23 @@ Captcha.v2 =
|
|||||||
else
|
else
|
||||||
null
|
null
|
||||||
|
|
||||||
save: (pasted) ->
|
save: (pasted, token) ->
|
||||||
$.forceSync 'captchas'
|
$.forceSync 'captchas'
|
||||||
@captchas.push
|
@captchas.push
|
||||||
response: $('textarea', @nodes.container).value
|
response: token or $('textarea', @nodes.container).value
|
||||||
timeout: (if pasted then @setupTime else Date.now()) + @lifetime
|
timeout: Date.now() + @lifetime
|
||||||
$.set 'captchas', @captchas
|
$.set 'captchas', @captchas
|
||||||
@count()
|
@count()
|
||||||
|
|
||||||
|
focus = d.activeElement?.nodeName is 'IFRAME' and /https?:\/\/www\.google\.com\/recaptcha\//.test(d.activeElement.src)
|
||||||
if @needed()
|
if @needed()
|
||||||
if QR.cooldown.auto or Conf['Post on Captcha Completion']
|
if focus
|
||||||
@shouldFocus = true
|
if QR.cooldown.auto or Conf['Post on Captcha Completion']
|
||||||
else
|
@shouldFocus = true
|
||||||
QR.nodes.status.focus()
|
else
|
||||||
|
QR.nodes.status.focus()
|
||||||
@reload()
|
@reload()
|
||||||
else
|
else
|
||||||
focus = d.activeElement?.nodeName is 'IFRAME' and d.activeElement.src?[...38] is 'https://www.google.com/recaptcha/api2/'
|
|
||||||
if pasted
|
if pasted
|
||||||
@destroy()
|
@destroy()
|
||||||
else
|
else
|
||||||
@ -172,7 +203,7 @@ Captcha.v2 =
|
|||||||
@captchas = @captchas[i..]
|
@captchas = @captchas[i..]
|
||||||
@count()
|
@count()
|
||||||
$.set 'captchas', @captchas
|
$.set 'captchas', @captchas
|
||||||
@setup true
|
@setup(d.activeElement is QR.nodes.status)
|
||||||
|
|
||||||
count: ->
|
count: ->
|
||||||
@nodes.counter.textContent = "Captchas: #{@captchas.length}"
|
@nodes.counter.textContent = "Captchas: #{@captchas.length}"
|
||||||
@ -181,9 +212,12 @@ Captcha.v2 =
|
|||||||
@timeouts.clear = setTimeout @clear.bind(@), @captchas[0].timeout - Date.now()
|
@timeouts.clear = setTimeout @clear.bind(@), @captchas[0].timeout - Date.now()
|
||||||
|
|
||||||
reload: ->
|
reload: ->
|
||||||
$.globalEval '''
|
if @noscript
|
||||||
(function() {
|
$('iframe', @nodes.container).src = @noscriptURL()
|
||||||
var container = document.querySelector("#qr .captcha-container");
|
else
|
||||||
window.grecaptcha.reset(container.dataset.widgetID);
|
$.globalEval '''
|
||||||
})();
|
(function() {
|
||||||
'''
|
var container = document.querySelector("#qr .captcha-container");
|
||||||
|
window.grecaptcha.reset(container.dataset.widgetID);
|
||||||
|
})();
|
||||||
|
'''
|
||||||
|
|||||||
@ -9,13 +9,7 @@ QR =
|
|||||||
|
|
||||||
return if g.VIEW is 'archive'
|
return if g.VIEW is 'archive'
|
||||||
|
|
||||||
$.globalEval 'document.documentElement.dataset.jsEnabled = true;'
|
version = if Conf['Use Recaptcha v1'] then 'v1' else 'v2'
|
||||||
version = if Conf['Force Noscript Captcha'] or !doc.dataset.jsEnabled
|
|
||||||
'noscript'
|
|
||||||
else if Conf['Use Recaptcha v1']
|
|
||||||
'v1'
|
|
||||||
else
|
|
||||||
'v2'
|
|
||||||
@captcha = Captcha[version]
|
@captcha = Captcha[version]
|
||||||
|
|
||||||
$.on d, '4chanXInitFinished', @initReady
|
$.on d, '4chanXInitFinished', @initReady
|
||||||
@ -42,7 +36,7 @@ QR =
|
|||||||
|
|
||||||
if Conf['Hide Original Post Form']
|
if Conf['Hide Original Post Form']
|
||||||
$.addClass doc, 'hide-original-post-form'
|
$.addClass doc, 'hide-original-post-form'
|
||||||
if !doc.dataset.jsEnabled
|
unless $.hasClass doc, 'js-enabled'
|
||||||
# Prevent unnecessary loading of fallback iframe.
|
# Prevent unnecessary loading of fallback iframe.
|
||||||
$.onExists doc, '#postForm noscript', true, $.rm
|
$.onExists doc, '#postForm noscript', true, $.rm
|
||||||
|
|
||||||
@ -136,19 +130,25 @@ QR =
|
|||||||
|
|
||||||
focus: ->
|
focus: ->
|
||||||
$.queueTask ->
|
$.queueTask ->
|
||||||
unless $$('.goog-bubble-content > iframe').some((el) -> el.getBoundingClientRect().top >= 0)
|
unless QR.inBubble()
|
||||||
focus = d.activeElement and QR.nodes.el.contains(d.activeElement)
|
QR.hasFocus = d.activeElement and QR.nodes.el.contains(d.activeElement)
|
||||||
$[if focus then 'addClass' else 'rmClass'] QR.nodes.el, 'focus'
|
QR.nodes.el.classList.toggle 'focus', QR.hasFocus
|
||||||
if chrome?
|
# XXX Stop unwanted scrolling due to captcha.
|
||||||
# XXX Stop anomalous scrolling on space/tab in/into captcha iframe.
|
if QR.captcha.isEnabled and !QR.captcha.noscript
|
||||||
if d.activeElement and QR.nodes.el.contains(d.activeElement) and d.activeElement.nodeName is 'IFRAME'
|
if QR.inCaptcha()
|
||||||
QR.scrollY = window.scrollY
|
QR.scrollY = window.scrollY
|
||||||
$.on d, 'scroll', QR.scrollLock
|
$.on d, 'scroll', QR.scrollLock
|
||||||
else
|
else
|
||||||
$.off d, 'scroll', QR.scrollLock
|
$.off d, 'scroll', QR.scrollLock
|
||||||
|
|
||||||
|
inBubble: ->
|
||||||
|
d.activeElement in $$('.goog-bubble-content > iframe')
|
||||||
|
|
||||||
|
inCaptcha: ->
|
||||||
|
(d.activeElement?.nodeName is 'IFRAME' and QR.nodes.el.contains(d.activeElement)) or (QR.hasFocus and QR.inBubble())
|
||||||
|
|
||||||
scrollLock: ->
|
scrollLock: ->
|
||||||
if d.activeElement and QR.nodes.el.contains(d.activeElement) and d.activeElement.nodeName is 'IFRAME'
|
if QR.inCaptcha()
|
||||||
window.scroll window.scrollX, QR.scrollY
|
window.scroll window.scrollX, QR.scrollY
|
||||||
else
|
else
|
||||||
$.off d, 'scroll', QR.scrollLock
|
$.off d, 'scroll', QR.scrollLock
|
||||||
@ -290,10 +290,10 @@ QR =
|
|||||||
|
|
||||||
characterCount: ->
|
characterCount: ->
|
||||||
counter = QR.nodes.charCount
|
counter = QR.nodes.charCount
|
||||||
count = QR.nodes.com.textLength
|
count = QR.nodes.com.value.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '_').length
|
||||||
counter.textContent = count
|
counter.textContent = count
|
||||||
counter.hidden = count < 1000
|
counter.hidden = count < 1000
|
||||||
(if count > 1500 then $.addClass else $.rmClass) counter, 'warning'
|
(if count > 2000 then $.addClass else $.rmClass) counter, 'warning'
|
||||||
|
|
||||||
getFile: ->
|
getFile: ->
|
||||||
$.event 'QRFile', QR.selected?.file
|
$.event 'QRFile', QR.selected?.file
|
||||||
@ -537,14 +537,13 @@ QR =
|
|||||||
event = if node.nodeName is 'SELECT' then 'change' else 'input'
|
event = if node.nodeName is 'SELECT' then 'change' else 'input'
|
||||||
$.on nodes[name], event, save
|
$.on nodes[name], event, save
|
||||||
|
|
||||||
<% if (type === 'userscript') { %>
|
# XXX Chromium treats width and height as min-width and min-height
|
||||||
if Conf['Remember QR Size']
|
if !chrome? and Conf['Remember QR Size']
|
||||||
$.get 'QR Size', '', (item) ->
|
$.get 'QR Size', '', (item) ->
|
||||||
nodes.com.style.cssText = item['QR Size']
|
nodes.com.style.cssText = item['QR Size']
|
||||||
$.on nodes.com, 'mouseup', (e) ->
|
$.on nodes.com, 'mouseup', (e) ->
|
||||||
return if e.button isnt 0
|
return if e.button isnt 0
|
||||||
$.set 'QR Size', @style.cssText
|
$.set 'QR Size', @style.cssText
|
||||||
<% } %>
|
|
||||||
|
|
||||||
QR.generatePostableThreadsList()
|
QR.generatePostableThreadsList()
|
||||||
QR.persona.init()
|
QR.persona.init()
|
||||||
|
|||||||
@ -15,7 +15,7 @@ QuotePreview =
|
|||||||
return
|
return
|
||||||
|
|
||||||
mouseover: (e) ->
|
mouseover: (e) ->
|
||||||
return if $.hasClass @, 'inlined'
|
return if $.hasClass(@, 'inlined') or !d.contains(@)
|
||||||
|
|
||||||
{boardID, threadID, postID} = Get.postDataFromLink @
|
{boardID, threadID, postID} = Get.postDataFromLink @
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user