Use Grunt for building from now on.
This commit is contained in:
parent
9037ad32da
commit
b3d7f30f3a
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
tmp/
|
||||
21
4chan_x.meta.js
Normal file
21
4chan_x.meta.js
Normal file
@ -0,0 +1,21 @@
|
||||
// ==UserScript==
|
||||
// @name 4chan X Alpha
|
||||
// @version 3.0.0
|
||||
// @description Adds various features.
|
||||
// @copyright 2009-2011 James Campos <james.r.campos@gmail.com>
|
||||
// @copyright 2012 Nicolas Stepien <stepien.nicolas@gmail.com>
|
||||
// @license MIT; http://en.wikipedia.org/wiki/Mit_license
|
||||
// @match *://boards.4chan.org/*
|
||||
// @match *://images.4chan.org/*
|
||||
// @match *://sys.4chan.org/*
|
||||
// @match *://api.4chan.org/*
|
||||
// @match *://*.foolz.us/api/*
|
||||
// @grant GM_getValue
|
||||
// @grant GM_setValue
|
||||
// @grant GM_deleteValue
|
||||
// @grant GM_openInTab
|
||||
// @run-at document-start
|
||||
// @updateURL https://github.com/MayhemYDG/4chan-x/raw/stable/4chan_x.meta.js
|
||||
// @downloadURL https://github.com/MayhemYDG/4chan-x/raw/stable/4chan_x.user.js
|
||||
// @icon data:image/gif;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAIALAAAAAAQABAAAAIxlI+pq+D9DAgUoFkPDlbs7lGiI2bSVnKglnJMOL6omczxVZK3dH/41AG6Lh7i6qUoAAA7
|
||||
// ==/UserScript==
|
||||
101
4chan_x.user.js
101
4chan_x.user.js
@ -1,5 +1,5 @@
|
||||
// ==UserScript==
|
||||
// @name 4chan X alpha
|
||||
// @name 4chan X Alpha
|
||||
// @version 3.0.0
|
||||
// @description Adds various features.
|
||||
// @copyright 2009-2011 James Campos <james.r.campos@gmail.com>
|
||||
@ -15,69 +15,33 @@
|
||||
// @grant GM_deleteValue
|
||||
// @grant GM_openInTab
|
||||
// @run-at document-start
|
||||
// @updateURL https://github.com/MayhemYDG/4chan-x/raw/stable/4chan_x.user.js
|
||||
// @updateURL https://github.com/MayhemYDG/4chan-x/raw/stable/4chan_x.meta.js
|
||||
// @downloadURL https://github.com/MayhemYDG/4chan-x/raw/stable/4chan_x.user.js
|
||||
// @icon data:image/gif;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAIALAAAAAAQABAAAAIxlI+pq+D9DAgUoFkPDlbs7lGiI2bSVnKglnJMOL6omczxVZK3dH/41AG6Lh7i6qUoAAA7
|
||||
// @icon https://github.com/MayhemYDG/4chan-x/raw/stable/img/icon.gif
|
||||
// ==/UserScript==
|
||||
|
||||
/* LICENSE
|
||||
/* 4chan X Alpha - Version 3.0.0 - 2012-09-24
|
||||
* http://mayhemydg.github.com/4chan-x/
|
||||
*
|
||||
* Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com>
|
||||
* Copyright (c) 2012 Nicolas Stepien <stepien.nicolas@gmail.com>
|
||||
* http://mayhemydg.github.com/4chan-x/
|
||||
* 4chan X 3.0.0
|
||||
* Licensed under the MIT license.
|
||||
* https://github.com/MayhemYDG/4chan-x/blob/master/LICENSE
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* HACKING
|
||||
*
|
||||
* 4chan X is written in CoffeeScript[1], and developed on GitHub[2].
|
||||
*
|
||||
* [1]: http://coffeescript.org/
|
||||
* [2]: https://github.com/MayhemYDG/4chan-x
|
||||
*
|
||||
* CONTRIBUTORS
|
||||
*
|
||||
* noface - unique ID fixes
|
||||
* desuwa - Firefox filename upload fix
|
||||
* seaweed - bottom padding for image hover
|
||||
* Contributors:
|
||||
* https://github.com/MayhemYDG/4chan-x/graphs/contributors
|
||||
* ferongr, xat-, Ongpot, thisisanon and Anonymous - cooldown sanity check
|
||||
* e000 - cooldown sanity check
|
||||
* ahodesuka - scroll back when unexpanding images, file info formatting
|
||||
* Shou- - pentadactyl fixes
|
||||
* ferongr - new favicons
|
||||
* xat- - new favicons
|
||||
* Zixaphir - fix qr textarea - captcha-image gap
|
||||
* Ongpot - sfw favicon
|
||||
* thisisanon - nsfw + 404 favicons
|
||||
* Anonymous - empty favicon
|
||||
* Seiba - chrome quick reply focusing
|
||||
* herpaderpderp - recaptcha fixes
|
||||
* WakiMiko - recaptcha tab order http://userscripts.org/scripts/show/82657
|
||||
* btmcsweeney - allow users to specify text for sauce links
|
||||
*
|
||||
* All the people who've taken the time to write bug reports.
|
||||
*
|
||||
* Thank you.
|
||||
*/
|
||||
|
||||
// Generated by CoffeeScript 1.3.3
|
||||
(function() {
|
||||
var $, $$, AutoGIF, Board, Build, Clone, Conf, Config, FileInfo, Get, ImageHover, Main, Post, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, Quotify, Redirect, RevealSpoilers, Sauce, Thread, ThreadUpdater, Time, UI, d, g,
|
||||
__hasProp = {}.hasOwnProperty,
|
||||
@ -86,7 +50,7 @@
|
||||
Config = {
|
||||
main: {
|
||||
Enhancing: {
|
||||
'Disable 4chan\'s extension': [true, 'Avoid conflicts between 4chan X and 4chan\'s inline extension.'],
|
||||
'Disable 4chan\'s extension': [true, 'Avoid conflicts between 4chan X Alpha and 4chan\'s inline extension.'],
|
||||
'404 Redirect': [true, 'Redirect dead threads and images.'],
|
||||
'Keybinds': [true, 'Bind actions to keyboard shortcuts.'],
|
||||
'Time Formatting': [true, 'Localize and format timestamps arbitrarily.'],
|
||||
@ -95,7 +59,7 @@
|
||||
'Thread Expansion': [true, 'Can expand threads to view all replies.'],
|
||||
'Index Navigation': [false, 'Navigate to previous / next thread.'],
|
||||
'Reply Navigation': [false, 'Navigate to top / bottom of thread.'],
|
||||
'Check for Updates': [true, 'Check for updated versions of 4chan X.']
|
||||
'Check for Updates': [true, 'Check for updated versions of 4chan X Alpha.']
|
||||
},
|
||||
Filtering: {
|
||||
'Anonymize': [false, 'Turn everyone Anonymous.'],
|
||||
@ -206,22 +170,6 @@
|
||||
imageFit: 'fit width'
|
||||
};
|
||||
|
||||
if (!/^(boards|images|sys)\.4chan\.org$/.test(location.hostname)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Conf = {};
|
||||
|
||||
d = document;
|
||||
|
||||
g = {
|
||||
VERSION: '3.0.0',
|
||||
NAMESPACE: '4chan_X.',
|
||||
boards: {},
|
||||
threads: {},
|
||||
posts: {}
|
||||
};
|
||||
|
||||
UI = (function() {
|
||||
var dialog, drag, dragend, dragstart, hover, hoverend, hoverstart, touchend, touchmove;
|
||||
dialog = function(id, position, html) {
|
||||
@ -386,13 +334,6 @@
|
||||
};
|
||||
})();
|
||||
|
||||
/*
|
||||
loosely follows the jquery api:
|
||||
http://api.jquery.com/
|
||||
not chainable
|
||||
*/
|
||||
|
||||
|
||||
$ = function(selector, root) {
|
||||
if (root == null) {
|
||||
root = d.body;
|
||||
@ -715,6 +656,22 @@
|
||||
}
|
||||
});
|
||||
|
||||
if (!/^(boards|images|sys)\.4chan\.org$/.test(location.hostname)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Conf = {};
|
||||
|
||||
d = document;
|
||||
|
||||
g = {
|
||||
VERSION: '3.0.0',
|
||||
NAMESPACE: "4chan_X_Alpha.",
|
||||
boards: {},
|
||||
threads: {},
|
||||
posts: {}
|
||||
};
|
||||
|
||||
Board = (function() {
|
||||
|
||||
Board.prototype.toString = function() {
|
||||
|
||||
45
CONTRIBUTING.md
Normal file
45
CONTRIBUTING.md
Normal file
@ -0,0 +1,45 @@
|
||||
## Reporting bugs
|
||||
|
||||
1. Make sure your **browser** & **4chan X** are up to date.
|
||||
2. Disable your other extensions & scripts.
|
||||
3. If your issue persists:
|
||||
1. Report precise steps to reproduce the problem.
|
||||
2. Report console errors, if any.
|
||||
3. Report browser and browser version.
|
||||
|
||||
Open your console with:
|
||||
- `Ctrl + Shift + J` on Chrome & Firefox
|
||||
- `Ctrl + Shift + O` on Opera.
|
||||
|
||||
***
|
||||
|
||||
## Development & Contribution
|
||||
|
||||
### Get started
|
||||
|
||||
- Clone 4chan X.
|
||||
- `cd` into it.
|
||||
- Install [node.js](http://nodejs.org/).
|
||||
- Install [CoffeeScript](http://coffeescript.org/) with `npm install -g coffee-script`.
|
||||
- Install [Grunt](http://gruntjs.com/) with `npm install -g grunt`.
|
||||
- Install [grunt-exec](https://npmjs.org/package/grunt-exec) with `npm install grunt-exec`.
|
||||
- Install [grunt-image-embed](https://npmjs.org/package/grunt-image-embed) with `npm install grunt-image-embed`.
|
||||
|
||||
### Build
|
||||
|
||||
- Build with `grunt`.
|
||||
- For development (continuous builds), run `grunt watch`.
|
||||
|
||||
### Release
|
||||
|
||||
- To upgrade, edit the version in `grunt.js` and run `grunt upgrade`.
|
||||
|
||||
Note: this is only used to release new 4chan X versions, and is not needed or wanted in pull requests.
|
||||
|
||||
### Contribute
|
||||
|
||||
- Edit the CoffeeScript source.
|
||||
- Build the JavaScript.
|
||||
- If the edits affect regular users, edit the changelog.
|
||||
- Fork the repo.
|
||||
- Send a pull request.
|
||||
120
Cakefile
120
Cakefile
@ -1,120 +0,0 @@
|
||||
{log} = console
|
||||
{exec} = require 'child_process'
|
||||
fs = require 'fs'
|
||||
|
||||
VERSION = '3.0.0'
|
||||
CAKEFILE = 'Cakefile'
|
||||
INFILE = 'script.coffee'
|
||||
OUTFILE = '4chan_x.user.js'
|
||||
CHANGELOG = 'changelog'
|
||||
LATEST = 'latest.js'
|
||||
HEADER = """
|
||||
// ==UserScript==
|
||||
// @name 4chan X alpha
|
||||
// @version #{VERSION}
|
||||
// @description Adds various features.
|
||||
// @copyright 2009-2011 James Campos <james.r.campos@gmail.com>
|
||||
// @copyright 2012 Nicolas Stepien <stepien.nicolas@gmail.com>
|
||||
// @license MIT; http://en.wikipedia.org/wiki/Mit_license
|
||||
// @match *://boards.4chan.org/*
|
||||
// @match *://images.4chan.org/*
|
||||
// @match *://sys.4chan.org/*
|
||||
// @match *://api.4chan.org/*
|
||||
// @match *://*.foolz.us/api/*
|
||||
// @grant GM_getValue
|
||||
// @grant GM_setValue
|
||||
// @grant GM_deleteValue
|
||||
// @grant GM_openInTab
|
||||
// @run-at document-start
|
||||
// @updateURL https://github.com/MayhemYDG/4chan-x/raw/stable/4chan_x.user.js
|
||||
// @downloadURL https://github.com/MayhemYDG/4chan-x/raw/stable/4chan_x.user.js
|
||||
// @icon data:image/gif;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAIALAAAAAAQABAAAAIxlI+pq+D9DAgUoFkPDlbs7lGiI2bSVnKglnJMOL6omczxVZK3dH/41AG6Lh7i6qUoAAA7
|
||||
// ==/UserScript==
|
||||
|
||||
/* LICENSE
|
||||
*
|
||||
* Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com>
|
||||
* Copyright (c) 2012 Nicolas Stepien <stepien.nicolas@gmail.com>
|
||||
* http://mayhemydg.github.com/4chan-x/
|
||||
* 4chan X #{VERSION}
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* HACKING
|
||||
*
|
||||
* 4chan X is written in CoffeeScript[1], and developed on GitHub[2].
|
||||
*
|
||||
* [1]: http://coffeescript.org/
|
||||
* [2]: https://github.com/MayhemYDG/4chan-x
|
||||
*
|
||||
* CONTRIBUTORS
|
||||
*
|
||||
* noface - unique ID fixes
|
||||
* desuwa - Firefox filename upload fix
|
||||
* seaweed - bottom padding for image hover
|
||||
* e000 - cooldown sanity check
|
||||
* ahodesuka - scroll back when unexpanding images, file info formatting
|
||||
* Shou- - pentadactyl fixes
|
||||
* ferongr - new favicons
|
||||
* xat- - new favicons
|
||||
* Zixaphir - fix qr textarea - captcha-image gap
|
||||
* Ongpot - sfw favicon
|
||||
* thisisanon - nsfw + 404 favicons
|
||||
* Anonymous - empty favicon
|
||||
* Seiba - chrome quick reply focusing
|
||||
* herpaderpderp - recaptcha fixes
|
||||
* WakiMiko - recaptcha tab order http://userscripts.org/scripts/show/82657
|
||||
* btmcsweeney - allow users to specify text for sauce links
|
||||
*
|
||||
* All the people who've taken the time to write bug reports.
|
||||
*
|
||||
* Thank you.
|
||||
*/
|
||||
|
||||
|
||||
"""
|
||||
|
||||
option '-v', '--version [version]', 'Upgrade version.'
|
||||
|
||||
task 'upgrade', (options) ->
|
||||
{version} = options
|
||||
unless version
|
||||
console.warn 'Version argument not specified. Exiting.'
|
||||
return
|
||||
regexp = RegExp VERSION, 'g'
|
||||
for file in [CAKEFILE, INFILE, OUTFILE, LATEST]
|
||||
data = fs.readFileSync(file, 'utf8').replace regexp, version
|
||||
fs.writeFileSync file, data
|
||||
# data = fs.readFileSync CHANGELOG, 'utf8'
|
||||
# fs.writeFileSync CHANGELOG, data.replace 'master', "master\n\n#{version}"
|
||||
exec "git commit -am 'Release #{version}.' && git tag -a #{version} -m '#{version}' && git tag -af stable -m '#{version}'"
|
||||
|
||||
task 'build', ->
|
||||
exec 'coffee --print script.coffee', (err, stdout, stderr) ->
|
||||
throw err if err
|
||||
fs.writeFile OUTFILE, HEADER + stdout, (err) ->
|
||||
throw err if err
|
||||
|
||||
task 'dev', ->
|
||||
invoke 'build'
|
||||
fs.watchFile INFILE, interval: 250, (curr, prev) ->
|
||||
if curr.mtime > prev.mtime
|
||||
invoke 'build'
|
||||
22
LICENSE
Normal file
22
LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com>
|
||||
Copyright (c) 2012 Nicolas Stepien <stepien.nicolas@gmail.com>
|
||||
|
||||
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.
|
||||
9
README.md
Normal file
9
README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Get 4chan X [HERE](http://mayhemydg.github.com/4chan-x/).
|
||||
|
||||
***
|
||||
|
||||
## [MIT License](/4chan-x/blob/master/LICENSE)
|
||||
|
||||
***
|
||||
|
||||
## [Contribute](/4chan-x/blob/master/LICENSE)
|
||||
158
css/style.css
Normal file
158
css/style.css
Normal file
@ -0,0 +1,158 @@
|
||||
/* general */
|
||||
.dialog.reply {
|
||||
display: block;
|
||||
border: 1px solid rgba(0, 0, 0, .25);
|
||||
padding: 0;
|
||||
}
|
||||
.move {
|
||||
cursor: move;
|
||||
}
|
||||
label {
|
||||
cursor: pointer;
|
||||
}
|
||||
a[href="javascript:;"] {
|
||||
text-decoration: none;
|
||||
}
|
||||
.warning {
|
||||
color: red;
|
||||
}
|
||||
|
||||
/* 4chan style fixes */
|
||||
.opContainer, .op {
|
||||
display: block !important;
|
||||
}
|
||||
.post {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* fixed, z-index */
|
||||
#qp, #ihover,
|
||||
#updater, #stats,
|
||||
#boardNavDesktop.reply,
|
||||
#qr, #watcher {
|
||||
position: fixed;
|
||||
}
|
||||
#qp, #ihover {
|
||||
z-index: 100;
|
||||
}
|
||||
#updater, #stats {
|
||||
z-index: 90;
|
||||
}
|
||||
#boardNavDesktop.reply:hover {
|
||||
z-index: 80;
|
||||
}
|
||||
#qr {
|
||||
z-index: 50;
|
||||
}
|
||||
#watcher {
|
||||
z-index: 30;
|
||||
}
|
||||
#boardNavDesktop.reply {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
|
||||
/* header */
|
||||
body.fourchan_x {
|
||||
margin-top: 2.5em;
|
||||
}
|
||||
#boardNavDesktop.reply {
|
||||
border-width: 0 0 1px;
|
||||
padding: 4px;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
transition: opacity .1s ease-in-out;
|
||||
-o-transition: opacity .1s ease-in-out;
|
||||
-moz-transition: opacity .1s ease-in-out;
|
||||
-webkit-transition: opacity .1s ease-in-out;
|
||||
}
|
||||
#boardNavDesktop.reply:not(:hover) {
|
||||
opacity: .4;
|
||||
transition: opacity 1.5s .5s ease-in-out;
|
||||
-o-transition: opacity 1.5s .5s ease-in-out;
|
||||
-moz-transition: opacity 1.5s .5s ease-in-out;
|
||||
-webkit-transition: opacity 1.5s .5s ease-in-out;
|
||||
}
|
||||
#boardNavDesktop.reply a {
|
||||
margin: -1px;
|
||||
}
|
||||
#settings {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* thread updater */
|
||||
#updater {
|
||||
text-align: right;
|
||||
}
|
||||
#updater:not(:hover) {
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
#updater input[type=number] {
|
||||
width: 4em;
|
||||
}
|
||||
#updater:not(:hover) > div:not(.move) {
|
||||
display: none;
|
||||
}
|
||||
.new {
|
||||
color: limegreen;
|
||||
}
|
||||
|
||||
/* quote */
|
||||
.quotelink.deadlink {
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
.deadlink:not(.quotelink) {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
.inlined {
|
||||
opacity: .5;
|
||||
}
|
||||
#qp input, .forwarded {
|
||||
display: none;
|
||||
}
|
||||
.quotelink.forwardlink,
|
||||
.backlink.forwardlink {
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dashed;
|
||||
}
|
||||
.inline {
|
||||
border: 1px solid rgba(128, 128, 128, .5);
|
||||
display: table;
|
||||
margin: 2px 0;
|
||||
}
|
||||
.inline .post {
|
||||
border: 0 !important;
|
||||
display: table !important;
|
||||
margin: 0 !important;
|
||||
padding: 1px 2px !important;
|
||||
}
|
||||
#qp {
|
||||
padding: 2px 2px 5px;
|
||||
}
|
||||
#qp .post {
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#qp img {
|
||||
max-height: 300px;
|
||||
max-width: 500px;
|
||||
}
|
||||
.qphl {
|
||||
box-shadow: 0 0 0 2px rgba(216, 94, 49, .7);
|
||||
}
|
||||
|
||||
/* file */
|
||||
.fileText:hover .fntrunc,
|
||||
.fileText:not(:hover) .fnfull {
|
||||
display: none;
|
||||
}
|
||||
#ihover {
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
max-height: 100%;
|
||||
max-width: 75%;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
120
grunt.js
Normal file
120
grunt.js
Normal file
@ -0,0 +1,120 @@
|
||||
module.exports = function(grunt) {
|
||||
|
||||
// Some tasks do not support directives.
|
||||
var meta = {
|
||||
name: '4chan X Alpha',
|
||||
version: '3.0.0',
|
||||
};
|
||||
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
meta: {
|
||||
name: meta.name,
|
||||
version: meta.version,
|
||||
repo: 'https://github.com/MayhemYDG/4chan-x/',
|
||||
banner: [
|
||||
'/* <%= meta.name %> - Version <%= meta.version %> - <%= grunt.template.today("yyyy-mm-dd") %>',
|
||||
' * http://mayhemydg.github.com/4chan-x/',
|
||||
' *',
|
||||
' * Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com>',
|
||||
' * Copyright (c) <%= grunt.template.today("yyyy") %> Nicolas Stepien <stepien.nicolas@gmail.com>',
|
||||
' * Licensed under the MIT license.',
|
||||
' * <%= meta.repo %>blob/master/LICENSE',
|
||||
' *',
|
||||
' * Contributors:',
|
||||
' * <%= meta.repo %>graphs/contributors',
|
||||
' * ferongr, xat-, Ongpot, thisisanon and Anonymous - cooldown sanity check',
|
||||
' * e000 - cooldown sanity check',
|
||||
' * Seiba - chrome quick reply focusing',
|
||||
' * herpaderpderp - recaptcha fixes',
|
||||
' * WakiMiko - recaptcha tab order http://userscripts.org/scripts/show/82657',
|
||||
' *',
|
||||
' * All the people who\'ve taken the time to write bug reports.',
|
||||
' *',
|
||||
' * Thank you.',
|
||||
' */'
|
||||
].join('\n'),
|
||||
metadataBlock: [
|
||||
'// ==UserScript==',
|
||||
'// @name <%= meta.name %>',
|
||||
'// @version <%= meta.version %>',
|
||||
'// @description Adds various features.',
|
||||
'// @copyright 2009-2011 James Campos <james.r.campos@gmail.com>',
|
||||
'// @copyright <%= grunt.template.today("yyyy") %> Nicolas Stepien <stepien.nicolas@gmail.com>',
|
||||
'// @license MIT; http://en.wikipedia.org/wiki/Mit_license',
|
||||
'// @match *://boards.4chan.org/*',
|
||||
'// @match *://images.4chan.org/*',
|
||||
'// @match *://sys.4chan.org/*',
|
||||
'// @match *://api.4chan.org/*',
|
||||
'// @match *://*.foolz.us/api/*',
|
||||
'// @grant GM_getValue',
|
||||
'// @grant GM_setValue',
|
||||
'// @grant GM_deleteValue',
|
||||
'// @grant GM_openInTab',
|
||||
'// @run-at document-start',
|
||||
'// @updateURL <%= meta.repo %>raw/stable/<%= meta.files.metajs %>',
|
||||
'// @downloadURL <%= meta.repo %>raw/stable/<%= meta.files.userjs %>',
|
||||
'// @icon <%= meta.repo %>raw/stable/img/icon.gif',
|
||||
'// ==/UserScript=='
|
||||
].join('\n'),
|
||||
latest: 'document.dispatchEvent(new CustomEvent("<%= meta.name.replace(/ /g, "") %>Update",{detail:{v:"<%= meta.version %>"}}))',
|
||||
files: {
|
||||
metajs: '4chan_x.meta.js',
|
||||
userjs: '4chan_x.user.js',
|
||||
latestjs: 'latestv3.js'
|
||||
},
|
||||
},
|
||||
concat: {
|
||||
coffee: {
|
||||
src: [
|
||||
'<file_template:src/config.coffee>',
|
||||
'<file_template:lib/ui.coffee>',
|
||||
'<file_template:lib/$.coffee>',
|
||||
'<file_template:src/globals.coffee>',
|
||||
'<file_template:src/main.coffee>',
|
||||
'<file_template:src/features.coffee>'
|
||||
],
|
||||
dest: 'tmp/script.coffee'
|
||||
},
|
||||
js: {
|
||||
src: ['<banner:meta.metadataBlock>', '<banner:meta.banner>', 'tmp/script.js'],
|
||||
dest: '<config:meta.files.userjs>'
|
||||
},
|
||||
meta: {
|
||||
src: '<banner:meta.metadataBlock>',
|
||||
dest: '<config:meta.files.metajs>'
|
||||
},
|
||||
latest: {
|
||||
src: '<banner:meta.latest>',
|
||||
dest: '<config:meta.files.latestjs>'
|
||||
}
|
||||
},
|
||||
exec: {
|
||||
coffee: {
|
||||
command: 'coffee --compile tmp/script.coffee',
|
||||
stdout: true
|
||||
},
|
||||
commit: {
|
||||
command: [
|
||||
'git commit -am "Release ' + meta.name + ' v' + meta.version + '."',
|
||||
'git tag -a ' + meta.version + ' -m "' + meta.version + '"',
|
||||
'git tag -af stable -m "' + meta.version + '"'
|
||||
].join(' && '),
|
||||
stdout: true
|
||||
},
|
||||
clean: {
|
||||
command: 'rm -r tmp'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
files: ['grunt.js', 'lib/**/*.coffee', 'src/**/*.coffee', 'css/**/*.css', 'img/*'],
|
||||
tasks: 'coffee concat:build'
|
||||
}
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-exec');
|
||||
|
||||
grunt.registerTask('default', 'concat:coffee exec:coffee concat:js exec:clean');
|
||||
grunt.registerTask('upgrade', 'concat:meta concat:latest default exec:commit');
|
||||
|
||||
};
|
||||
BIN
img/icon.gif
Normal file
BIN
img/icon.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 96 B |
1
latestv3.js
Normal file
1
latestv3.js
Normal file
@ -0,0 +1 @@
|
||||
document.dispatchEvent(new CustomEvent("4chanXAlphaUpdate",{detail:{v:"3.0.0"}}))
|
||||
230
lib/$.coffee
Normal file
230
lib/$.coffee
Normal file
@ -0,0 +1,230 @@
|
||||
# loosely follows the jquery api:
|
||||
# http://api.jquery.com/
|
||||
# not chainable
|
||||
$ = (selector, root=d.body) ->
|
||||
root.querySelector selector
|
||||
$$ = (selector, root=d.body) ->
|
||||
Array::slice.call root.querySelectorAll selector
|
||||
|
||||
$.extend = (object, properties) ->
|
||||
for key, val of properties
|
||||
object[key] = val
|
||||
return
|
||||
|
||||
$.extend $,
|
||||
SECOND: 1000
|
||||
MINUTE: 1000 * 60
|
||||
HOUR : 1000 * 60 * 60
|
||||
DAY : 1000 * 60 * 60 * 24
|
||||
log: console.log.bind console
|
||||
engine: /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase()
|
||||
id: (id) ->
|
||||
d.getElementById id
|
||||
ready: (fc) ->
|
||||
if /interactive|complete/.test d.readyState
|
||||
$.queueTask fc
|
||||
return
|
||||
cb = ->
|
||||
$.off d, 'DOMContentLoaded', cb
|
||||
fc()
|
||||
$.on d, 'DOMContentLoaded', cb
|
||||
sync: (key, cb) ->
|
||||
$.on window, 'storage', (e) ->
|
||||
if e.key is "#{g.NAMESPACE}#{key}"
|
||||
cb JSON.parse e.newValue
|
||||
formData: (form) ->
|
||||
if form instanceof HTMLFormElement
|
||||
return new FormData form
|
||||
fd = new FormData()
|
||||
for key, val of form
|
||||
fd.append key, val if val
|
||||
fd
|
||||
ajax: (url, callbacks, opts={}) ->
|
||||
{type, headers, upCallbacks, form} = opts
|
||||
r = new XMLHttpRequest()
|
||||
type or= form and 'post' or 'get'
|
||||
r.open type, url, true
|
||||
for key, val of headers
|
||||
r.setRequestHeader key, val
|
||||
$.extend r, callbacks
|
||||
$.extend r.upload, upCallbacks
|
||||
r.withCredentials = type is 'post'
|
||||
r.send form
|
||||
r
|
||||
cache: (->
|
||||
reqs = {}
|
||||
(url, cb) ->
|
||||
if req = reqs[url]
|
||||
if req.readyState is 4
|
||||
cb.call req
|
||||
else
|
||||
req.callbacks.push cb
|
||||
return
|
||||
req = $.ajax url,
|
||||
onload: ->
|
||||
cb.call @ for cb in @callbacks
|
||||
delete @callbacks
|
||||
onabort: -> delete reqs[url]
|
||||
onerror: -> delete reqs[url]
|
||||
req.callbacks = [cb]
|
||||
reqs[url] = req
|
||||
)()
|
||||
cb:
|
||||
checked: ->
|
||||
$.set @name, @checked
|
||||
Conf[@name] = @checked
|
||||
value: ->
|
||||
$.set @name, @value.trim()
|
||||
Conf[@name] = @value
|
||||
addStyle: (css) ->
|
||||
style = $.el 'style',
|
||||
textContent: css
|
||||
# That's terrible.
|
||||
# XXX tmp fix for scriptish:
|
||||
# https://github.com/scriptish/scriptish/issues/16
|
||||
f = ->
|
||||
# XXX Only Chrome has no d.head on document-start.
|
||||
if root = d.head or d.documentElement
|
||||
$.add root, style
|
||||
else
|
||||
setTimeout f, 20
|
||||
f()
|
||||
style
|
||||
x: (path, root=d.body) ->
|
||||
# XPathResult.ANY_UNORDERED_NODE_TYPE === 8
|
||||
d.evaluate(path, root, null, 8, null).singleNodeValue
|
||||
addClass: (el, className) ->
|
||||
el.classList.add className
|
||||
rmClass: (el, className) ->
|
||||
el.classList.remove className
|
||||
hasClass: (el, className) ->
|
||||
el.classList.contains className
|
||||
rm: (el) ->
|
||||
el.parentNode.removeChild el
|
||||
tn: (s) ->
|
||||
d.createTextNode s
|
||||
nodes: (nodes) ->
|
||||
# In (at least) Chrome, elements created inside different
|
||||
# scripts/window contexts inherit from unequal prototypes.
|
||||
# window_context1.Node !== window_context2.Node
|
||||
unless nodes instanceof Array
|
||||
return nodes
|
||||
frag = d.createDocumentFragment()
|
||||
for node in nodes
|
||||
frag.appendChild node
|
||||
frag
|
||||
add: (parent, el) ->
|
||||
parent.appendChild $.nodes el
|
||||
prepend: (parent, el) ->
|
||||
parent.insertBefore $.nodes(el), parent.firstChild
|
||||
after: (root, el) ->
|
||||
root.parentNode.insertBefore $.nodes(el), root.nextSibling
|
||||
before: (root, el) ->
|
||||
root.parentNode.insertBefore $.nodes(el), root
|
||||
replace: (root, el) ->
|
||||
root.parentNode.replaceChild $.nodes(el), root
|
||||
el: (tag, properties) ->
|
||||
el = d.createElement tag
|
||||
$.extend el, properties if properties
|
||||
el
|
||||
on: (el, events, handler) ->
|
||||
for event in events.split ' '
|
||||
el.addEventListener event, handler, false
|
||||
return
|
||||
off: (el, events, handler) ->
|
||||
for event in events.split ' '
|
||||
el.removeEventListener event, handler, false
|
||||
return
|
||||
open: (url) ->
|
||||
(GM_openInTab or window.open) url, '_blank'
|
||||
queueTask: (->
|
||||
# inspired by https://www.w3.org/Bugs/Public/show_bug.cgi?id=15007
|
||||
taskQueue = []
|
||||
execTask = ->
|
||||
task = taskQueue.shift()
|
||||
func = task[0]
|
||||
args = Array::slice.call task, 1
|
||||
func.apply func, args
|
||||
if window.MessageChannel
|
||||
taskChannel = new MessageChannel()
|
||||
taskChannel.port1.onmessage = execTask
|
||||
->
|
||||
taskQueue.push arguments
|
||||
taskChannel.port2.postMessage null
|
||||
else # XXX Firefox
|
||||
->
|
||||
taskQueue.push arguments
|
||||
setTimeout execTask, 0
|
||||
)()
|
||||
globalEval: (code) ->
|
||||
script = $.el 'script',
|
||||
textContent: code
|
||||
$.add d.head, script
|
||||
$.rm script
|
||||
# http://mths.be/unsafewindow
|
||||
unsafeWindow:
|
||||
if window.opera # Opera
|
||||
window
|
||||
else if unsafeWindow isnt window # Firefox
|
||||
unsafeWindow
|
||||
else # Chrome
|
||||
(->
|
||||
p = d.createElement 'p'
|
||||
p.setAttribute 'onclick', 'return window'
|
||||
p.onclick()
|
||||
)()
|
||||
bytesToString: (size) ->
|
||||
unit = 0 # Bytes
|
||||
while size >= 1024
|
||||
size /= 1024
|
||||
unit++
|
||||
# Remove trailing 0s.
|
||||
size =
|
||||
if unit > 1
|
||||
# Keep the size as a float if the size is greater than 2^20 B.
|
||||
# Round to hundredth.
|
||||
Math.round(size * 100) / 100
|
||||
else
|
||||
# Round to an integer otherwise.
|
||||
Math.round size
|
||||
"#{size} #{['B', 'KB', 'MB', 'GB'][unit]}"
|
||||
|
||||
$.extend $,
|
||||
if GM_deleteValue?
|
||||
delete: (name) ->
|
||||
GM_deleteValue g.NAMESPACE + name
|
||||
get: (name, defaultValue) ->
|
||||
if value = GM_getValue g.NAMESPACE + name
|
||||
JSON.parse value
|
||||
else
|
||||
defaultValue
|
||||
set: (name, value) ->
|
||||
name = g.NAMESPACE + name
|
||||
value = JSON.stringify value
|
||||
# for `storage` events
|
||||
localStorage.setItem name, value
|
||||
GM_setValue name, value
|
||||
else if window.opera
|
||||
delete: (name)->
|
||||
delete opera.scriptStorage[g.NAMESPACE + name]
|
||||
get: (name, defaultValue) ->
|
||||
if value = opera.scriptStorage[g.NAMESPACE + name]
|
||||
JSON.parse value
|
||||
else
|
||||
defaultValue
|
||||
set: (name, value) ->
|
||||
name = g.NAMESPACE + name
|
||||
value = JSON.stringify value
|
||||
# for `storage` events
|
||||
localStorage.setItem name, value
|
||||
opera.scriptStorage[name] = value
|
||||
else
|
||||
delete: (name) ->
|
||||
localStorage.removeItem g.NAMESPACE + name
|
||||
get: (name, defaultValue) ->
|
||||
if value = localStorage.getItem g.NAMESPACE + name
|
||||
JSON.parse value
|
||||
else
|
||||
defaultValue
|
||||
set: (name, value) ->
|
||||
localStorage.setItem g.NAMESPACE + name, JSON.stringify value
|
||||
129
lib/ui.coffee
Normal file
129
lib/ui.coffee
Normal file
@ -0,0 +1,129 @@
|
||||
UI = (->
|
||||
dialog = (id, position, html) ->
|
||||
el = d.createElement 'div'
|
||||
el.className = 'reply dialog'
|
||||
el.innerHTML = html
|
||||
el.id = id
|
||||
el.style.cssText = localStorage.getItem("#{g.NAMESPACE}#{id}.position") or position
|
||||
move = el.querySelector '.move'
|
||||
move.addEventListener 'touchstart', dragstart, false
|
||||
move.addEventListener 'mousedown', dragstart, false
|
||||
el
|
||||
|
||||
dragstart = (e) ->
|
||||
# prevent text selection
|
||||
e.preventDefault()
|
||||
el = @parentNode
|
||||
if isTouching = e.type is 'touchstart'
|
||||
e = e.changedTouches[e.changedTouches.length - 1]
|
||||
# distance from pointer to el edge is constant; calculate it here.
|
||||
rect = el.getBoundingClientRect()
|
||||
screenHeight = d.documentElement.clientHeight
|
||||
screenWidth = d.documentElement.clientWidth
|
||||
o = {
|
||||
id: el.id
|
||||
style: el.style
|
||||
dx: e.clientX - rect.left
|
||||
dy: e.clientY - rect.top
|
||||
height: screenHeight - rect.height
|
||||
width: screenWidth - rect.width
|
||||
screenHeight: screenHeight
|
||||
screenWidth: screenWidth
|
||||
isTouching: isTouching
|
||||
}
|
||||
if isTouching
|
||||
o.identifier = e.identifier
|
||||
o.move = touchmove.bind o
|
||||
o.up = touchend.bind o
|
||||
d.addEventListener 'touchmove', o.move, false
|
||||
d.addEventListener 'touchend', o.up, false
|
||||
d.addEventListener 'touchcancel', o.up, false
|
||||
else # mousedown
|
||||
o.move = drag.bind o
|
||||
o.up = dragend.bind o
|
||||
d.addEventListener 'mousemove', o.move, false
|
||||
d.addEventListener 'mouseup', o.up, false
|
||||
touchmove = (e) ->
|
||||
for touch in e.changedTouches
|
||||
if touch.identifier is @identifier
|
||||
drag.call @, touch
|
||||
return
|
||||
drag = (e) ->
|
||||
left = e.clientX - @dx
|
||||
top = e.clientY - @dy
|
||||
if left < 10 then left = 0
|
||||
else if @width - left < 10 then left = null
|
||||
if top < 10 then top = 0
|
||||
else if @height - top < 10 then top = null
|
||||
if left is null
|
||||
@style.left = null
|
||||
@style.right = '0%'
|
||||
else
|
||||
@style.left = left / @screenWidth * 100 + '%'
|
||||
@style.right = null
|
||||
if top is null
|
||||
@style.top = null
|
||||
@style.bottom = '0%'
|
||||
else
|
||||
@style.top = top / @screenHeight * 100 + '%'
|
||||
@style.bottom = null
|
||||
touchend = (e) ->
|
||||
for touch in e.changedTouches
|
||||
if touch.identifier is @identifier
|
||||
dragend.call @
|
||||
return
|
||||
dragend = ->
|
||||
if @isTouching
|
||||
d.removeEventListener 'touchmove', @move, false
|
||||
d.removeEventListener 'touchend', @up, false
|
||||
d.removeEventListener 'touchcancel', @up, false
|
||||
else # mouseup
|
||||
d.removeEventListener 'mousemove', @move, false
|
||||
d.removeEventListener 'mouseup', @up, false
|
||||
localStorage.setItem "#{g.NAMESPACE}#{@id}.position", @style.cssText
|
||||
|
||||
hoverstart = (root, el, events, cb) ->
|
||||
o = {
|
||||
root: root
|
||||
el: el
|
||||
style: el.style
|
||||
cb: cb
|
||||
events: events.split ' '
|
||||
clientHeight: d.documentElement.clientHeight
|
||||
clientWidth: d.documentElement.clientWidth
|
||||
}
|
||||
o.hover = hover.bind o
|
||||
o.hoverend = hoverend.bind o
|
||||
for event in o.events
|
||||
root.addEventListener event, o.hoverend, false
|
||||
root.addEventListener 'mousemove', o.hover, false
|
||||
hover = (e) ->
|
||||
height = @el.offsetHeight
|
||||
top = e.clientY - height / 2
|
||||
@style.top =
|
||||
if @clientHeight <= height or top <= 0
|
||||
'0px'
|
||||
else if top + height >= @clientHeight
|
||||
@clientHeight - height + 'px'
|
||||
else
|
||||
top + 'px'
|
||||
|
||||
{clientX} = e
|
||||
if clientX <= @clientWidth - 400
|
||||
@style.left = clientX + 45 + 'px'
|
||||
@style.right = null
|
||||
else
|
||||
@style.left = null
|
||||
@style.right = @clientWidth - clientX + 45 + 'px'
|
||||
hoverend = ->
|
||||
@el.parentNode.removeChild @el
|
||||
for event in @events
|
||||
@root.removeEventListener event, @hoverend, false
|
||||
@root.removeEventListener 'mousemove', @hover, false
|
||||
@cb.call @ if @cb
|
||||
|
||||
return {
|
||||
dialog: dialog
|
||||
hover: hoverstart
|
||||
}
|
||||
)()
|
||||
22
readme.md
22
readme.md
@ -1,22 +0,0 @@
|
||||
# Get 4chan X [HERE](http://mayhemydg.github.com/4chan-x/).
|
||||
|
||||
# Building
|
||||
|
||||
- Install [node.js](http://nodejs.org/).
|
||||
- Install [CoffeeScript](http://coffeescript.org/) with `npm install -g coffee-script`.
|
||||
- Clone 4chan X.
|
||||
- `cd` into it and build with `cake build`.
|
||||
- For development (continuous builds), run `cake dev &`. Kill the process with `killall node`.
|
||||
|
||||
# Releasing
|
||||
|
||||
- Upgrade version with `cake -v VERSION upgrade`. Note that this is only used to
|
||||
release new 4chan x versions, and is not needed or wanted in pull requests.
|
||||
|
||||
# Contributing
|
||||
|
||||
- Edit the CoffeeScript source
|
||||
- Build the JavaScript
|
||||
- If the edits affect regular users, edit the changelog
|
||||
- Fork the repo
|
||||
- Send a pull request
|
||||
163
src/config.coffee
Normal file
163
src/config.coffee
Normal file
@ -0,0 +1,163 @@
|
||||
Config =
|
||||
main:
|
||||
Enhancing:
|
||||
'Disable 4chan\'s extension': [true, 'Avoid conflicts between <%= meta.name %> and 4chan\'s inline extension.']
|
||||
'404 Redirect': [true, 'Redirect dead threads and images.']
|
||||
'Keybinds': [true, 'Bind actions to keyboard shortcuts.']
|
||||
'Time Formatting': [true, 'Localize and format timestamps arbitrarily.']
|
||||
'File Info Formatting': [true, 'Reformat the file information.']
|
||||
'Comment Expansion': [true, 'Can expand too long comments.']
|
||||
'Thread Expansion': [true, 'Can expand threads to view all replies.']
|
||||
'Index Navigation': [false, 'Navigate to previous / next thread.']
|
||||
'Reply Navigation': [false, 'Navigate to top / bottom of thread.']
|
||||
'Check for Updates': [true, 'Check for updated versions of <%= meta.name %>.']
|
||||
Filtering:
|
||||
'Anonymize': [false, 'Turn everyone Anonymous.']
|
||||
'Filter': [true, 'Self-moderation placebo.']
|
||||
'Recursive Filtering': [true, 'Filter replies of filtered posts, recursively.']
|
||||
'Reply Hiding': [true, 'Hide single replies.']
|
||||
'Thread Hiding': [true, 'Hide entire threads.']
|
||||
'Stubs': [true, 'Make stubs of hidden threads / replies.']
|
||||
Imaging:
|
||||
'Auto-GIF': [false, 'Animate GIF thumbnails.']
|
||||
'Image Expansion': [true, 'Expand images.']
|
||||
'Expand From Position': [true, 'Expand all images only from current position to thread end.']
|
||||
'Image Hover': [false, 'Show full image on mouseover.']
|
||||
'Sauce': [true, 'Add sauce links to images.']
|
||||
'Reveal Spoilers': [false, 'Reveal spoiler thumbnails.']
|
||||
Menu:
|
||||
'Menu': [true, 'Add a drop-down menu in posts.']
|
||||
'Report Link': [true, 'Add a report link to the menu.']
|
||||
'Delete Link': [true, 'Add post and image deletion links to the menu.']
|
||||
'Download Link': [true, 'Add a download with original filename link to the menu. Chrome-only currently.']
|
||||
'Archive Link': [true, 'Add an archive link to the menu.']
|
||||
Monitoring:
|
||||
'Thread Updater': [true, 'Fetch and insert new replies. Has more options in its own dialog.']
|
||||
'Unread Count': [true, 'Show the unread posts count in the tab title.']
|
||||
'Unread Favicon': [true, 'Show a different favicon when there are unread posts.']
|
||||
'Post in Title': [true, 'Show the thread\'s subject in the tab title.']
|
||||
'Thread Stats': [true, 'Display reply and image count.']
|
||||
'Thread Watcher': [true, 'Bookmark threads.']
|
||||
'Auto Watch': [true, 'Automatically watch threads that you start.']
|
||||
'Auto Watch Reply': [false, 'Automatically watch threads that you reply to.']
|
||||
Posting:
|
||||
'Quick Reply': [true, 'WMD.']
|
||||
'Persistent QR': [false, 'The Quick reply won\'t disappear after posting.']
|
||||
'Auto Hide QR': [false, 'Automatically hide the quick reply when posting.']
|
||||
'Open Reply in New Tab': [false, 'Open replies posted from the board pages in a new tab.']
|
||||
'Remember Subject': [false, 'Remember the subject field, instead of resetting after posting.']
|
||||
'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.']
|
||||
'Hide Original Post Form': [true, 'Replace the normal post form with a shortcut to open the QR.']
|
||||
Quoting:
|
||||
'Quote Backlinks': [true, 'Add quote backlinks.']
|
||||
'OP Backlinks': [false, 'Add backlinks to the OP.']
|
||||
'Quote Inline': [true, 'Inline quoted post on click.']
|
||||
'Forward Hiding': [true, 'Hide original posts of inlined backlinks.']
|
||||
'Quote Preview': [true, 'Show quoted post on hover.']
|
||||
'Quote Highlighting': [true, 'Highlight the previewed post.']
|
||||
'Resurrect Quotes': [true, 'Linkify dead quotes to archives.']
|
||||
'Indicate OP Quotes': [true, 'Add \'(OP)\' to OP quotes.']
|
||||
'Indicate Cross-thread Quotes': [true, 'Add \'(Cross-thread)\' to cross-threads quotes.']
|
||||
filter:
|
||||
name: [
|
||||
'# Filter any namefags:'
|
||||
'#/^(?!Anonymous$)/'
|
||||
].join '\n'
|
||||
uniqueid: [
|
||||
'# Filter a specific ID:'
|
||||
'#/Txhvk1Tl/'
|
||||
].join '\n'
|
||||
tripcode: [
|
||||
'# Filter any tripfags'
|
||||
'#/^!/'
|
||||
].join '\n'
|
||||
capcode: [
|
||||
'# Set a custom class for mods:'
|
||||
'#/Mod$/;highlight:mod;op:yes'
|
||||
'# Set a custom class for moot:'
|
||||
'#/Admin$/;highlight:moot;op:yes'
|
||||
].join '\n'
|
||||
email: [
|
||||
'# Filter any e-mails that are not `sage` on /a/ and /jp/:'
|
||||
'#/^(?!sage$)/;boards:a,jp'
|
||||
].join '\n'
|
||||
subject: [
|
||||
'# Filter Generals on /v/:'
|
||||
'#/general/i;boards:v;op:only'
|
||||
].join '\n'
|
||||
comment: [
|
||||
'# Filter Stallman copypasta on /g/:'
|
||||
'#/what you\'re refer+ing to as linux/i;boards:g'
|
||||
].join '\n'
|
||||
flag: [
|
||||
''
|
||||
].join '\n'
|
||||
filename: [
|
||||
''
|
||||
].join '\n'
|
||||
dimensions: [
|
||||
'# Highlight potential wallpapers:'
|
||||
'#/1920x1080/;op:yes;highlight;top:no;boards:w,wg'
|
||||
].join '\n'
|
||||
filesize: [
|
||||
''
|
||||
].join '\n'
|
||||
md5: [
|
||||
''
|
||||
].join '\n'
|
||||
sauces: [
|
||||
'http://iqdb.org/?url=%turl'
|
||||
'http://www.google.com/searchbyimage?image_url=%turl'
|
||||
'#http://tineye.com/search?url=%turl'
|
||||
'#http://saucenao.com/search.php?db=999&url=%turl'
|
||||
'#http://3d.iqdb.org/?url=%turl'
|
||||
'#http://regex.info/exif.cgi?imgurl=%url'
|
||||
'# uploaders:'
|
||||
'#http://imgur.com/upload?url=%url;text:Upload to imgur'
|
||||
'#http://omploader.org/upload?url1=%url;text:Upload to omploader'
|
||||
'# "View Same" in archives:'
|
||||
'#//archive.foolz.us/_/search/image/%md5/;text:View same on foolz'
|
||||
'#//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/'
|
||||
].join '\n'
|
||||
time: '%m/%d/%y(%a)%H:%M:%S'
|
||||
backlink: '>>%id'
|
||||
fileInfo: '%l (%p%s, %r)'
|
||||
favicon: 'ferongr'
|
||||
hotkeys:
|
||||
# QR & Options
|
||||
'open QR': ['q', 'Open QR with post number inserted.']
|
||||
'open empty QR': ['Q', 'Open QR without post number inserted.']
|
||||
'open options': ['alt+o', 'Open Options.']
|
||||
'close': ['Esc', 'Close Options or QR.']
|
||||
'spoiler tags': ['ctrl+s', 'Insert spoiler tags.']
|
||||
'code tags': ['alt+c', 'Insert code tags.']
|
||||
'submit QR': ['alt+s', 'Submit post.']
|
||||
# Thread related
|
||||
'watch': ['w', 'Watch thread.']
|
||||
'update': ['u', 'Update the thread now.']
|
||||
'read thread': ['r', 'Mark thread as read.']
|
||||
# Images
|
||||
'expand image': ['E', 'Expand selected image.']
|
||||
'expand images': ['e', 'Expand all images.']
|
||||
# Board Navigation
|
||||
'front page': ['0', 'Jump to page 0.']
|
||||
'next page': ['Right', 'Jump to the next page.']
|
||||
'previous page': ['Left', 'Jump to the previous page.']
|
||||
# Thread Navigation
|
||||
'next thread': ['Down', 'See next thread.']
|
||||
'previous thread': ['Up', 'See previous thread.']
|
||||
'expand thread': ['ctrl+e', 'Expand thread.']
|
||||
'open thread': ['o', 'Open thread in current tab.']
|
||||
'open thread tab': ['O', 'Open thread in new tab.']
|
||||
# Reply Navigation
|
||||
'next reply': ['j', 'Select next reply.']
|
||||
'previous reply': ['k', 'Select previous reply.']
|
||||
'hide': ['x', 'Hide thread.']
|
||||
updater:
|
||||
checkbox:
|
||||
'Auto Scroll': [false, 'Scroll updated posts into view. Only enabled at bottom of page.']
|
||||
'Scroll BG': [false, 'Auto-scroll background tabs.']
|
||||
'Auto Update': [true, 'Automatically fetch new posts.']
|
||||
'Interval': 30
|
||||
imageFit: 'fit width'
|
||||
File diff suppressed because it is too large
Load Diff
12
src/globals.coffee
Normal file
12
src/globals.coffee
Normal file
@ -0,0 +1,12 @@
|
||||
# Opera doesn't support the @match metadata key,
|
||||
# return 4chan X here if we're not on 4chan.
|
||||
return unless /^(boards|images|sys)\.4chan\.org$/.test location.hostname
|
||||
|
||||
Conf = {}
|
||||
d = document
|
||||
g =
|
||||
VERSION: '<%= meta.version %>'
|
||||
NAMESPACE: "<%= meta.name.replace(/ /g, '_') %>."
|
||||
boards: {}
|
||||
threads: {}
|
||||
posts: {}
|
||||
458
src/main.coffee
Normal file
458
src/main.coffee
Normal file
@ -0,0 +1,458 @@
|
||||
class Board
|
||||
toString: -> @ID
|
||||
|
||||
constructor: (@ID) ->
|
||||
@threads = {}
|
||||
@posts = {}
|
||||
|
||||
g.boards[@] = @
|
||||
|
||||
class Thread
|
||||
callbacks: []
|
||||
toString: -> @ID
|
||||
|
||||
constructor: (ID, @board) ->
|
||||
@ID = +ID
|
||||
@posts = {}
|
||||
|
||||
# XXX Can't check when parsing single posts
|
||||
# move to Post constructor? unless @isReply
|
||||
# postInfo = $ '.postInfo', root.firstElementChild
|
||||
# @isClosed = !!$ 'img[title=Closed]', postInfo
|
||||
# @isSticky = !!$ 'img[title=Sticky]', postInfo
|
||||
|
||||
g.threads["#{board}.#{@}"] = board.threads[@] = @
|
||||
|
||||
class Post
|
||||
callbacks: []
|
||||
toString: -> @ID
|
||||
|
||||
constructor: (root, @thread, @board, that={}) ->
|
||||
@ID = +root.id[2..]
|
||||
|
||||
post = $ '.post', root
|
||||
info = $ '.postInfo', post
|
||||
@nodes =
|
||||
root: root
|
||||
post: post
|
||||
info: info
|
||||
comment: $ '.postMessage', post
|
||||
quotelinks: []
|
||||
backlinks: info.getElementsByClassName 'backlink'
|
||||
|
||||
@info = {}
|
||||
if subject = $ '.subject', info
|
||||
@nodes.subject = subject
|
||||
@info.subject = subject.textContent
|
||||
if name = $ '.name', info
|
||||
@nodes.name = name
|
||||
@info.name = name.textContent
|
||||
if email = $ '.useremail', info
|
||||
@nodes.email = email
|
||||
@info.email = decodeURIComponent email.href[7..]
|
||||
if tripcode = $ '.postertrip', info
|
||||
@nodes.tripcode = tripcode
|
||||
@info.tripcode = tripcode.textContent
|
||||
if uniqueID = $ '.posteruid', info
|
||||
@nodes.uniqueID = uniqueID
|
||||
@info.uniqueID = uniqueID.textContent
|
||||
if capcode = $ '.capcode', info
|
||||
@nodes.capcode = capcode
|
||||
@info.capcode = capcode.textContent
|
||||
if flag = $ '.countryFlag', info
|
||||
@nodes.flag = flag
|
||||
@info.flag = flag.title
|
||||
if date = $ '.dateTime', info
|
||||
@nodes.date = date
|
||||
@info.date = new Date date.dataset.utc * 1000
|
||||
|
||||
# Get the comment's text.
|
||||
# <br> -> \n
|
||||
# Remove:
|
||||
# 'Comment too long'...
|
||||
# Admin/Mod/Dev replies. (/q/)
|
||||
# EXIF data. (/p/)
|
||||
# Rolls. (/tg/)
|
||||
# Preceding and following new lines.
|
||||
# Trailing spaces.
|
||||
bq = @nodes.comment.cloneNode true
|
||||
for node in $$ '.abbr, .capcodeReplies, .exif, b', bq
|
||||
$.rm node
|
||||
text = []
|
||||
# XPathResult.ORDERED_NODE_SNAPSHOT_TYPE === 7
|
||||
nodes = d.evaluate './/br|.//text()', bq, null, 7, null
|
||||
for i in [0...nodes.snapshotLength]
|
||||
text.push if data = nodes.snapshotItem(i).data then data else '\n'
|
||||
@info.comment = text.join('').replace /^\n+|\n+$| +(?=\n|$)/g, ''
|
||||
|
||||
quotes = {}
|
||||
for quotelink in $$ '.quotelink', @nodes.comment
|
||||
# Don't add board links. (>>>/b/)
|
||||
# Don't add text-board quotelinks. (>>>/img/1234)
|
||||
# Don't count capcode replies as quotes. (Admin/Mod/Dev Replies: ...)
|
||||
# Only add quotes that link to posts on an imageboard.
|
||||
if quotelink.hash
|
||||
@nodes.quotelinks.push quotelink
|
||||
continue if quotelink.parentNode.parentNode.className is 'capcodeReplies'
|
||||
quotes["#{quotelink.pathname.split('/')[1]}.#{quotelink.hash[2..]}"] = true
|
||||
@quotes = Object.keys quotes
|
||||
|
||||
if (file = $ '.file', post) and thumb = $ 'img[data-md5]', file
|
||||
# Supports JPG/PNG/GIF/PDF.
|
||||
# Flash files are not supported.
|
||||
alt = thumb.alt
|
||||
anchor = thumb.parentNode
|
||||
fileInfo = file.firstElementChild
|
||||
@file =
|
||||
info: fileInfo
|
||||
text: fileInfo.firstElementChild
|
||||
thumb: thumb
|
||||
URL: anchor.href
|
||||
MD5: thumb.dataset.md5
|
||||
isSpoiler: $.hasClass anchor, 'imgspoiler'
|
||||
size = +alt.match(/\d+(\.\d+)?/)[0]
|
||||
unit = ['B', 'KB', 'MB', 'GB'].indexOf alt.match(/\w+$/)[0]
|
||||
while unit--
|
||||
size *= 1024
|
||||
@file.size = size
|
||||
@file.thumbURL =
|
||||
if that.isArchived
|
||||
thumb.src
|
||||
else
|
||||
"#{location.protocol}//thumbs.4chan.org/#{board}/thumb/#{@file.URL.match(/(\d+)\./)[1]}s.jpg"
|
||||
# replace %22 with quotes, see:
|
||||
# crbug.com/81193
|
||||
# webk.it/62107
|
||||
# https://www.w3.org/Bugs/Public/show_bug.cgi?id=16909
|
||||
# http://www.whatwg.org/specs/web-apps/current-work/#multipart-form-data
|
||||
@file.name = $('span[title]', fileInfo).title.replace /%22/g, '"'
|
||||
if @file.isImage = /(jpg|png|gif)$/i.test @file.name
|
||||
@file.dimensions = @file.text.textContent.match(/\d+x\d+/)[0]
|
||||
|
||||
@isReply = $.hasClass post, 'reply'
|
||||
@clones = []
|
||||
g.posts["#{board}.#{@}"] = thread.posts[@] = board.posts[@] = @
|
||||
@kill() if that.isArchived
|
||||
|
||||
kill: (img) ->
|
||||
if @file and !@file.isDead
|
||||
@file.isDead = true
|
||||
return if img
|
||||
@isDead = true
|
||||
$.addClass @nodes.root, 'dead'
|
||||
# XXX style dead posts.
|
||||
|
||||
# Get quote/backlinks to this post,
|
||||
# and paint them (Dead).
|
||||
# First:
|
||||
# In every posts,
|
||||
# if it did quote this post,
|
||||
# get all their backlinks.
|
||||
# Second:
|
||||
# If we have quote backlinks,
|
||||
# in all posts this post quoted,
|
||||
# and their clones,
|
||||
# get all of their backlinks.
|
||||
# Third:
|
||||
# In all collected links,
|
||||
# apply (Dead) if relevant.
|
||||
quotelinks = []
|
||||
num = "#{@board}.#{@}"
|
||||
for ID, post of g.posts
|
||||
if -1 < post.quotes.indexOf num
|
||||
for post in [post].concat post.clones
|
||||
quotelinks.push.apply quotelinks, post.nodes.quotelinks
|
||||
if Conf['Quote Backlinks']
|
||||
for quote in @quotes
|
||||
post = g.posts[quote]
|
||||
for post in [post].concat post.clones
|
||||
quotelinks.push.apply quotelinks, Array::slice.call post.nodes.backlinks
|
||||
for quotelink in quotelinks
|
||||
continue if $.hasClass quotelink, 'deadlink'
|
||||
{board, postID} = Get.postDataFromLink quotelink
|
||||
if board is @board.ID postID is @ID
|
||||
$.add quotelink, $.tn '\u00A0(Dead)'
|
||||
$.addClass quotelinks, 'deadlink'
|
||||
return
|
||||
addClone: (context) ->
|
||||
new Clone @, context
|
||||
rmClone: (index) ->
|
||||
@clones.splice index, 1
|
||||
for clone in @clones[index..]
|
||||
clone.nodes.root.setAttribute 'data-clone', index++
|
||||
return
|
||||
|
||||
class Clone extends Post
|
||||
constructor: (@origin, @context) ->
|
||||
for key in ['ID', 'board', 'thread', 'info', 'quotes', 'isReply']
|
||||
# Copy or point to the origin's key value.
|
||||
@[key] = origin[key]
|
||||
|
||||
{nodes} = origin
|
||||
root = nodes.root.cloneNode true
|
||||
post = $ '.post', root
|
||||
info = $ '.postInfo', post
|
||||
@nodes =
|
||||
root: root
|
||||
post: post
|
||||
info: info
|
||||
comment: $ '.postMessage', post
|
||||
quotelinks: []
|
||||
backlinks: info.getElementsByClassName 'backlink'
|
||||
|
||||
# Remove inlined posts inside of this post.
|
||||
for inline in $$ '.inline', post
|
||||
$.rm inline
|
||||
for inlined in $$ '.inlined', post
|
||||
$.rmClass inlined, 'inlined'
|
||||
|
||||
# root.hidden = false # post hiding
|
||||
$.rmClass root, 'forwarded' # quote inlining
|
||||
# $.rmClass post, 'highlight' # keybind navigation
|
||||
|
||||
if nodes.subject
|
||||
@nodes.subject = $ '.subject', info
|
||||
if nodes.name
|
||||
@nodes.name = $ '.name', info
|
||||
if nodes.email
|
||||
@nodes.email = $ '.useremail', info
|
||||
if nodes.tripcode
|
||||
@nodes.tripcode = $ '.postertrip', info
|
||||
if nodes.uniqueID
|
||||
@nodes.uniqueID = $ '.posteruid', info
|
||||
if nodes.capcode
|
||||
@nodes.capcode = $ '.capcode', info
|
||||
if nodes.flag
|
||||
@nodes.flag = $ '.countryFlag', info
|
||||
if nodes.date
|
||||
@nodes.date = $ '.dateTime', info
|
||||
|
||||
for quotelink in $$ '.quotelink', @nodes.comment
|
||||
# See comments in Post's constructor.
|
||||
if quotelink.hash or $.hasClass quotelink, 'deadlink'
|
||||
@nodes.quotelinks.push quotelink
|
||||
|
||||
if origin.file
|
||||
# Copy values, point to relevant elements.
|
||||
# See comments in Post's constructor.
|
||||
@file = {}
|
||||
for key, val of origin.file
|
||||
@file[key] = val
|
||||
file = $ '.file', post
|
||||
@file.info = file.firstElementChild
|
||||
@file.text = @file.info.firstElementChild
|
||||
@file.thumb = $ 'img[data-md5]', file
|
||||
|
||||
@isDead = true if origin.isDead
|
||||
@isClone = true
|
||||
index = origin.clones.push(@) - 1
|
||||
root.setAttribute 'data-clone', index
|
||||
|
||||
|
||||
Main =
|
||||
init: ->
|
||||
# flatten Config into Conf
|
||||
# and get saved or default values
|
||||
flatten = (parent, obj) ->
|
||||
if obj instanceof Array
|
||||
Conf[parent] = obj[0]
|
||||
else if typeof obj is 'object'
|
||||
for key, val of obj
|
||||
flatten key, val
|
||||
else # string or number
|
||||
Conf[parent] = obj
|
||||
return
|
||||
flatten null, Config
|
||||
for key, val of Conf
|
||||
Conf[key] = $.get key, val
|
||||
|
||||
pathname = location.pathname.split '/'
|
||||
g.BOARD = new Board pathname[1]
|
||||
if g.REPLY = pathname[2] is 'res'
|
||||
g.THREAD = +pathname[3]
|
||||
|
||||
switch location.hostname
|
||||
when 'boards.4chan.org'
|
||||
Main.initHeader()
|
||||
Main.initFeatures()
|
||||
when 'sys.4chan.org'
|
||||
return
|
||||
when 'images.4chan.org'
|
||||
$.ready ->
|
||||
if Conf['404 Redirect'] and d.title is '4chan - 404 Not Found'
|
||||
path = location.pathname.split '/'
|
||||
url = Redirect.image path[1], path[3]
|
||||
location.href = url if url
|
||||
return
|
||||
|
||||
initHeader: ->
|
||||
$.addStyle Main.css
|
||||
Main.header = $.el 'div',
|
||||
className: 'reply'
|
||||
innerHTML: '<div class=extra></div>'
|
||||
$.ready Main.initHeaderReady
|
||||
initHeaderReady: ->
|
||||
header = Main.header
|
||||
$.prepend d.body, header
|
||||
|
||||
if nav = $.id 'boardNavDesktop'
|
||||
header.id = nav.id
|
||||
$.prepend header, nav
|
||||
nav.id = nav.className = null
|
||||
nav.lastElementChild.hidden = true
|
||||
settings = $.el 'span',
|
||||
id: 'settings'
|
||||
innerHTML: '[<a href=javascript:;>Settings</a>]'
|
||||
$.on settings.firstElementChild, 'click', Main.settings
|
||||
$.add nav, settings
|
||||
$("a[href$='/#{g.BOARD}/']", nav)?.className = 'current'
|
||||
|
||||
$.addClass d.body, $.engine
|
||||
$.addClass d.body, 'fourchan_x'
|
||||
|
||||
# disable the mobile layout
|
||||
$('link[href*=mobile]', d.head)?.disabled = true
|
||||
$.id('boardNavDesktopFoot')?.hidden = true
|
||||
|
||||
initFeatures: ->
|
||||
if Conf['Disable 4chan\'s extension']
|
||||
settings = JSON.parse(localStorage.getItem '4chan-settings') or {}
|
||||
settings.disableAll = true
|
||||
localStorage.setItem '4chan-settings', JSON.stringify settings
|
||||
|
||||
if Conf['Resurrect Quotes']
|
||||
try
|
||||
Quotify.init()
|
||||
catch err
|
||||
# XXX handle error
|
||||
$.log err, 'Resurrect Quotes'
|
||||
|
||||
if Conf['Quote Inline']
|
||||
try
|
||||
QuoteInline.init()
|
||||
catch err
|
||||
# XXX handle error
|
||||
$.log err, 'Quote Inline'
|
||||
|
||||
if Conf['Quote Preview']
|
||||
try
|
||||
QuotePreview.init()
|
||||
catch err
|
||||
# XXX handle error
|
||||
$.log err, 'Quote Preview'
|
||||
|
||||
if Conf['Quote Backlinks']
|
||||
try
|
||||
QuoteBacklink.init()
|
||||
catch err
|
||||
# XXX handle error
|
||||
$.log err, 'Quote Backlinks'
|
||||
|
||||
if Conf['Indicate OP Quotes']
|
||||
try
|
||||
QuoteOP.init()
|
||||
catch err
|
||||
# XXX handle error
|
||||
$.log err, 'Indicate OP Quotes'
|
||||
|
||||
if Conf['Indicate Cross-thread Quotes']
|
||||
try
|
||||
QuoteCT.init()
|
||||
catch err
|
||||
# XXX handle error
|
||||
$.log err, 'Indicate Cross-thread Quotes'
|
||||
|
||||
if Conf['Time Formatting']
|
||||
try
|
||||
Time.init()
|
||||
catch err
|
||||
# XXX handle error
|
||||
$.log err, 'Time Formatting'
|
||||
|
||||
if Conf['File Info Formatting']
|
||||
try
|
||||
FileInfo.init()
|
||||
catch err
|
||||
# XXX handle error
|
||||
$.log err, 'File Info Formatting'
|
||||
|
||||
if Conf['Sauce']
|
||||
try
|
||||
Sauce.init()
|
||||
catch err
|
||||
# XXX handle error
|
||||
$.log err, 'Sauce'
|
||||
|
||||
if Conf['Reveal Spoilers']
|
||||
try
|
||||
RevealSpoilers.init()
|
||||
catch err
|
||||
# XXX handle error
|
||||
$.log err, 'Reveal Spoilers'
|
||||
|
||||
if Conf['Auto-GIF']
|
||||
try
|
||||
AutoGIF.init()
|
||||
catch err
|
||||
# XXX handle error
|
||||
$.log err, 'Auto-GIF'
|
||||
|
||||
if Conf['Image Hover']
|
||||
try
|
||||
ImageHover.init()
|
||||
catch err
|
||||
# XXX handle error
|
||||
$.log err, 'Image Hover'
|
||||
|
||||
if Conf['Thread Updater']
|
||||
try
|
||||
ThreadUpdater.init()
|
||||
catch err
|
||||
# XXX handle error
|
||||
$.log err, 'Thread Updater'
|
||||
|
||||
$.ready Main.initFeaturesReady
|
||||
initFeaturesReady: ->
|
||||
if d.title is '4chan - 404 Not Found'
|
||||
if Conf['404 Redirect'] and g.REPLY
|
||||
location.href = Redirect.thread g.BOARD, g.THREAD, location.hash
|
||||
return
|
||||
|
||||
return unless $.id 'navtopright'
|
||||
|
||||
threads = []
|
||||
posts = []
|
||||
|
||||
for boardChild in $('.board').children
|
||||
continue unless $.hasClass boardChild, 'thread'
|
||||
thread = new Thread boardChild.id[1..], g.BOARD
|
||||
threads.push thread
|
||||
for threadChild in boardChild.children
|
||||
continue unless $.hasClass threadChild, 'postContainer'
|
||||
try
|
||||
posts.push new Post threadChild, thread, g.BOARD
|
||||
catch err
|
||||
# Skip posts that we failed to parse.
|
||||
# XXX handle error
|
||||
# Post parser crashed for post No.#{threadChild.id[2..]}
|
||||
$.log threadChild, err
|
||||
|
||||
Main.callbackNodes Thread, threads, true
|
||||
Main.callbackNodes Post, posts, true
|
||||
|
||||
callbackNodes: (klass, nodes, notify) ->
|
||||
# get the nodes' length only once
|
||||
len = nodes.length
|
||||
for callback in klass::callbacks
|
||||
try
|
||||
for i in [0...len]
|
||||
callback.cb.call nodes[i]
|
||||
catch err
|
||||
# XXX handle error if notify
|
||||
$.log callback.name, 'crashed. error:', err.message, nodes[i], err
|
||||
return
|
||||
|
||||
settings: ->
|
||||
alert 'Here be settings'
|
||||
|
||||
css: """<%= grunt.file.read('css/style.css') %>"""
|
||||
Loading…
x
Reference in New Issue
Block a user