Merge branch 'v3' into Av2
Conflicts: builds/4chan-X.js builds/4chan-X.meta.js builds/4chan-X.user.js builds/crx.crx builds/crx/manifest.json builds/crx/script.js package.json src/General/Config.coffee src/General/Main.coffee src/General/Settings.coffee src/General/css/burichan.css src/General/css/futaba.css src/General/css/photon.css src/General/css/style.css src/General/css/tomorrow.css src/General/css/yotsuba-b.css src/General/css/yotsuba.css src/General/img/favicons/Original/unreadDead.gif src/General/img/favicons/Original/unreadNSFW.gif src/General/img/favicons/Original/unreadSFW.gif src/General/img/favicons/dead.gif src/General/img/favicons/empty.gif src/General/img/favicons/ferongr/unreadDead.gif src/General/img/favicons/ferongr/unreadNSFW.gif src/General/img/favicons/ferongr/unreadSFW.gif src/General/meta/metadata.js src/Miscellaneous/AnnouncementHiding.coffee src/Miscellaneous/Keybinds.coffee src/Monitoring/ThreadStats.coffee src/Monitoring/ThreadUpdater.coffee src/Posting/QuickReply.coffee src/features/misc/header.coffee src/features/monitoring/favicon.coffee
2
.gitignore
vendored
@ -4,3 +4,5 @@ node_modules/
|
||||
tmp-crx/
|
||||
tmp-userjs/
|
||||
tmp-userscript/
|
||||
builds/4chan-X.zip
|
||||
Gruntfile.js
|
||||
193
CHANGELOG.md
@ -1,125 +1,106 @@
|
||||
- Added the option `Hide Unread Count at (0)`, disabled by default.
|
||||
### 1.1.3 - 2013-04-28
|
||||
seaweedchan:
|
||||
- Chrome doesn't get .null, so don't style it
|
||||
- Fix count when auto update is disabled and set updater text to "Update"
|
||||
- Remove /v/ and /vg/ redirection. See https://archive.foolz.us/foolz/thread/509388/ for news and how you can donate to bring /v/ and /vg/ archiving back.
|
||||
- Toggle keybind for header auto-hiding
|
||||
|
||||
### 3.1.4 - *2013-04-17*
|
||||
MayhemYDG:
|
||||
- Fix Unread Count taking into account hidden posts.
|
||||
|
||||
- Fix QR remembering the file spoiler state when it shouldn't, for real this time.
|
||||
- Fix inputs in the `Rice` tab being empty when `Custom Board Navigation` is disabled.
|
||||
### 1.1.2 - 2013-04-26
|
||||
seaweedchan:
|
||||
- Fix emoji and favicon previews not updating on change.
|
||||
- Fix issue with dragging thread watcher
|
||||
- Fix some settings not importing when coming from Mayhem's v3
|
||||
- Fix menu z-index
|
||||
|
||||
### 3.1.3 - *2013-04-16*
|
||||
MayhemYDG:
|
||||
- Fix bug where a thread would freeze on load.
|
||||
|
||||
- Fix Chrome freezing when switching from the `Filter` tab to another tab in the settings.
|
||||
zixaphir:
|
||||
- Fix preview with favicons and emoji
|
||||
- Fix NaN error on Thread Updater Interval
|
||||
- Draggable UI can no longer overlap the Header.
|
||||
-- Setting the header to Autohide also increases its z-index to overlap other UI
|
||||
|
||||
### 3.1.2 - *2013-04-16*
|
||||
|
||||
- Fix error with successful posting.
|
||||
|
||||
### 3.1.1 - *2013-04-16*
|
||||
|
||||
- Styling adjustments for the announcement toggler.
|
||||
|
||||
## 3.1.0 - *2013-04-16*
|
||||
|
||||
- **New feature**: `Announcement Hiding`, enabled by default.
|
||||
- Fix support for www.4chan.org/frames on Chrome.
|
||||
- Fix quote features not working on dead quotelinks in inlined posts.
|
||||
- Fix resurrecting dead quotelinks on HTTP.
|
||||
|
||||
### 3.0.6 - *2013-04-14*
|
||||
|
||||
- Fix regression concerning thread selection when quoting on the index.
|
||||
|
||||
### 3.0.5 - *2013-04-14*
|
||||
|
||||
- `Scroll to Last Read Post` is now optional, enabled by default.
|
||||
- The QR won't auto-hide when auto-hide is enabled and one of its input is focused. Doesn't work on Firefox.
|
||||
- Added the `Remember QR Size` setting back in, disabled by default. Only on Firefox.
|
||||
- Fix QR remembering the file spoiler state when it shouldn't.
|
||||
- Fix QR cooldown in Opera.
|
||||
|
||||
### 3.0.4 - *2013-04-11*
|
||||
|
||||
- More minor fixes.
|
||||
|
||||
### 3.0.3 - *2013-04-10*
|
||||
### 1.1.1 - 2013-04-26
|
||||
zixaphir:
|
||||
- Fix script on Opera
|
||||
|
||||
MayhemYDG:
|
||||
- Minor fixes.
|
||||
- Chrome only: Due to technical limitations, Filter lists and Custom CSS will not by synchronized across devices anymore.
|
||||
|
||||
### 3.0.2 - *2013-04-09*
|
||||
seaweedchan:
|
||||
- Allow thread watcher to load on catalog
|
||||
|
||||
- Added a setting in the Header's menu to move it at the bottom of the screen.
|
||||
- Added the `Cooldown` setting back in.
|
||||
- Fixed the Header going above posts when following quotelinks for example.
|
||||
- Fixed a bug where dead quotelinks would disappear.
|
||||
### 1.0.10:
|
||||
- Add message pertaining to rewrite
|
||||
|
||||
### 3.0.1 - *2013-04-08*
|
||||
### 1.0.9:
|
||||
ihavenoface:
|
||||
- Implement Announcement Hiding
|
||||
seaweedchan:
|
||||
- Change #options back to inheriting colors from replies
|
||||
- Fix script breaking when disabling image expansion
|
||||
|
||||
- Added the possibility to combine board-list toggle and custom text.
|
||||
- Added Reply Navigation back in, disabled by default.
|
||||
- Fixed Thread Hiding initialization error.
|
||||
### 1.0.8:
|
||||
seaweedchan:
|
||||
- Redo settings menu styling
|
||||
- Move Export/Import buttons and dialog
|
||||
- Update license and use banner.js for license
|
||||
|
||||
# 3.0.0 - *2013-04-07*
|
||||
### 1.0.7:
|
||||
qqueue:
|
||||
- Relative post dates
|
||||
MayhemYDG:
|
||||
- Exporting/importing settings
|
||||
|
||||
**Major rewrite of 4chan X.**
|
||||
### 1.0.6
|
||||
seaweedchan:
|
||||
- Update supported boards for archive redirection and custom navigation
|
||||
- Point to github.io instead of github.com for pages
|
||||
- Fix post archive link for InstallGentoo and Foolz
|
||||
- Make InstallGentoo default for /g/
|
||||
- Fix embedding issues
|
||||
|
||||
Header:
|
||||
- Easily access features and the boards list directly from the Header.
|
||||
- The board list can be customized.
|
||||
- The Header can be automatically hidden.
|
||||
### 1.0.5:
|
||||
seaweedchan:
|
||||
- Added keybind to toggle Fappe Tyme
|
||||
- Fix code tag keybind
|
||||
Zixaphir:
|
||||
- Add 'yourPost' class to own replies
|
||||
|
||||
Extension-related changes for Chrome and Opera:
|
||||
- Installing and updating is now pain-free on Chrome.
|
||||
- Settings will persist on different subdomains and protocols (HTTP/HTTPS).
|
||||
- Settings will persist in Incognito on Chrome.
|
||||
- Clearing your cookies won't erase your settings anymore.
|
||||
- Fixed Chrome's install warning saying that 4chan X would run on all web sites.
|
||||
### 1.0.4:
|
||||
seaweedchan:
|
||||
- Fix Fappe Tyme
|
||||
- Re- add label for image expanding
|
||||
- Move restore button to left side as per RiDeag
|
||||
|
||||
Egocentrism:
|
||||
- `(You)` will be added to quotes linking to your posts.
|
||||
- The Unread tab icon will indicate new unread posts quoting you with an exclamation mark.
|
||||
### 1.0.3
|
||||
seaweedchan:
|
||||
- Add ad- blocking CSS into Custom CSS examples
|
||||
Zixaphir:
|
||||
- Fix ctrl+s bringing up save dialog
|
||||
- Fix issues with soundcloud embedding
|
||||
|
||||
Quick Reply changes:
|
||||
- Opening text files will insert their content in the comment field.
|
||||
- Pasting files/images (e.g. from another website) in Chrome will open them in the QR.
|
||||
- Cooldown start time is now more accurate, which means shorter cooldown period and faster auto-posting.
|
||||
- Cooldown remaining time will adjust to your upload speed and file size for faster auto-posting.
|
||||
- Clicking the submit button while uploading will abort the upload and won't start re-uploading automatically anymore.
|
||||
- Closing the QR while uploading will abort the upload and won't close the QR anymore.
|
||||
- Creating threads outside of the index is now possible.
|
||||
- Selection-to-quote also applies to selected text inside the post, not just inside the comment.
|
||||
- Added support for thread creation in the catalog.
|
||||
- Added thumbnailing support for Opera.
|
||||
### 1.0.2:
|
||||
seaweedchan:
|
||||
- New Rice option: Emoji Position
|
||||
- New layout for Rice tab
|
||||
- No more Yotsuba / Yotsuba B in options
|
||||
|
||||
Image Expansion changes:
|
||||
- The toggle and settings are now located in the Header's shortcuts and menu.
|
||||
- Expanding spoilers along with all non-spoiler images is now optional, and disabled by default.
|
||||
- Expanding OP images won't squish replies anymore.
|
||||
### 1.0.1:
|
||||
- New option: Emoji
|
||||
- New Rice option: Sage Emoji
|
||||
seaweedchan:
|
||||
- Prettier error messages
|
||||
|
||||
Thread Updater changes:
|
||||
- The Thread Updater will now notify of sticky/closed status change and update the icons.
|
||||
- The Thread Updater will pause when offline, and resume when online.
|
||||
- Added a setting to always auto-scroll to the bottom instead of the first new post.
|
||||
|
||||
Unread posts changes:
|
||||
- Added a line to distinguish read posts from unread ones.
|
||||
- Read posts won't be marked as unread after reloading a thread.
|
||||
- The page will scroll to the last read post after reloading a thread.
|
||||
- Visible posts will not be taken into account towards the unread count.
|
||||
|
||||
Thread Stats changes:
|
||||
- Post and file count will now adjust with deleted posts.
|
||||
- The post count will now become red past the bump limit.
|
||||
- The file count will not become red anymore inside sticky threads.
|
||||
|
||||
Thread/Post Hiding changes:
|
||||
- Added Thread & Post Hiding in the Menu, with individual settings.
|
||||
- Thread & Post Hiding Buttons can now be disabled in the settings.
|
||||
- Recursive Hiding will be automatically applied when manually showing/hiding a post.
|
||||
|
||||
Other:
|
||||
- Added touch and multi-touch support for dragging windows.
|
||||
- Added [eqn] and [math] tags keybind.
|
||||
- Fix Chrome's install warning saying that 4chan X would execute on all domains.
|
||||
- Fix Quote Backlinks and Quote Highlighting not affecting inlined quotes.
|
||||
- Fix unreadable inlined posts with the Tomorrow theme.
|
||||
- Fix user ID highlighting on fetched posts.
|
||||
- More fixes and improvements.
|
||||
### 1.0.0
|
||||
- Initial release
|
||||
zixaphir:
|
||||
- Fix unread post count for filtered posts
|
||||
- Fix issues when switching from ihavenoface's fork
|
||||
- Fix backlinks not receiving filtered class
|
||||
- Fix QR position not saving on refresh
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
## Reporting bugs and suggestions
|
||||
|
||||
Reporting bugs:
|
||||
|
||||
1. Make sure both your **browser** and **4chan X** are up to date.
|
||||
2. Disable your other extensions & scripts to identify conflicts.
|
||||
3. If your issue persists, open a [new issue](https://github.com/MayhemYDG/4chan-x/issues) with the following information:
|
||||
1. Precise steps to reproduce the problem, with the expected and actual results.
|
||||
2. Console errors, if any.
|
||||
3. Browser version.
|
||||
4. Your exported settings.
|
||||
|
||||
Open your console with:
|
||||
- `Ctrl + Shift + J` on Chrome.
|
||||
- `Ctrl + Shift + K` on Firefox.
|
||||
- `Ctrl + Shift + O` on Opera.
|
||||
|
||||
Respect these guidelines:
|
||||
- Describe the issue clearly, put some effort into it. A one-liner isn't a good enough description.
|
||||
- If you want to get your suggestion implemented sooner, make it convincing.
|
||||
- If you want to criticize, make it convincing and constructive.
|
||||
- Be mature. Act like an idiot and you will be blocked without warning.
|
||||
|
||||
## Development & Contribution
|
||||
|
||||
### Get started
|
||||
|
||||
- Install [node.js](http://nodejs.org/).
|
||||
- Install [Grunt's CLI](http://gruntjs.com/) with `npm install -g grunt-cli`.
|
||||
- Clone 4chan X.
|
||||
- `cd` into it.
|
||||
- Install/Update 4chan X dependencies with `npm install`.
|
||||
|
||||
### Build
|
||||
|
||||
- Build with `grunt`.
|
||||
- Continuously build with `grunt watch`.
|
||||
|
||||
### Release
|
||||
|
||||
- Update the version with `grunt patch`, `grunt minor` or `grunt major`.
|
||||
- Release with `grunt release`.
|
||||
|
||||
Note: this is only used to release new 4chan X versions, and is **not** needed or wanted in pull requests.
|
||||
|
||||
### Contribute
|
||||
|
||||
- Edit the CoffeeScript sources.
|
||||
- If the edits affect regular users, edit the changelog.
|
||||
- Open a pull request.
|
||||
@ -17,29 +17,47 @@ module.exports = (grunt) ->
|
||||
coffee:
|
||||
options: concatOptions
|
||||
src: [
|
||||
'src/config.coffee'
|
||||
'src/globals.coffee'
|
||||
'src/lib/*.coffee'
|
||||
'src/features/*/*.coffee'
|
||||
'src/settings.coffee'
|
||||
'src/main.coffee'
|
||||
'src/General/Config.coffee'
|
||||
'src/General/Globals.coffee'
|
||||
'src/General/lib/*.coffee'
|
||||
'src/General/Header.coffee'
|
||||
'src/General/Build.coffee'
|
||||
'src/General/Get.coffee'
|
||||
'src/General/UI.coffee'
|
||||
'src/Filtering/*'
|
||||
'src/Quotelinks/*'
|
||||
'src/Linkification/*'
|
||||
'src/Posting/*'
|
||||
'src/Images/*'
|
||||
'src/Menu/*'
|
||||
'src/Monitoring/*'
|
||||
'src/Archive/*'
|
||||
'src/Theming/*'
|
||||
'src/Miscellaneous/*'
|
||||
'src/General/Settings.coffee'
|
||||
'src/General/Main.coffee'
|
||||
]
|
||||
dest: 'tmp-<%= pkg.type %>/script.coffee'
|
||||
|
||||
license:
|
||||
options: concatOptions
|
||||
files:
|
||||
'LICENSE': 'src/General/meta/banner.js'
|
||||
|
||||
crx:
|
||||
options: concatOptions
|
||||
files:
|
||||
'builds/crx/manifest.json': 'src/meta/manifest.json'
|
||||
'builds/crx/manifest.json': 'src/General/meta/manifest.json'
|
||||
'builds/crx/script.js': [
|
||||
'src/banner.js'
|
||||
'src/General/meta/banner.js'
|
||||
'tmp-<%= pkg.type %>/script.js'
|
||||
]
|
||||
|
||||
userjs:
|
||||
options: concatOptions
|
||||
src: [
|
||||
'src/meta/metadata.js'
|
||||
'src/meta/banner.js'
|
||||
'src/General/meta/metadata.js'
|
||||
'src/General/meta/banner.js'
|
||||
'tmp-<%= pkg.type %>/script.js'
|
||||
]
|
||||
dest: 'builds/<%= pkg.name %>.js'
|
||||
@ -47,16 +65,16 @@ module.exports = (grunt) ->
|
||||
userscript:
|
||||
options: concatOptions
|
||||
files:
|
||||
'builds/<%= pkg.name %>.meta.js': 'src/metadata.js'
|
||||
'builds/<%= pkg.name %>.meta.js': 'src/General/meta/metadata.js'
|
||||
'builds/<%= pkg.name %>.user.js': [
|
||||
'src/meta/metadata.js'
|
||||
'src/meta/banner.js'
|
||||
'src/General/meta/metadata.js'
|
||||
'src/General/meta/banner.js'
|
||||
'tmp-<%= pkg.type %>/script.js'
|
||||
]
|
||||
|
||||
copy:
|
||||
crx:
|
||||
src: 'src/img/*.png'
|
||||
src: 'src/General/img/*.png'
|
||||
dest: 'builds/crx/'
|
||||
expand: true
|
||||
flatten: true
|
||||
@ -80,7 +98,7 @@ module.exports = (grunt) ->
|
||||
'git checkout <%= pkg.meta.mainBranch %>',
|
||||
'git commit -am "Release <%= pkg.meta.name %> v<%= pkg.version %>."',
|
||||
'git tag -a <%= pkg.version %> -m "<%= pkg.meta.name %> v<%= pkg.version %>."',
|
||||
'git tag -af stable-v3 -m "<%= pkg.meta.name %> v<%= pkg.version %>."'
|
||||
'git tag -af stable -m "<%= pkg.meta.name %> v<%= pkg.version %>."'
|
||||
].join(' && ')
|
||||
stdout: true
|
||||
|
||||
@ -135,6 +153,7 @@ module.exports = (grunt) ->
|
||||
|
||||
grunt.registerTask 'build', [
|
||||
'concurrent:build'
|
||||
'concat:license'
|
||||
]
|
||||
|
||||
grunt.registerTask 'build-crx', [
|
||||
@ -164,38 +183,39 @@ module.exports = (grunt) ->
|
||||
|
||||
grunt.registerTask 'release', [
|
||||
'default'
|
||||
'compress:crx'
|
||||
'shell:commit'
|
||||
'shell:push'
|
||||
]
|
||||
|
||||
grunt.registerTask 'patch', [
|
||||
'bump'
|
||||
'reloadPkh'
|
||||
'reloadPkg'
|
||||
'updcl:3'
|
||||
]
|
||||
|
||||
grunt.registerTask 'minor', [
|
||||
'bump:minor'
|
||||
'reloadPkh'
|
||||
'reloadPkg'
|
||||
'updcl:2'
|
||||
]
|
||||
|
||||
grunt.registerTask 'major', [
|
||||
'bump:major'
|
||||
'reloadPkh'
|
||||
'reloadPkg'
|
||||
'updcl:1'
|
||||
]
|
||||
|
||||
grunt.registerTask 'reloadPkg', 'Reload the package', ->
|
||||
# Update the `pkg` object with the new version.
|
||||
pkg = grunt.file.readJSON('package.json')
|
||||
concatOptions.process.data = pkg
|
||||
grunt.config.data.pkg = concatOptions.process.data = pkg
|
||||
grunt.log.ok('pkg reloaded.')
|
||||
|
||||
grunt.registerTask 'updcl', 'Update the changelog', (i) ->
|
||||
# i is the number of #s for markdown.
|
||||
version = []
|
||||
version.length = +i + 1
|
||||
version = version.join('#') + ' ' + pkg.version + ' *(' + grunt.template.today('yyyy-mm-dd') + ')*'
|
||||
version = version.join('#') + ' ' + pkg.version + ' - ' + grunt.template.today('yyyy-mm-dd')
|
||||
grunt.file.write 'CHANGELOG.md', version + '\n' + grunt.file.read('CHANGELOG.md')
|
||||
grunt.log.ok 'Changelog updated for v' + pkg.version + '.'
|
||||
109
LICENSE
@ -1,22 +1,89 @@
|
||||
Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com>
|
||||
Copyright (c) 2012-2013 Nicolas Stepien <stepien.nicolas@gmail.com>
|
||||
/*
|
||||
* appchan x - Version 2.0.0 - 2013-04-28
|
||||
*
|
||||
* Licensed under the MIT license.
|
||||
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE
|
||||
*
|
||||
* Appchan X Copyright © 2013-2013 Zixaphir <zixaphirmoxphar@gmail.com>
|
||||
* http://zixaphir.github.io/appchan-x/
|
||||
* 4chan x Copyright © 2009-2011 James Campos <james.r.campos@gmail.com>
|
||||
* https://github.com/aeosynth/4chan-x
|
||||
* 4chan x Copyright © 2012-2013 Nicolas Stepien <stepien.nicolas@gmail.com>
|
||||
* https://4chan-x.just-believe.in/
|
||||
* 4chan x Copyright © 2013-2013 Jordan Bates <saudrapsmann@gmail.com>
|
||||
* http://seaweedchan.github.io/4chan-x/
|
||||
* 4chan x Copyright © 2012-2013 ihavenoface
|
||||
* http://ihavenoface.github.io/4chan-x/
|
||||
* 4chan SS Copyright © 2011-2013 Ahodesuka
|
||||
* https://github.com/ahodesuka/4chan-Style-Script/
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* Contributors:
|
||||
* aeosynth
|
||||
* mayhemydg
|
||||
* noface
|
||||
* !K.WeEabo0o
|
||||
* blaise
|
||||
* that4chanwolf
|
||||
* desuwa
|
||||
* seaweed
|
||||
* e000
|
||||
* ahodesuka
|
||||
* Shou
|
||||
* ferongr
|
||||
* xat
|
||||
* Ongpot
|
||||
* thisisanon
|
||||
* Anonymous
|
||||
* Seiba
|
||||
* herpaderpderp
|
||||
* WakiMiko
|
||||
* btmcsweeney
|
||||
* AppleBloom
|
||||
*
|
||||
* All the people who've taken the time to write bug reports.
|
||||
*
|
||||
* Thank you.
|
||||
*/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
/*
|
||||
* Contains data from external sources:
|
||||
*
|
||||
* audio/beep.wav from http://freesound.org/people/pierrecartoons1979/sounds/90112/
|
||||
* cc-by-nc-3.0
|
||||
*
|
||||
* 4chan/4chan-JS (https://github.com/4chan/4chan-JS)
|
||||
* Copyright (c) 2012-2013, 4chan LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* license: https://github.com/4chan/4chan-JS/blob/master/LICENSE
|
||||
*
|
||||
* Linkify: (http://userscripts.org/scripts/show/1352)
|
||||
* Copyright (c) 2011, Anthony Lieuallen
|
||||
* All rights reserved.
|
||||
* Originally written by Anthony Lieuallen of http://arantius.com/
|
||||
* Licensed for unlimited modification and redistribution as long as
|
||||
* this notice is kept intact.
|
||||
*
|
||||
* license: http://userscripts.org/scripts/review/1352
|
||||
*
|
||||
*/
|
||||
46
README.md
@ -1,8 +1,44 @@
|
||||
# 4chan X
|
||||
# Get 4chan X [HERE](http://seaweedchan.github.io/4chan-x/).
|
||||
|
||||
Get it [here](https://4chan-x.just-believe.in/).
|
||||
## Reporting bugs and suggestions
|
||||
|
||||
***
|
||||
1. Make sure both your **browser** and **4chan X** are up to date.
|
||||
2. Disable your other extensions & scripts to identify conflicts.
|
||||
3. If your issue persists, open a [new issue](https://github.com/seaweedchan/4chan-x/issues) with the following information:
|
||||
1. Precise steps to reproduce the problem, with the expected and actual results.
|
||||
2. Console errors, if any.
|
||||
3. Browser version.
|
||||
4. Your exported settings.
|
||||
|
||||
### [MIT License](/LICENSE)
|
||||
### [Contribute](/CONTRIBUTING.md)
|
||||
Open your console with:
|
||||
- `Ctrl + Shift + J` on Chrome.
|
||||
- `Ctrl + Shift + K` on Firefox.
|
||||
- `Ctrl + Shift + O` on Opera.
|
||||
|
||||
## Development & Contribution
|
||||
|
||||
### Get started
|
||||
|
||||
- Install [node.js](http://nodejs.org/).
|
||||
- Install [Grunt's CLI](http://gruntjs.com/) with `npm install -g grunt-cli`.
|
||||
- Clone 4chan X.
|
||||
- `cd` into it.
|
||||
- Install/Update 4chan X dependencies with `npm install`.
|
||||
|
||||
### Build
|
||||
|
||||
- Build with `grunt`.
|
||||
- Continuously build with `grunt watch`.
|
||||
|
||||
### Release
|
||||
|
||||
- Update the version with `grunt patch`, `grunt minor` or `grunt major`.
|
||||
- Release with `grunt release`.
|
||||
|
||||
Note: this is only used to release new 4chan X versions, and is **not** needed or wanted in pull requests.
|
||||
|
||||
### Contribute
|
||||
|
||||
- Edit the CoffeeScript sources.
|
||||
- If the edits affect regular users, edit the changelog.
|
||||
- Open a pull request.
|
||||
9316
builds/appchan-x.js
@ -0,0 +1,19 @@
|
||||
// ==UserScript==
|
||||
// @name appchan x
|
||||
// @version 2.0.0
|
||||
// @namespace zixaphir
|
||||
// @description The most comprehensive 4chan userscript.
|
||||
// @license MIT; https://github.com/zixaphir/appchan-x/blob/Av2/LICENSE
|
||||
// @match *://api.4chan.org/*
|
||||
// @match *://boards.4chan.org/*
|
||||
// @match *://images.4chan.org/*
|
||||
// @match *://sys.4chan.org/*
|
||||
// @grant GM_getValue
|
||||
// @grant GM_setValue
|
||||
// @grant GM_deleteValue
|
||||
// @grant GM_openInTab
|
||||
// @run-at document-start
|
||||
// @updateURL https://github.com/zixaphir/appchan-x/raw/stable/builds/4chan_X.meta.js
|
||||
// @downloadURL https://github.com/zixaphir/appchan-x/raw/stable/builds/4chan_X.user.js
|
||||
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAElBMVEX///8EZgR8ulSk0oT///8EAgQ1A88mAAAAAXRSTlMAQObYZgAAAIpJREFUeF6t0sENwjAMhWF84N4H6gAYMUBkdQMYwfuvwmstEeD4kl892P0OaaWcpga2/K0SGII1HNBXARgu7veoY3ANd+esgMHZIz85u0EABrbms3pl/bkC1Tn5ihGOfQwqHeZ/FdYdirEMgCG2ZAQWDTL0m9FvjAhcvoGNAK2gZhGYYX9+ZgFm9gaiNmNkMENY4QAAAABJRU5ErkJggg==
|
||||
// ==/UserScript==
|
||||
9363
builds/crx/script.js
1320
changelog-old
12
package.json
@ -14,16 +14,20 @@
|
||||
"*://boards.4chan.org/*",
|
||||
"*://images.4chan.org/*",
|
||||
"*://sys.4chan.org/*"
|
||||
]
|
||||
],
|
||||
"files": {
|
||||
"metajs": "4chan_X.meta.js",
|
||||
"userjs": "4chan_X.user.js"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-bump": "~0.0.2",
|
||||
"grunt-concurrent": "~0.2.0",
|
||||
"grunt-contrib-clean": "~0.4.1",
|
||||
"grunt-contrib-coffee": "~0.6.7",
|
||||
"grunt-contrib-compress": "~0.4.10",
|
||||
"grunt-contrib-concat": "~0.2.0",
|
||||
"grunt-contrib-coffee": "~0.7.0",
|
||||
"grunt-contrib-compress": "~0.5.0",
|
||||
"grunt-contrib-concat": "~0.3.0",
|
||||
"grunt-contrib-copy": "~0.4.1",
|
||||
"grunt-contrib-watch": "~0.3.1",
|
||||
"grunt-shell": "~0.2.2"
|
||||
|
||||
@ -9,7 +9,7 @@ Redirect =
|
||||
image: (boardID, filename) ->
|
||||
# Do not use g.BOARD, the image url can originate from a cross-quote.
|
||||
switch boardID
|
||||
when 'a', 'gd', 'jp', 'm', 'q', 'tg', 'vg', 'vp', 'vr', 'wsg'
|
||||
when 'a', 'gd', 'jp', 'm', 'q', 'tg', 'vp', 'vr', 'wsg'
|
||||
"//archive.foolz.us/#{boardID}/full_image/#{filename}"
|
||||
when 'u'
|
||||
"//nsfw.foolz.us/#{boardID}/full_image/#{filename}"
|
||||
@ -61,7 +61,7 @@ Redirect =
|
||||
archiver:
|
||||
'Foolz':
|
||||
base: 'https://archive.foolz.us'
|
||||
boards: ['a', 'co', 'gd', 'jp', 'm', 'q', 'sp', 'tg', 'tv', 'v', 'vg', 'vp', 'vr', 'wsg']
|
||||
boards: ['a', 'co', 'gd', 'jp', 'm', 'q', 'sp', 'tg', 'tv', 'vp', 'vr', 'wsg']
|
||||
type: 'foolfuuka'
|
||||
'NSFWFoolz':
|
||||
base: 'https://nsfw.foolz.us'
|
||||
@ -130,4 +130,4 @@ Redirect =
|
||||
"##{postID}"
|
||||
else
|
||||
"#p#{postID}"
|
||||
"#{base}/#{path}"
|
||||
"#{base}/#{path}"
|
||||
@ -9,10 +9,6 @@ Config =
|
||||
false
|
||||
'Link to external catalog instead of the internal one.'
|
||||
]
|
||||
'Custom Board Navigation': [
|
||||
false
|
||||
'Show custom links instead of the full board list.'
|
||||
]
|
||||
'QR Shortcut': [
|
||||
false,
|
||||
'Adds a small [QR] link in the header.'
|
||||
@ -61,6 +57,22 @@ Config =
|
||||
true
|
||||
'Check for updated versions of <%= meta.name %>.'
|
||||
]
|
||||
'Emoji': [
|
||||
false
|
||||
'Adds icons next to names for different emails'
|
||||
]
|
||||
'Color User IDs': [
|
||||
false
|
||||
'Assign unique colors to user IDs on boards that use them'
|
||||
]
|
||||
'Remove Spoilers': [
|
||||
false
|
||||
'Remove all spoilers in text.'
|
||||
]
|
||||
'Indicate Spoilers': [
|
||||
false
|
||||
'Indicate spoilers if Remove Spoilers is enabled.'
|
||||
]
|
||||
|
||||
'Linkification':
|
||||
'Linkify': [
|
||||
@ -183,7 +195,7 @@ Config =
|
||||
]
|
||||
'Hide Unread Count at (0)': [
|
||||
false
|
||||
'Hide the unread posts count when it reaches 0.'
|
||||
'Hide the unread posts count in the tab title when it reaches 0.'
|
||||
]
|
||||
'Unread Favicon': [
|
||||
true
|
||||
@ -259,7 +271,11 @@ Config =
|
||||
]
|
||||
'Cooldown': [
|
||||
true
|
||||
'Prevent "flood detected" errors.'
|
||||
'Indicate the remaining time before posting again.'
|
||||
]
|
||||
'Cooldown Prediction': [
|
||||
true
|
||||
'Decrease the cooldown time by taking into account upload speed. Disable it if it\'s inaccurate for you.'
|
||||
]
|
||||
|
||||
'Quote Links':
|
||||
@ -295,6 +311,10 @@ Config =
|
||||
true
|
||||
'Add \'(You)\' to quotes linking to your posts.'
|
||||
]
|
||||
'Highlight Own Posts': [
|
||||
false
|
||||
'Highlights own posts if Mark Quotes of You is enabled.'
|
||||
]
|
||||
'Mark OP Quotes': [
|
||||
true
|
||||
'Add \'(OP)\' to OP quotes.'
|
||||
@ -410,11 +430,6 @@ Config =
|
||||
true
|
||||
'Adds an icon you can hover over to show the watcher, as opposed to having the watcher always visible.'
|
||||
]
|
||||
'Updater Position': [
|
||||
'top'
|
||||
'The position of 4chan thread updater and stats'
|
||||
['top', 'bottom', 'moveable']
|
||||
]
|
||||
|
||||
Posts:
|
||||
'Alternate Post Colors': [
|
||||
@ -734,14 +749,20 @@ http://iqdb.org/?url=%TURL
|
||||
#//archive.foolz.us/%board/search/image/%MD5/;text:View same on foolz /%board/
|
||||
#//archive.installgentoo.net/%board/image/%MD5;text:View same on installgentoo /%board/
|
||||
"""
|
||||
|
||||
'sageEmoji': 'appchan'
|
||||
|
||||
'emojiPos': 'before'
|
||||
|
||||
'Custom CSS': false
|
||||
|
||||
'Boards Navigation': 'Sticky top'
|
||||
|
||||
'Header auto-hide': false
|
||||
|
||||
'Header catalog links': false
|
||||
Header:
|
||||
'Fixed Header': true
|
||||
'Header auto-hide': false
|
||||
'Bottom Header': false
|
||||
'Hide Header': false
|
||||
'Header catalog links': false
|
||||
'Bottom Board List': true
|
||||
'Custom Board Navigation': true
|
||||
|
||||
boardnav: '[ toggle-all ] [current-title]'
|
||||
|
||||
@ -784,12 +805,16 @@ box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
|
||||
'Ctrl+b'
|
||||
'Toggle the full board list.'
|
||||
]
|
||||
'Toggle header': [
|
||||
'Shift+h'
|
||||
'Toggle the auto-hide option of the header.'
|
||||
]
|
||||
'Open empty QR': [
|
||||
'l'
|
||||
'i'
|
||||
'Open QR without post number inserted.'
|
||||
]
|
||||
'Open QR': [
|
||||
'Shift+l'
|
||||
'Shift+i'
|
||||
'Open QR with post number inserted.'
|
||||
]
|
||||
'Open settings': [
|
||||
@ -816,8 +841,12 @@ box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
|
||||
'Alt+m'
|
||||
'Insert math tags.'
|
||||
]
|
||||
'Submit QR': [
|
||||
'Toggle sage': [
|
||||
'Alt+s'
|
||||
'Toggle sage in email field'
|
||||
]
|
||||
'Submit QR': [
|
||||
'Ctrl+Enter'
|
||||
'Submit post.'
|
||||
]
|
||||
# Thread related
|
||||
@ -859,6 +888,10 @@ box-shadow: inset 2px 2px 2px rgba(0,0,0,0.2);
|
||||
'Left'
|
||||
'Jump to the previous page.'
|
||||
]
|
||||
'Open catalog': [
|
||||
'Shift+c'
|
||||
'Open the catalog of the current board'
|
||||
]
|
||||
# Thread Navigation
|
||||
'Next thread': [
|
||||
'Down'
|
||||
@ -2810,5 +2810,5 @@ textarea,
|
||||
}"""
|
||||
|
||||
Icons =
|
||||
oneechan: '<%= grunt.file.read("src/img/icons/oneechan.png", {encoding: "base64"}) %>'
|
||||
"4chan SS": '<%= grunt.file.read("src/img/icons/4chanSS.png", {encoding: "base64"}) %>'
|
||||
oneechan: '<%= grunt.file.read("src/General/img/icons/oneechan.png", {encoding: "base64"}) %>'
|
||||
"4chan SS": '<%= grunt.file.read("src/General/img/icons/4chanSS.png", {encoding: "base64"}) %>'
|
||||
267
src/General/Header.coffee
Normal file
@ -0,0 +1,267 @@
|
||||
Header =
|
||||
init: ->
|
||||
@menu = new UI.Menu 'header'
|
||||
@menuButton = $.el 'span',
|
||||
className: 'menu-button'
|
||||
id: 'main-menu'
|
||||
|
||||
barFixedToggler = $.el 'label',
|
||||
innerHTML: '<input type=checkbox name="Fixed Header"> Fixed Header'
|
||||
headerToggler = $.el 'label',
|
||||
innerHTML: '<input type=checkbox name="Header auto-hide"> Auto-hide header'
|
||||
barPositionToggler = $.el 'label',
|
||||
innerHTML: '<input type=checkbox name="Bottom header"> Bottom header'
|
||||
customNavToggler = $.el 'label',
|
||||
innerHTML: '<input type=checkbox name="Custom Board Navigation"> Custom board navigation'
|
||||
footerToggler = $.el 'label',
|
||||
innerHTML: "<input type=checkbox #{unless Conf['Bottom Board List'] then 'checked' else ''}> Hide bottom board list"
|
||||
editCustomNav = $.el 'a',
|
||||
textContent: 'Edit custom board navigation'
|
||||
href: 'javascript:;'
|
||||
|
||||
@barFixedToggler = barFixedToggler.firstElementChild
|
||||
@barPositionToggler = barPositionToggler.firstElementChild
|
||||
@headerToggler = headerToggler.firstElementChild
|
||||
@footerToggler = footerToggler.firstElementChild
|
||||
@customNavToggler = customNavToggler.firstElementChild
|
||||
|
||||
$.on @menuButton, 'click', @menuToggle
|
||||
$.on @barFixedToggler, 'change', @toggleBarFixed
|
||||
$.on @barPositionToggler, 'change', @toggleBarPosition
|
||||
$.on @headerToggler, 'change', @toggleBarVisibility
|
||||
$.on @footerToggler, 'change', @toggleFooterVisibility
|
||||
$.on @customNavToggler, 'change', @toggleCustomNav
|
||||
$.on editCustomNav, 'click', @editCustomNav
|
||||
|
||||
@setBarFixed Conf['Fixed Header']
|
||||
@setBarVisibility Conf['Header auto-hide']
|
||||
|
||||
$.sync 'Fixed Header', Header.setBarFixed
|
||||
$.sync 'Bottom Header', Header.setBarPosition
|
||||
$.sync 'Header auto-hide', Header.setBarVisibility
|
||||
|
||||
$.event 'AddMenuEntry',
|
||||
type: 'header'
|
||||
el: $.el 'span',
|
||||
textContent: 'Header'
|
||||
order: 107
|
||||
subEntries: [
|
||||
{el: barFixedToggler}
|
||||
{el: headerToggler}
|
||||
{el: barPositionToggler}
|
||||
{el: footerToggler}
|
||||
{el: customNavToggler}
|
||||
{el: editCustomNav}
|
||||
]
|
||||
|
||||
$.on window, 'load hashchange', Header.hashScroll
|
||||
$.on d, 'CreateNotification', @createNotification
|
||||
|
||||
$.asap (-> d.body), =>
|
||||
return unless Main.isThisPageLegit()
|
||||
# Wait for #boardNavMobile instead of #boardNavDesktop,
|
||||
# it might be incomplete otherwise.
|
||||
$.asap (-> $.id('boardNavMobile') or d.readyState is 'complete'), Header.setBoardList
|
||||
$.prepend d.body, @bar
|
||||
$.add d.body, Header.hover
|
||||
@setBarPosition Conf['Bottom Header']
|
||||
|
||||
$.ready =>
|
||||
cs = $.id('settingsWindowLink')
|
||||
cs.textContent = 'Catalog Settings'
|
||||
@addShortcut cs if g.VIEW is 'catalog'
|
||||
|
||||
bar: $.el 'div',
|
||||
id: 'header-bar'
|
||||
|
||||
notify: $.el 'div',
|
||||
id: 'notifications'
|
||||
|
||||
shortcuts: $.el 'span',
|
||||
id: 'shortcuts'
|
||||
|
||||
hover: $.el 'div',
|
||||
id: 'hoverUI'
|
||||
|
||||
toggle: $.el 'div',
|
||||
id: 'scroll-marker'
|
||||
|
||||
setBoardList: ->
|
||||
fourchannav = $.id 'boardNavDesktop'
|
||||
if a = $ "a[href*='/#{g.BOARD}/']", fourchannav
|
||||
a.className = 'current'
|
||||
|
||||
boardList = $.el 'span',
|
||||
id: 'board-list'
|
||||
innerHTML: "<span id=custom-board-list></span><span id=full-board-list hidden>[<a href=javascript:; class='hide-board-list-button'> - </a>] #{fourchannav.innerHTML}</span>"
|
||||
fullBoardList = $ '#full-board-list', boardList
|
||||
btn = $ '.hide-board-list-button', fullBoardList
|
||||
$.on btn, 'click', Header.toggleBoardList
|
||||
|
||||
$.rm $ '#navtopright', fullBoardList
|
||||
|
||||
settings = $.id('navtopright')
|
||||
$.prepend d.body, settings
|
||||
$.add settings, Header.menuButton
|
||||
|
||||
$.add boardList, fullBoardList
|
||||
$.add Header.bar, [boardList, Header.shortcuts, Header.notify, Header.toggle]
|
||||
|
||||
Header.setCustomNav Conf['Custom Board Navigation']
|
||||
Header.generateBoardList Conf['boardnav']
|
||||
|
||||
$.sync 'Custom Board Navigation', Header.setCustomNav
|
||||
$.sync 'boardnav', Header.generateBoardList
|
||||
|
||||
generateBoardList: (text) ->
|
||||
list = $ '#custom-board-list', Header.bar
|
||||
$.rmAll list
|
||||
return unless text
|
||||
as = $$('#full-board-list a', Header.bar)
|
||||
nodes = text.match(/[\w@]+(-(all|title|replace|full|index|catalog|text:"[^"]+"))*|[^\w@]+/g).map (t) ->
|
||||
if /^[^\w@]/.test t
|
||||
return $.tn t
|
||||
if /^toggle-all/.test t
|
||||
a = $.el 'a',
|
||||
className: 'show-board-list-button'
|
||||
textContent: (t.match(/-text:"(.+)"/) || [null, '+'])[1]
|
||||
href: 'javascript:;'
|
||||
$.on a, 'click', Header.toggleBoardList
|
||||
return a
|
||||
board = if /^current/.test t
|
||||
g.BOARD.ID
|
||||
else
|
||||
t.match(/^[^-]+/)[0]
|
||||
for a in as
|
||||
if a.textContent is board
|
||||
a = a.cloneNode true
|
||||
if /-title/.test t
|
||||
a.textContent = a.title
|
||||
else if /-replace/.test t
|
||||
if $.hasClass a, 'current'
|
||||
a.textContent = a.title
|
||||
else if /-full/.test t
|
||||
a.textContent = "/#{board}/ - #{a.title}"
|
||||
else if /-(index|catalog|text)/.test t
|
||||
if m = t.match /-(index|catalog)/
|
||||
a.setAttribute 'data-only', m[1]
|
||||
a.href = "//boards.4chan.org/#{board}/"
|
||||
a.href += 'catalog' if m[1] is 'catalog'
|
||||
if m = t.match /-text:"(.+)"/
|
||||
a.textContent = m[1]
|
||||
else if board is '@'
|
||||
$.addClass a, 'navSmall'
|
||||
return a
|
||||
$.tn t
|
||||
$.add list, nodes
|
||||
|
||||
toggleBoardList: ->
|
||||
{bar} = Header
|
||||
custom = $ '#custom-board-list', bar
|
||||
full = $ '#full-board-list', bar
|
||||
showBoardList = !full.hidden
|
||||
custom.hidden = !showBoardList
|
||||
full.hidden = showBoardList
|
||||
|
||||
setBarPosition: (bottom) ->
|
||||
Header.barPositionToggler.checked = bottom
|
||||
if bottom
|
||||
$.rmClass doc, 'top'
|
||||
$.addClass doc, 'bottom'
|
||||
$.after Header.bar, Header.notify
|
||||
else
|
||||
$.rmClass doc, 'bottom'
|
||||
$.addClass doc, 'top'
|
||||
$.add Header.bar, Header.notify
|
||||
Style.padding()
|
||||
|
||||
toggleBarPosition: ->
|
||||
$.event 'CloseMenu'
|
||||
|
||||
Header.setBarPosition @checked
|
||||
|
||||
Conf['Bottom Header'] = @checked
|
||||
$.set 'Bottom Header', @checked
|
||||
|
||||
setBarFixed: (fixed) ->
|
||||
Header.barFixedToggler.checked = fixed
|
||||
if fixed
|
||||
$.addClass doc, 'fixed'
|
||||
$.addClass Header.bar, 'dialog'
|
||||
else
|
||||
$.rmClass doc, 'fixed'
|
||||
$.rmClass Header.bar, 'dialog'
|
||||
|
||||
toggleBarFixed: ->
|
||||
$.event 'CloseMenu'
|
||||
|
||||
Header.setBarFixed @checked
|
||||
|
||||
Conf['Fixed Header'] = @checked
|
||||
$.set 'Fixed Header', @checked
|
||||
|
||||
setBarVisibility: (hide) ->
|
||||
Header.headerToggler.checked = hide
|
||||
$.event 'CloseMenu'
|
||||
(if hide then $.addClass else $.rmClass) Header.bar, 'autohide'
|
||||
(if hide then $.addClass else $.rmClass) doc, 'autohide'
|
||||
|
||||
toggleBarVisibility: (e) ->
|
||||
return if e.type is 'mousedown' and e.button isnt 0 # not LMB
|
||||
hide = if @nodeName is 'INPUT'
|
||||
@checked
|
||||
else
|
||||
!$.hasClass Header.bar, 'autohide'
|
||||
Conf['Header auto-hide'] = hide
|
||||
$.set 'Header auto-hide', hide
|
||||
Header.setBarVisibility hide
|
||||
message = if hide
|
||||
'The header bar will automatically hide itself.'
|
||||
else
|
||||
'The header bar will remain visible.'
|
||||
new Notification 'info', message, 2
|
||||
|
||||
setCustomNav: (show) ->
|
||||
Header.customNavToggler.checked = show
|
||||
cust = $ '#custom-board-list', Header.bar
|
||||
full = $ '#full-board-list', Header.bar
|
||||
btn = $ '.hide-board-list-button', full
|
||||
[cust.hidden, full.hidden] = if show
|
||||
[false, true]
|
||||
else
|
||||
[true, false]
|
||||
|
||||
toggleCustomNav: ->
|
||||
$.cb.checked.call @
|
||||
Header.setCustomNav @checked
|
||||
|
||||
editCustomNav: ->
|
||||
Settings.open 'Advanced'
|
||||
settings = $.id 'fourchanx-settings'
|
||||
$('input[name=boardnav]', settings).focus()
|
||||
|
||||
hashScroll: ->
|
||||
return unless (hash = @location.hash) and post = $.id hash[1..]
|
||||
return if (Get.postFromRoot post).isHidden
|
||||
Header.scrollToPost post
|
||||
|
||||
scrollToPost: (post) ->
|
||||
{top} = post.getBoundingClientRect()
|
||||
if Conf['Fixed Header'] and not Conf['Bottom Header']
|
||||
headRect = Header.bar.getBoundingClientRect()
|
||||
top += - headRect.top - headRect.height
|
||||
(if $.engine is 'webkit' then d.body else doc).scrollTop += top
|
||||
|
||||
addShortcut: (el) ->
|
||||
shortcut = $.el 'span',
|
||||
className: 'shortcut'
|
||||
$.add shortcut, [$.tn(' ['), el, $.tn(']')]
|
||||
$.prepend Header.shortcuts, shortcut
|
||||
|
||||
menuToggle: (e) ->
|
||||
Header.menu.toggle e, @, g
|
||||
|
||||
createNotification: (e) ->
|
||||
{type, content, lifetime, cb} = e.detail
|
||||
notif = new Notification type, content, lifetime
|
||||
cb notif if cb
|
||||
@ -75,7 +75,7 @@ Main =
|
||||
for name, module of features
|
||||
# c.time "#{name} initialization"
|
||||
try
|
||||
do module.init
|
||||
module.init()
|
||||
catch err
|
||||
Main.handleErrors
|
||||
message: "\"#{name}\" initialization crashed."
|
||||
@ -99,6 +99,9 @@ Main =
|
||||
'Settings': Settings
|
||||
'Announcement Hiding': PSAHiding
|
||||
'Fourchan thingies': Fourchan
|
||||
'Emoji': Emoji
|
||||
'Color User IDs': IDColor
|
||||
'Remove Spoilers': RemoveSpoilers
|
||||
'Custom CSS': CustomCSS
|
||||
'Linkify': Linkify
|
||||
'Resurrect Quotes': Quotify
|
||||
@ -183,7 +186,11 @@ Main =
|
||||
Main.handleErrors errors if errors
|
||||
|
||||
Main.callbackNodes Thread, threads
|
||||
Main.callbackNodes Post, posts
|
||||
Main.callbackNodesDB Post, posts, ->
|
||||
$.event '4chanXInitFinished'
|
||||
Main.checkUpdate()
|
||||
|
||||
return
|
||||
|
||||
$.event '4chanXInitFinished'
|
||||
Main.checkUpdate()
|
||||
@ -206,6 +213,45 @@ Main =
|
||||
# c.profileEnd callback.name
|
||||
Main.handleErrors errors if errors
|
||||
|
||||
callbackNodesDB: (klass, nodes, cb) ->
|
||||
queue = []
|
||||
softTask = ->
|
||||
task = queue.shift()
|
||||
func = task[0]
|
||||
args = Array::slice.call task, 1
|
||||
func.apply func, args
|
||||
return unless queue.length
|
||||
if (queue.length % 7) is 0
|
||||
setTimeout softTask, 0
|
||||
else
|
||||
softTask()
|
||||
|
||||
# get the nodes' length only once
|
||||
len = nodes.length
|
||||
i = 0
|
||||
errors = null
|
||||
|
||||
func = (node, i) ->
|
||||
for callback in klass::callbacks
|
||||
try
|
||||
callback.cb.call node
|
||||
catch err
|
||||
unless errors
|
||||
errors = []
|
||||
errors.push
|
||||
message: "\"#{callback.name}\" crashed on #{klass.name} No.#{node} (/#{node.board}/)."
|
||||
error: err
|
||||
# finish
|
||||
if i is len
|
||||
Main.handleErrors errors if errors
|
||||
cb() if cb
|
||||
|
||||
while i < len
|
||||
node = nodes[i]
|
||||
queue.push [func, node, ++i]
|
||||
|
||||
softTask()
|
||||
|
||||
addCallback: (e) ->
|
||||
obj = e.detail
|
||||
unless typeof obj.callback.name is 'string'
|
||||
@ -40,7 +40,7 @@ Settings =
|
||||
Settings.addSection 'Script', Settings.main
|
||||
Settings.addSection 'Filter', Settings.filter
|
||||
Settings.addSection 'Sauce', Settings.sauce
|
||||
Settings.addSection 'Rice', Settings.rice
|
||||
Settings.addSection 'Advanced', Settings.advanced
|
||||
Settings.addSection 'Keybinds', Settings.keybinds
|
||||
|
||||
$.on d, 'AddSettingsSection', Settings.addSection
|
||||
@ -68,22 +68,7 @@ Settings =
|
||||
Settings.dialog = dialog = $.el 'div',
|
||||
id: 'appchanx-settings'
|
||||
class: 'dialog'
|
||||
innerHTML: """
|
||||
<nav>
|
||||
<div class=sections-list></div>
|
||||
<span class='imp-exp-result warning'></span>
|
||||
<div class=credits>
|
||||
<a class=export>Export</a> |
|
||||
<a class=import>Import</a> |
|
||||
<input type=file style='display: none;'>
|
||||
<a href='<%= meta.page %>' target=_blank><%= meta.name %></a> |
|
||||
<a href='<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md' target=_blank>#{g.VERSION}</a> |
|
||||
<a href='<%= meta.repo %>blob/<%= meta.mainBranch %>/CONTRIBUTING.md#reporting-bugs-and-suggestions' target=_blank>Issues</a> |
|
||||
<a href=javascript:; class=close title=Close>×</a>
|
||||
</div>
|
||||
</nav>
|
||||
<hr>
|
||||
<div class=section-container><section></section></div>"""
|
||||
innerHTML: """<%= grunt.file.read('src/General/html/Settings/Settings.html').replace(/>\s+</g, '><').trim() %>"""
|
||||
|
||||
Settings.overlay = overlay = $.el 'div',
|
||||
id: 'overlay'
|
||||
@ -306,6 +291,12 @@ Settings =
|
||||
data.Conf[key] = data.Conf[key].replace(/ctrl|alt|meta/g, (s) -> "#{s[0].toUpperCase()}#{s[1..]}").replace /(^|.+\+)[A-Z]$/g, (s) ->
|
||||
"Shift+#{s[0...-1]}#{s[-1..].toLowerCase()}"
|
||||
data.Conf.WatchedThreads = data.WatchedThreads
|
||||
else if version[0] is '3'
|
||||
data = Settings.convertSettings data,
|
||||
'Reply Hiding': 'Reply Hiding Buttons'
|
||||
'Thread Hiding': 'Thread Hiding Buttons'
|
||||
'Bottom header': 'Bottom Header'
|
||||
'Unread Tab Icon': 'Unread Favicon'
|
||||
$.set data.Conf
|
||||
|
||||
convertSettings: (data, map) ->
|
||||
@ -316,22 +307,7 @@ Settings =
|
||||
|
||||
filter: (section) ->
|
||||
section.innerHTML = """
|
||||
<select name=filter>
|
||||
<option value=guide>Guide</option>
|
||||
<option value=name>Name</option>
|
||||
<option value=uniqueID>Unique ID</option>
|
||||
<option value=tripcode>Tripcode</option>
|
||||
<option value=capcode>Capcode</option>
|
||||
<option value=email>E-mail</option>
|
||||
<option value=subject>Subject</option>
|
||||
<option value=comment>Comment</option>
|
||||
<option value=flag>Flag</option>
|
||||
<option value=filename>Filename</option>
|
||||
<option value=dimensions>Image dimensions</option>
|
||||
<option value=filesize>Filesize</option>
|
||||
<option value=MD5>Image MD5</option>
|
||||
</select>
|
||||
<div></div>
|
||||
<%= grunt.file.read('src/General/html/Settings/Filter-select.html').replace(/>\s+</g, '><').trim() %>
|
||||
"""
|
||||
select = $ 'select', section
|
||||
$.on select, 'change', Settings.selectFilter
|
||||
@ -351,135 +327,27 @@ Settings =
|
||||
$.add div, ta
|
||||
return
|
||||
div.innerHTML = """
|
||||
<div class=warning #{if Conf['Filter'] then 'hidden' else ''}><code>Filter</code> is disabled.</div>
|
||||
<p>
|
||||
Use <a href=https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions>regular expressions</a>, one per line.<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>
|
||||
MD5 filtering uses exact string matching, not regular expressions.
|
||||
</p>
|
||||
<ul>You can use these settings with each regular expression, separate them with semicolons:
|
||||
<li>
|
||||
Per boards, separate them with commas. It is global if not specified.<br>
|
||||
For example: <code>boards:a,jp;</code>.
|
||||
</li>
|
||||
<li>
|
||||
Filter OPs only along with their threads (`only`), replies only (`no`), or both (`yes`, this is default).<br>
|
||||
For example: <code>op:only;</code>, <code>op:no;</code> or <code>op:yes;</code>.
|
||||
</li>
|
||||
<li>
|
||||
Overrule the `Show Stubs` setting if specified: create a stub (`yes`) or not (`no`).<br>
|
||||
For example: <code>stub:yes;</code> or <code>stub:no;</code>.
|
||||
</li>
|
||||
<li>
|
||||
Highlight instead of hiding. You can specify a class name to use with a userstyle.<br>
|
||||
For example: <code>highlight;</code> or <code>highlight:wallpaper;</code>.
|
||||
</li>
|
||||
<li>
|
||||
Highlighted OPs will have their threads put on top of board pages by default.<br>
|
||||
For example: <code>top:yes;</code> or <code>top:no;</code>.
|
||||
</li>
|
||||
</ul>
|
||||
<%= grunt.file.read('src/General/html/Settings/Filter-guide.html').replace(/>\s+</g, '><').trim() %>
|
||||
"""
|
||||
|
||||
sauce: (section) ->
|
||||
section.innerHTML = """
|
||||
<div class=warning #{if Conf['Sauce'] then 'hidden' else ''}><code>Sauce</code> is disabled.</div>
|
||||
<div>Lines starting with a <code>#</code> will be ignored.</div>
|
||||
<div>You can specify a display text by appending <code>;text:[text]</code> to the URL.</div>
|
||||
<ul>These parameters will be replaced by their corresponding values:
|
||||
<li><code>%TURL</code>: Thumbnail URL.</li>
|
||||
<li><code>%URL</code>: Full image URL.</li>
|
||||
<li><code>%MD5</code>: MD5 hash.</li>
|
||||
<li><code>%board</code>: Current board.</li>
|
||||
</ul>
|
||||
<textarea name=sauces class=field spellcheck=false></textarea>
|
||||
<%= grunt.file.read('src/General/html/Settings/Sauce.html').replace(/>\s+</g, '><').trim() %>
|
||||
"""
|
||||
sauce = $ 'textarea', section
|
||||
$.get 'sauces', Conf['sauces'], (item) ->
|
||||
sauce.value = item['sauces']
|
||||
$.on sauce, 'change', $.cb.value
|
||||
|
||||
rice: (section) ->
|
||||
section.innerHTML = """
|
||||
<fieldset>
|
||||
<legend>Archiver</legend>
|
||||
Select an Archiver for this board:
|
||||
<select name=archiver></select>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Custom Board Navigation <span class=warning #{if Conf['Custom Board Navigation'] then 'hidden' else ''}>is disabled.</span></legend>
|
||||
<div><input name=boardnav class=field spellcheck=false></div>
|
||||
<div>In the following, <code>board</code> can translate to a board ID (<code>a</code>, <code>b</code>, etc...), the current board (<code>current</code>), or the Status/Twitter link (<code>status</code>, <code>@</code>).</div>
|
||||
<div>
|
||||
For example:<br>
|
||||
<code>[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:"Piracy"]</code><br>
|
||||
will give you<br>
|
||||
<code>[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]</code><br>
|
||||
if you are on /g/.
|
||||
</div>
|
||||
<div>Board link: <code>board</code></div>
|
||||
<div>Title link: <code>board-title</code></div>
|
||||
<div>Board link (Replace with title when on that board): <code>board-replace</code></div>
|
||||
<div>Full text link: <code>board-full</code></div>
|
||||
<div>Custom text link: <code>board-text:"VIP Board"</code></div>
|
||||
<div>Index-only link: <code>board-index</code></div>
|
||||
<div>Catalog-only link: <code>board-catalog</code></div>
|
||||
<div>Combinations are possible: <code>board-index-text:"VIP Index"</code></div>
|
||||
<div>Full board list toggle: <code>toggle-all</code></div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Time Formatting <span class=warning #{if Conf['Time Formatting'] then 'hidden' else ''}>is disabled.</span></legend>
|
||||
<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>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>Year: <code>%y</code></div>
|
||||
<div>Hour: <code>%k</code>, <code>%H</code>, <code>%l</code>, <code>%I</code>, <code>%p</code>, <code>%P</code></div>
|
||||
<div>Minute: <code>%M</code></div>
|
||||
<div>Second: <code>%S</code></div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Quote Backlinks formatting <span class=warning #{if Conf['Quote Backlinks'] then 'hidden' else ''}>is disabled.</span></legend>
|
||||
<div><input name=backlink class=field spellcheck=false>: <span class=backlink-preview></span></div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>File Info Formatting <span class=warning #{if Conf['File Info Formatting'] then 'hidden' else ''}>is disabled.</span></legend>
|
||||
<div><input name=fileInfo class=field spellcheck=false>: <span class='fileText file-info-preview'></span></div>
|
||||
<div>Link: <code>%l</code> (truncated), <code>%L</code> (untruncated), <code>%T</code> (Unix timestamp)</div>
|
||||
<div>Original file name: <code>%n</code> (truncated), <code>%N</code> (untruncated), <code>%t</code> (Unix timestamp)</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>Resolution: <code>%r</code> (Displays 'PDF' for PDF files)</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Unread Favicon <span class=warning #{if Conf['Unread Favicon'] then 'hidden' else ''}>is disabled.</span></legend>
|
||||
<select name=favicon>
|
||||
<option value=ferongr>ferongr</option>
|
||||
<option value=xat->xat-</option>
|
||||
<option value=Mayhem>Mayhem</option>
|
||||
<option value=Original>Original</option>
|
||||
</select>
|
||||
<span class=favicon-preview></span>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend><input type=checkbox name='Custom CSS' #{if Conf['Custom CSS'] then 'checked' else ''}> Custom CSS</legend>
|
||||
<button id=apply-css>Apply CSS</button>
|
||||
<textarea name=usercss class=field spellcheck=false #{if Conf['Custom CSS'] then '' else 'disabled'}></textarea>
|
||||
</fieldset>
|
||||
"""
|
||||
advanced: (section) ->
|
||||
section.innerHTML = """<%= grunt.file.read('src/General/html/Settings/Advanced.html').replace(/>\s+</g, '><').trim() %>"""
|
||||
items = {}
|
||||
inputs = {}
|
||||
for name in ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss']
|
||||
for name in ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'emojiPos', 'sageEmoji', 'usercss']
|
||||
input = $ "[name='#{name}']", section
|
||||
items[name] = Conf[name]
|
||||
inputs[name] = input
|
||||
event = if ['favicon', 'usercss'].contains name
|
||||
event = if ['favicon', 'usercss', 'sageEmoji', 'emojiPos'].contains name
|
||||
'change'
|
||||
else
|
||||
'input'
|
||||
@ -501,13 +369,14 @@ Settings =
|
||||
|
||||
$.get items, (items) ->
|
||||
for key, val of items
|
||||
continue if ['usercss', 'emojiPos', 'archiver'].contains key
|
||||
input = inputs[key]
|
||||
input.value = val
|
||||
unless 'usercss' is name
|
||||
$.on input, event, Settings[key]
|
||||
Settings[key].call input
|
||||
return
|
||||
Rice.nodes section
|
||||
$.on input, event, Settings[key]
|
||||
Settings[key].call input
|
||||
Rice.nodes sectionreturn
|
||||
|
||||
$.on $('input[name=Interval]', section), 'change', ThreadUpdater.cb.interval
|
||||
$.on $('input[name="Custom CSS"]', section), 'change', Settings.togglecss
|
||||
$.on $.id('apply-css'), 'click', Settings.usercss
|
||||
|
||||
@ -519,7 +388,7 @@ Settings =
|
||||
@nextElementSibling.textContent = funk Time, new Date()
|
||||
|
||||
backlink: ->
|
||||
@nextElementSibling.textContent = Conf['backlink'].replace /%id/, '123456789'
|
||||
@nextElementSibling.textContent = @value.replace /%id/, '123456789'
|
||||
|
||||
fileInfo: ->
|
||||
data =
|
||||
@ -538,13 +407,18 @@ Settings =
|
||||
favicon: ->
|
||||
Favicon.switch()
|
||||
Unread.update() if g.VIEW is 'thread' and Conf['Unread Favicon']
|
||||
@nextElementSibling.innerHTML = """
|
||||
$.id('favicon-preview').innerHTML = """
|
||||
<img src=#{Favicon.default}>
|
||||
<img src=#{Favicon.unreadSFW}>
|
||||
<img src=#{Favicon.unreadNSFW}>
|
||||
<img src=#{Favicon.unreadDead}>
|
||||
"""
|
||||
|
||||
sageEmoji: ->
|
||||
$.id('sageicon-preview').innerHTML = """
|
||||
<img src=data:image/png;base64,#{Emoji.sage[@value]}>
|
||||
"""
|
||||
|
||||
togglecss: ->
|
||||
if $('textarea', @parentNode.parentNode).disabled = !@checked
|
||||
CustomCSS.rmStyle()
|
||||
@ -557,12 +431,7 @@ Settings =
|
||||
|
||||
keybinds: (section) ->
|
||||
section.innerHTML = """
|
||||
<div class=warning #{if Conf['Keybinds'] then 'hidden' else ''}><code>Keybinds</code> are disabled.</div>
|
||||
<div>Allowed keys: <kbd>a-z</kbd>, <kbd>0-9</kbd>, <kbd>Ctrl</kbd>, <kbd>Shift</kbd>, <kbd>Alt</kbd>, <kbd>Meta</kbd>, <kbd>Enter</kbd>, <kbd>Esc</kbd>, <kbd>Up</kbd>, <kbd>Down</kbd>, <kbd>Right</kbd>, <kbd>Left</kbd>.</div>
|
||||
<div>Press <kbd>Backspace</kbd> to disable a keybind.</div>
|
||||
<table><tbody>
|
||||
<tr><th>Actions</th><th>Keybinds</th></tr>
|
||||
</tbody></table>
|
||||
<%= grunt.file.read('src/General/html/Settings/Keybinds.html').replace(/>\s+</g, '><').trim() %>
|
||||
"""
|
||||
tbody = $ 'tbody', section
|
||||
items = {}
|
||||
@ -1116,4 +985,4 @@ Settings =
|
||||
userThemes = item["userThemes"]
|
||||
userThemes[@id] = Themes[@id]
|
||||
$.set 'userThemes', userThemes
|
||||
$.rm @
|
||||
$.rm @
|
||||
@ -54,7 +54,7 @@ UI = do ->
|
||||
menu = @makeMenu()
|
||||
currentMenu = menu
|
||||
lastToggledButton = button
|
||||
|
||||
|
||||
@entries.sort (first, second) ->
|
||||
first.order - second.order
|
||||
|
||||
@ -226,6 +226,14 @@ UI = do ->
|
||||
screenWidth: screenWidth
|
||||
isTouching: isTouching
|
||||
}
|
||||
|
||||
[o.topBorder, o.bottomBorder] = if Conf['Header auto-hide']
|
||||
[0, 0]
|
||||
else if Conf['Bottom Header']
|
||||
[0, Header.bar.getBoundingClientRect().height]
|
||||
else
|
||||
[Header.bar.getBoundingClientRect().height, 0]
|
||||
|
||||
if isTouching
|
||||
o.identifier = e.identifier
|
||||
o.move = touchmove.bind o
|
||||
@ -256,9 +264,9 @@ UI = do ->
|
||||
left / @screenWidth * 100 + '%'
|
||||
|
||||
top = clientY - @dy
|
||||
top = if top < 10
|
||||
0
|
||||
else if @height - top < 10
|
||||
top = if top < (10 + @topBorder)
|
||||
@topBorder + 'px'
|
||||
else if @height - top < (10 + @bottomBorder)
|
||||
null
|
||||
else
|
||||
top / @screenHeight * 100 + '%'
|
||||
@ -267,8 +275,9 @@ UI = do ->
|
||||
0
|
||||
else
|
||||
null
|
||||
|
||||
bottom = if top is null
|
||||
0
|
||||
@bottomBorder + 'px'
|
||||
else
|
||||
null
|
||||
|
||||
@ -78,7 +78,7 @@ body > a[style="cursor: pointer; float: right;"]::after {
|
||||
#boardNavDesktopFoot {
|
||||
top: 16px !important;
|
||||
}
|
||||
#{if _conf['Boards Navigation'] is 'Top' or _conf['Boards Navigation'] is 'Sticky top' then '#header-bar' else if _conf['Pagination'] is 'top' or _conf['Pagination'] is 'sticky top' then '.pagelist'} {
|
||||
.fixed.top #header-bar#{if _conf['Pagination'] is 'top' or _conf['Pagination'] is 'sticky top' then ',\n.pagelist' else ''} {
|
||||
#{if _conf['4chan SS Navigation']
|
||||
"padding-#{align}: #{iconOffset}px;"
|
||||
else
|
||||
@ -78,7 +78,7 @@ div.navLinks > a:first-of-type::after {
|
||||
width: #{233 + Style.sidebarOffset.W}px !important;
|
||||
#{align}: 18px !important;
|
||||
}
|
||||
#{if _conf['Boards Navigation'] is 'Top' or _conf['Boards Navigation'] is 'Sticky top' then '#header-bar' else if _conf['Pagination'] is 'top' or _conf['Pagination'] is 'sticky top' then '.pagelist'} {
|
||||
.fixed.top #header-bar#{if _conf['Pagination'] is 'top' or _conf['Pagination'] is 'sticky top' then ',\n.pagelist' else ''} {
|
||||
#{if _conf['4chan SS Navigation']
|
||||
"padding-#{align}: #{iconOffset}px;"
|
||||
else
|
||||
@ -1,5 +1,6 @@
|
||||
/* Cleanup */
|
||||
#absbot,
|
||||
#boardNavDesktop,
|
||||
#delPassword,
|
||||
#delform > hr:last-of-type,
|
||||
#navbotright,
|
||||
@ -168,6 +169,9 @@ else "
|
||||
" else ""}
|
||||
text-align: #{_conf["Navigation Alignment"]};
|
||||
}
|
||||
.fixed #header-bar.autohide {
|
||||
z-index: 24;
|
||||
}
|
||||
.fixed #header-bar {
|
||||
position: fixed;
|
||||
}
|
||||
@ -500,31 +504,6 @@ else "
|
||||
outline: none;
|
||||
z-index: 22;
|
||||
}
|
||||
/* Updater */
|
||||
#updater {
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
padding: 0 1px 1px;
|
||||
#{if _conf["Rounded Edges"] then "border-radius: 3px;" else ""}
|
||||
}
|
||||
#updater:hover {
|
||||
z-index: 30;
|
||||
}
|
||||
#updater:not(:hover) > div:not(.move) {
|
||||
display: none;
|
||||
}
|
||||
#updater input {
|
||||
text-align: right;
|
||||
}
|
||||
#updater .field {
|
||||
width: 50px;
|
||||
}
|
||||
/* Stats */
|
||||
#thread-stats {
|
||||
position: fixed;
|
||||
#{if _conf["Rounded Edges"] then "border-radius: 3px;" else ""}
|
||||
z-index: 10;
|
||||
}
|
||||
/* Image Expansion */
|
||||
.fit-width .full-image {
|
||||
max-width: 100%;
|
||||
@ -1297,6 +1276,9 @@ input:not([type=radio]) {
|
||||
vertical-align: bottom;
|
||||
padding: 0 1px;
|
||||
}
|
||||
.selectrice {
|
||||
padding-right: 1.6em;
|
||||
}
|
||||
#qr textarea {
|
||||
min-width: 100%;
|
||||
}
|
||||
@ -1521,6 +1503,14 @@ a:only-of-type > .remove {
|
||||
#{if _conf["Single Column Mode"] then "margin: 0 auto 6px;" else "margin: 0 3px 6px;\n display: inline-block;"}
|
||||
border: 0;
|
||||
}
|
||||
#appchanx-settings .section-advanced fieldset {
|
||||
display: block;
|
||||
margin: 0 auto 6px;
|
||||
}
|
||||
.section-advanced .selectrice {
|
||||
display: inline-block;
|
||||
clear: both;
|
||||
}
|
||||
.section-container {
|
||||
overflow: auto;
|
||||
position: absolute;
|
||||
@ -1554,13 +1544,13 @@ a:only-of-type > .remove {
|
||||
}
|
||||
.section-script fieldset > div,
|
||||
.section-style fieldset > div,
|
||||
.section-rice fieldset > div {
|
||||
.section-advanced fieldset > div {
|
||||
overflow: visible;
|
||||
padding: 0 5px 0 7px;
|
||||
}
|
||||
#appchanx-settings tr:nth-of-type(2n+1),
|
||||
.section-script fieldset > div:nth-of-type(2n+1),
|
||||
.section-rice fieldset > div:nth-of-type(2n+1),
|
||||
.section-advanced fieldset > div:nth-of-type(2n+1),
|
||||
.section-style fieldset > div:nth-of-type(2n+1),
|
||||
.section-keybinds tr:nth-of-type(2n+1),
|
||||
#selectrice li:nth-of-type(2n+1) {
|
||||
@ -1820,10 +1810,6 @@ opacity: 0;
|
||||
#info .btn-wrap {
|
||||
display: inline-block;
|
||||
}
|
||||
#settings .selectrice {
|
||||
width: 100px;
|
||||
display: inline-block;
|
||||
}
|
||||
#post-preview {
|
||||
position: absolute;
|
||||
z-index: 22;
|
||||
@ -103,7 +103,6 @@ s:not(:hover) {
|
||||
#appchanx-settings,
|
||||
#qrtab,
|
||||
#{if _conf["Post Form Decorations"] then "#qr," else ""}
|
||||
#updater,
|
||||
input[type="submit"],
|
||||
input[value="Report"],
|
||||
span[style="left: 5px; position: absolute;"] a {
|
||||
@ -227,9 +226,8 @@ a[style="cursor: pointer; float: right;"] ~ div[style^="width: 100%;"] > table {
|
||||
color: #{theme["Timestamps"]} !important;
|
||||
}
|
||||
#fs_status a,
|
||||
#updater #count:not(.new)::after,
|
||||
#updater #update-status:not(.new)::after,
|
||||
#showQR,
|
||||
#updater,
|
||||
.abbr,
|
||||
.boxbar,
|
||||
.boxcontent,
|
||||
@ -280,7 +278,6 @@ textarea {
|
||||
.selectrice::after {
|
||||
border-top: .45em solid #{theme["Inputs"]};
|
||||
}
|
||||
#updater input,
|
||||
.bd {
|
||||
background: #{theme["Buttons Background"]};
|
||||
border: 1px solid #{theme["Buttons Border"]};
|
||||
@ -116,7 +116,7 @@ a.useremail[href*="SAGE"]:last-of-type::#{_conf["Sage Highlight Position"]} {
|
||||
a.useremail[href*="sage"]:last-of-type::#{_conf["Sage Highlight Position"]},
|
||||
a.useremail[href*="Sage"]:last-of-type::#{_conf["Sage Highlight Position"]},
|
||||
a.useremail[href*="SAGE"]:last-of-type::#{_conf["Sage Highlight Position"]} {
|
||||
content: url("data:image/png;base64,<%= grunt.file.read("src/img/emoji/sage.png", {encoding: "base64"}) %>");
|
||||
content: url("data:image/png;base64,<%= grunt.file.read("src/General/img/emoji/sage.png", {encoding: "base64"}) %>");
|
||||
vertical-align: top;
|
||||
margin-#{if _conf["Sage Highlight Position"] is "before" then "right" else "left"}: #{parseInt _conf['Emoji Spacing']}px;
|
||||
}\n
|
||||
37
src/General/html/Features/QuickReply.html
Normal file
@ -0,0 +1,37 @@
|
||||
<div id=qrtab class=move>
|
||||
<input type=checkbox id=autohide title=Auto-hide> Post Form
|
||||
<a href=javascript:; class=close title=Close>×</a>
|
||||
</div>
|
||||
<form>
|
||||
<div class=persona>
|
||||
<input id=dump-button type=button title='Dump list' value=+ tabindex=0>
|
||||
<input name=name data-name=name title=Name placeholder=Name class=field size=1 tabindex=10>
|
||||
<input name=email data-name=email title=E-mail placeholder=E-mail class=field size=1 tabindex=20>
|
||||
<input name=sub data-name=sub title=Subject placeholder=Subject class=field size=1 tabindex=30>
|
||||
</div>
|
||||
<div class=textarea>
|
||||
<textarea data-name=com title=Comment placeholder=Comment class=field tabindex=40></textarea>
|
||||
<span id=char-count></span>
|
||||
</div>
|
||||
<div id=dump-list-container>
|
||||
<div id=dump-list></div>
|
||||
<a id=add-post href=javascript:; title="Add a post" tabindex=50>+</a>
|
||||
</div>
|
||||
<div id=file-n-submit>
|
||||
<span id=qr-filename-container class=field tabindex=60>
|
||||
<span id=qr-no-file>No selected file</span>
|
||||
<span id=qr-filename></span>
|
||||
<a id=qr-filerm href=javascript:; title='Remove file' tabindex=80>×</a>
|
||||
</span>
|
||||
<input type=submit tabindex=70>
|
||||
</div>
|
||||
<input type=file multiple>
|
||||
<div id=qr-thread-select>
|
||||
<select title='Create a new thread / Reply'>
|
||||
<option value=new>New thread</option>
|
||||
</select>
|
||||
</div>
|
||||
<label id=qr-spoiler-label>
|
||||
<input type=checkbox id=qr-file-spoiler title='Spoiler image' tabindex=90>Spoiler?
|
||||
</label>
|
||||
</form>
|
||||
101
src/General/html/Settings/Advanced.html
Normal file
@ -0,0 +1,101 @@
|
||||
<fieldset>
|
||||
<legend>Archiver</legend>
|
||||
<div>
|
||||
Select an Archiver for this board:
|
||||
<select name=archiver></select>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Custom Board Navigation <span class=warning #{if Conf['Custom Board Navigation'] then 'hidden' else ''}>is disabled.</span></legend>
|
||||
<div><input name=boardnav class=field spellcheck=false></div>
|
||||
<div>In the following, <code>board</code> can translate to a board ID (<code>a</code>, <code>b</code>, etc...), the current board (<code>current</code>), or the Status/Twitter link (<code>status</code>, <code>@</code>).</div>
|
||||
<div>
|
||||
For example:<br>
|
||||
<code>[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:"Piracy"]</code><br>
|
||||
will give you<br>
|
||||
<code>[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]</code><br>
|
||||
if you are on /g/.
|
||||
</div>
|
||||
<div>Board link: <code>board</code></div>
|
||||
<div>Title link: <code>board-title</code></div>
|
||||
<div>Board link (Replace with title when on that board): <code>board-replace</code></div>
|
||||
<div>Full text link: <code>board-full</code></div>
|
||||
<div>Custom text link: <code>board-text:"VIP Board"</code></div>
|
||||
<div>Index-only link: <code>board-index</code></div>
|
||||
<div>Catalog-only link: <code>board-catalog</code></div>
|
||||
<div>Combinations are possible: <code>board-index-text:"VIP Index"</code></div>
|
||||
<div>Full board list toggle: <code>toggle-all</code></div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Time Formatting <span class=warning #{if Conf['Time Formatting'] then 'hidden' else ''}>is disabled.</span></legend>
|
||||
<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>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>Year: <code>%y</code></div>
|
||||
<div>Hour: <code>%k</code>, <code>%H</code>, <code>%l</code>, <code>%I</code>, <code>%p</code>, <code>%P</code></div>
|
||||
<div>Minute: <code>%M</code></div>
|
||||
<div>Second: <code>%S</code></div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Quote Backlinks formatting <span class=warning #{if Conf['Quote Backlinks'] then 'hidden' else ''}>is disabled.</span></legend>
|
||||
<div><input name=backlink class=field spellcheck=false>: <span class=backlink-preview></span></div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>File Info Formatting <span class=warning #{if Conf['File Info Formatting'] then 'hidden' else ''}>is disabled.</span></legend>
|
||||
<div><input name=fileInfo class=field spellcheck=false>: <span class='fileText file-info-preview'></span></div>
|
||||
<div>Link: <code>%l</code> (truncated), <code>%L</code> (untruncated), <code>%T</code> (Unix timestamp)</div>
|
||||
<div>Original file name: <code>%n</code> (truncated), <code>%N</code> (untruncated), <code>%t</code> (Unix timestamp)</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>Resolution: <code>%r</code> (Displays 'PDF' for PDF files)</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Unread Favicon <span class=warning #{if Conf['Unread Favicon'] then 'hidden' else ''}>is disabled.</span></legend>
|
||||
<div>
|
||||
<select name=favicon>
|
||||
<option value=ferongr>ferongr</option>
|
||||
<option value=xat->xat-</option>
|
||||
<option value=Mayhem>Mayhem</option>
|
||||
<option value=4chanJS>4chanJS</option>
|
||||
<option value=Original>Original</option>
|
||||
</select>
|
||||
<span id=favicon-preview></span>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Emoji <span class=warning #{if Conf['Emoji'] then 'hidden' else ''}>is disabled.</span></legend>
|
||||
<div>
|
||||
Sage Icon: <select name=sageEmoji>
|
||||
<option value="4chan SS">4chan SS</option>
|
||||
<option value="appchan">appchan</option>
|
||||
</select>
|
||||
<span id=sageicon-preview></span>
|
||||
</div>
|
||||
<div>
|
||||
Position: <select name=emojiPos>
|
||||
<option value="before">Before</option>
|
||||
<option value="after">After</option>
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Thread Updater <span class=warning #{if Conf['Thread Updater'] then 'hidden' else ''}>is disabled.</span></legend>
|
||||
<div>
|
||||
Interval: <input type=number name=Interval class=field min=1 value=#{Conf['Interval']}>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend><input type=checkbox name='Custom CSS' #{if Conf['Custom CSS'] then 'checked' else ''}> Custom CSS</legend>
|
||||
<div>
|
||||
<button id=apply-css>Apply CSS</button>
|
||||
<textarea name=usercss class=field spellcheck=false #{if Conf['Custom CSS'] then '' else 'disabled'}></textarea>
|
||||
</div>
|
||||
</fieldset>
|
||||
29
src/General/html/Settings/Filter-guide.html
Normal file
@ -0,0 +1,29 @@
|
||||
<div class=warning #{if Conf['Filter'] then 'hidden' else ''}><code>Filter</code> is disabled.</div>
|
||||
<p>
|
||||
Use <a href=https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions>regular expressions</a>, one per line.<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>
|
||||
MD5 filtering uses exact string matching, not regular expressions.
|
||||
</p>
|
||||
<ul>You can use these settings with each regular expression, separate them with semicolons:
|
||||
<li>
|
||||
Per boards, separate them with commas. It is global if not specified.<br>
|
||||
For example: <code>boards:a,jp;</code>.
|
||||
</li>
|
||||
<li>
|
||||
Filter OPs only along with their threads (`only`), replies only (`no`), or both (`yes`, this is default).<br>
|
||||
For example: <code>op:only;</code>, <code>op:no;</code> or <code>op:yes;</code>.
|
||||
</li>
|
||||
<li>
|
||||
Overrule the `Show Stubs` setting if specified: create a stub (`yes`) or not (`no`).<br>
|
||||
For example: <code>stub:yes;</code> or <code>stub:no;</code>.
|
||||
</li>
|
||||
<li>
|
||||
Highlight instead of hiding. You can specify a class name to use with a userstyle.<br>
|
||||
For example: <code>highlight;</code> or <code>highlight:wallpaper;</code>.
|
||||
</li>
|
||||
<li>
|
||||
Highlighted OPs will have their threads put on top of board pages by default.<br>
|
||||
For example: <code>top:yes;</code> or <code>top:no;</code>.
|
||||
</li>
|
||||
</ul>
|
||||
16
src/General/html/Settings/Filter-select.html
Normal file
@ -0,0 +1,16 @@
|
||||
<select name=filter>
|
||||
<option value=guide>Guide</option>
|
||||
<option value=name>Name</option>
|
||||
<option value=uniqueID>Unique ID</option>
|
||||
<option value=tripcode>Tripcode</option>
|
||||
<option value=capcode>Capcode</option>
|
||||
<option value=email>E-mail</option>
|
||||
<option value=subject>Subject</option>
|
||||
<option value=comment>Comment</option>
|
||||
<option value=flag>Flag</option>
|
||||
<option value=filename>Filename</option>
|
||||
<option value=dimensions>Image dimensions</option>
|
||||
<option value=filesize>Filesize</option>
|
||||
<option value=MD5>Image MD5</option>
|
||||
</select>
|
||||
<div></div>
|
||||
6
src/General/html/Settings/Keybinds.html
Normal file
@ -0,0 +1,6 @@
|
||||
<div class=warning #{if Conf['Keybinds'] then 'hidden' else ''}><code>Keybinds</code> are disabled.</div>
|
||||
<div>Allowed keys: <kbd>a-z</kbd>, <kbd>0-9</kbd>, <kbd>Ctrl</kbd>, <kbd>Shift</kbd>, <kbd>Alt</kbd>, <kbd>Meta</kbd>, <kbd>Enter</kbd>, <kbd>Esc</kbd>, <kbd>Up</kbd>, <kbd>Down</kbd>, <kbd>Right</kbd>, <kbd>Left</kbd>.</div>
|
||||
<div>Press <kbd>Backspace</kbd> to disable a keybind.</div>
|
||||
<table><tbody>
|
||||
<tr><th>Actions</th><th>Keybinds</th></tr>
|
||||
</tbody></table>
|
||||
10
src/General/html/Settings/Sauce.html
Normal file
@ -0,0 +1,10 @@
|
||||
<div class=warning #{if Conf['Sauce'] then 'hidden' else ''}><code>Sauce</code> is disabled.</div>
|
||||
<div>Lines starting with a <code>#</code> will be ignored.</div>
|
||||
<div>You can specify a display text by appending <code>;text:[text]</code> to the URL.</div>
|
||||
<ul>These parameters will be replaced by their corresponding values:
|
||||
<li><code>%TURL</code>: Thumbnail URL.</li>
|
||||
<li><code>%URL</code>: Full image URL.</li>
|
||||
<li><code>%MD5</code>: MD5 hash.</li>
|
||||
<li><code>%board</code>: Current board.</li>
|
||||
</ul>
|
||||
<textarea name=sauces class=field spellcheck=false></textarea>
|
||||
15
src/General/html/Settings/Settings.html
Normal file
@ -0,0 +1,15 @@
|
||||
<nav>
|
||||
<div class=sections-list></div>
|
||||
<span class='imp-exp-result warning'></span>
|
||||
<div class=credits>
|
||||
<a class=export>Export</a> |
|
||||
<a class=import>Import</a> |
|
||||
<input type=file style='display: none;'>
|
||||
<a href='<%= meta.page %>' target=_blank><%= meta.name %></a> |
|
||||
<a href='<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md' target=_blank>#{g.VERSION}</a> |
|
||||
<a href='<%= meta.repo %>blob/<%= meta.mainBranch %>/CONTRIBUTING.md#reporting-bugs-and-suggestions' target=_blank>Issues</a> |
|
||||
<a href=javascript:; class=close title=Close>×</a>
|
||||
</div>
|
||||
</nav>
|
||||
<hr>
|
||||
<div class=section-container><section></section></div>
|
||||
BIN
src/General/img/emoji/SS-sage.png
Normal file
|
After Width: | Height: | Size: 576 B |
|
Before Width: | Height: | Size: 659 B After Width: | Height: | Size: 659 B |
|
Before Width: | Height: | Size: 912 B After Width: | Height: | Size: 912 B |
|
Before Width: | Height: | Size: 567 B After Width: | Height: | Size: 567 B |
|
Before Width: | Height: | Size: 987 B After Width: | Height: | Size: 987 B |
|
Before Width: | Height: | Size: 858 B After Width: | Height: | Size: 858 B |
BIN
src/General/img/emoji/crunchbang.png
Normal file
|
After Width: | Height: | Size: 297 B |
|
Before Width: | Height: | Size: 559 B After Width: | Height: | Size: 559 B |
|
Before Width: | Height: | Size: 631 B After Width: | Height: | Size: 631 B |
|
Before Width: | Height: | Size: 1021 B After Width: | Height: | Size: 1021 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 882 B After Width: | Height: | Size: 882 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 231 B After Width: | Height: | Size: 231 B |
|
Before Width: | Height: | Size: 1006 B After Width: | Height: | Size: 1006 B |
|
Before Width: | Height: | Size: 1012 B After Width: | Height: | Size: 1012 B |
|
Before Width: | Height: | Size: 1002 B After Width: | Height: | Size: 1002 B |
|
Before Width: | Height: | Size: 820 B After Width: | Height: | Size: 820 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 668 B After Width: | Height: | Size: 668 B |
|
Before Width: | Height: | Size: 884 B After Width: | Height: | Size: 884 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 797 B After Width: | Height: | Size: 797 B |
|
Before Width: | Height: | Size: 830 B After Width: | Height: | Size: 830 B |
BIN
src/General/img/emoji/sage.png
Normal file
|
After Width: | Height: | Size: 659 B |
|
Before Width: | Height: | Size: 934 B After Width: | Height: | Size: 934 B |
|
Before Width: | Height: | Size: 339 B After Width: | Height: | Size: 339 B |
|
Before Width: | Height: | Size: 912 B After Width: | Height: | Size: 912 B |
|
Before Width: | Height: | Size: 850 B After Width: | Height: | Size: 850 B |
|
Before Width: | Height: | Size: 820 B After Width: | Height: | Size: 820 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 625 B After Width: | Height: | Size: 625 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
src/General/img/favicons/4chanJS/unreadDead.png
Normal file
|
After Width: | Height: | Size: 162 B |
BIN
src/General/img/favicons/4chanJS/unreadDeadY.png
Normal file
|
After Width: | Height: | Size: 161 B |
BIN
src/General/img/favicons/4chanJS/unreadNSFW.png
Normal file
|
After Width: | Height: | Size: 168 B |
BIN
src/General/img/favicons/4chanJS/unreadNSFWY.png
Normal file
|
After Width: | Height: | Size: 167 B |
BIN
src/General/img/favicons/4chanJS/unreadSFW.png
Normal file
|
After Width: | Height: | Size: 162 B |
BIN
src/General/img/favicons/4chanJS/unreadSFWY.png
Normal file
|
After Width: | Height: | Size: 161 B |
|
Before Width: | Height: | Size: 346 B After Width: | Height: | Size: 346 B |
|
Before Width: | Height: | Size: 456 B After Width: | Height: | Size: 456 B |
|
Before Width: | Height: | Size: 323 B After Width: | Height: | Size: 323 B |
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 450 B |
|
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 321 B |
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 450 B |
BIN
src/General/img/favicons/Original/unreadDead.gif
Normal file
|
After Width: | Height: | Size: 110 B |
|
Before Width: | Height: | Size: 170 B After Width: | Height: | Size: 170 B |
|
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 232 B |
BIN
src/General/img/favicons/Original/unreadNSFW.gif
Normal file
|
After Width: | Height: | Size: 110 B |
|
Before Width: | Height: | Size: 170 B After Width: | Height: | Size: 170 B |
|
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 232 B |
BIN
src/General/img/favicons/Original/unreadSFW.gif
Normal file
|
After Width: | Height: | Size: 110 B |
|
Before Width: | Height: | Size: 170 B After Width: | Height: | Size: 170 B |