Merge remote-tracking branch 'mayhem/v3' into v3
Conflicts: .gitattributes 4chan-X.meta.js 4chan-X.user.js Gruntfile.js lib/$.coffee src/config.coffee src/features.coffee src/globals.coffee
@ -3,10 +3,10 @@
|
||||
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. Report precise steps to reproduce the problem.
|
||||
2. Report console errors, if any.
|
||||
3. Report browser version.
|
||||
4. Include your exported settings.
|
||||
1. Precise steps to reproduce the problem.
|
||||
2. Console errors, if any.
|
||||
3. Browser version.
|
||||
4. Your exported settings.
|
||||
|
||||
Open your console with:
|
||||
- `Ctrl + Shift + J` on Chrome.
|
||||
|
||||
124
Gruntfile.coffee
@ -23,31 +23,18 @@ module.exports = (grunt) ->
|
||||
]
|
||||
dest: 'tmp/script.coffee'
|
||||
|
||||
manifest:
|
||||
options:
|
||||
process:
|
||||
data: pkg
|
||||
src: 'src/manifest.json',
|
||||
dest: 'builds/crx/manifest.json'
|
||||
|
||||
metadata:
|
||||
options:
|
||||
process:
|
||||
data: pkg
|
||||
src: 'src/metadata.js',
|
||||
dest: '<%= pkg.name %>.meta.js'
|
||||
|
||||
crx:
|
||||
options:
|
||||
process:
|
||||
data: pkg
|
||||
src: [
|
||||
'src/banner.js'
|
||||
'tmp/script.js'
|
||||
]
|
||||
dest: 'builds/crx/script.js'
|
||||
files:
|
||||
'builds/crx/manifest.json': 'src/manifest.json'
|
||||
'builds/crx/script.js': [
|
||||
'src/banner.js'
|
||||
'tmp/script.js'
|
||||
]
|
||||
|
||||
userscript:
|
||||
userjs:
|
||||
options:
|
||||
process:
|
||||
data: pkg
|
||||
@ -56,13 +43,27 @@ module.exports = (grunt) ->
|
||||
'src/banner.js'
|
||||
'tmp/script.js'
|
||||
]
|
||||
dest: '<%= pkg.name %>.user.js'
|
||||
|
||||
userjs:
|
||||
# Lazily copy the userscript
|
||||
src: '<%= pkg.name %>.user.js'
|
||||
dest: 'builds/<%= pkg.name %>.js'
|
||||
|
||||
userscript:
|
||||
options:
|
||||
process:
|
||||
data: pkg
|
||||
files:
|
||||
'builds/<%= pkg.name %>.meta.js': 'src/metadata.js'
|
||||
'builds/<%= pkg.name %>.user.js': [
|
||||
'src/metadata.js'
|
||||
'src/banner.js'
|
||||
'tmp/script.js'
|
||||
]
|
||||
|
||||
copy:
|
||||
crx:
|
||||
src: 'img/*.png'
|
||||
dest: 'builds/crx/'
|
||||
expand: true
|
||||
flatten: true
|
||||
|
||||
coffee:
|
||||
script:
|
||||
src: 'tmp/script.coffee'
|
||||
@ -89,35 +90,73 @@ module.exports = (grunt) ->
|
||||
options:
|
||||
interrupt: true
|
||||
files: [
|
||||
'Gruntfile.js'
|
||||
'Gruntfile.coffee'
|
||||
'package.json'
|
||||
'lib/**/*.coffee'
|
||||
'src/**/*.coffee'
|
||||
'src/**/*.js'
|
||||
'css/**/*.css'
|
||||
'img/*'
|
||||
'lib/**/*'
|
||||
'src/**/*'
|
||||
'css/**/*'
|
||||
'img/**/*'
|
||||
]
|
||||
tasks: 'default'
|
||||
tasks: 'build'
|
||||
|
||||
compress:
|
||||
crx:
|
||||
options:
|
||||
archive: 'builds/4chan-X.zip'
|
||||
level: 9
|
||||
pretty: true
|
||||
expand: true
|
||||
cwd: 'builds/crx/'
|
||||
src: '**'
|
||||
|
||||
clean:
|
||||
tmp: 'tmp'
|
||||
builds: 'builds'
|
||||
tmp: 'tmp'
|
||||
|
||||
grunt.loadNpmTasks 'grunt-bump'
|
||||
grunt.loadNpmTasks 'grunt-contrib-clean'
|
||||
grunt.loadNpmTasks 'grunt-contrib-coffee'
|
||||
grunt.loadNpmTasks 'grunt-contrib-compress'
|
||||
grunt.loadNpmTasks 'grunt-contrib-concat'
|
||||
grunt.loadNpmTasks 'grunt-contrib-copy'
|
||||
grunt.loadNpmTasks 'grunt-contrib-watch'
|
||||
grunt.loadNpmTasks 'grunt-exec'
|
||||
|
||||
grunt.registerTask 'default', [
|
||||
'concat:coffee',
|
||||
'coffee:script',
|
||||
'concat:manifest',
|
||||
'concat:crx',
|
||||
'concat:userscript',
|
||||
'concat:userjs',
|
||||
'concat:metadata',
|
||||
'clean'
|
||||
grunt.registerTask 'default', ['build']
|
||||
|
||||
grunt.registerTask 'set-build', 'Set the build type variable', (type) ->
|
||||
pkg.type = type;
|
||||
grunt.log.ok 'pkg.type = %s', type
|
||||
|
||||
grunt.registerTask 'build', [
|
||||
'build-crx'
|
||||
'build-userjs'
|
||||
'build-userscript'
|
||||
]
|
||||
|
||||
grunt.registerTask 'build-crx', [
|
||||
'set-build:crx'
|
||||
'concat:coffee'
|
||||
'coffee:script'
|
||||
'concat:crx'
|
||||
'copy:crx'
|
||||
'clean:tmp'
|
||||
]
|
||||
|
||||
grunt.registerTask 'build-userjs', [
|
||||
'set-build:userjs'
|
||||
'concat:coffee'
|
||||
'coffee:script'
|
||||
'concat:userjs'
|
||||
'clean:tmp'
|
||||
]
|
||||
|
||||
grunt.registerTask 'build-userscript', [
|
||||
'set-build:userscript'
|
||||
'concat:coffee'
|
||||
'coffee:script'
|
||||
'concat:userscript'
|
||||
'clean:tmp'
|
||||
]
|
||||
|
||||
grunt.registerTask 'release', [
|
||||
@ -140,6 +179,7 @@ module.exports = (grunt) ->
|
||||
'bump:major'
|
||||
'updcl:1'
|
||||
]
|
||||
|
||||
grunt.registerTask 'updcl', 'Update the changelog', (i) ->
|
||||
# Update the `pkg` object with the new version.
|
||||
pkg = grunt.file.readJSON('package.json');
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
# 4chan X
|
||||
|
||||
Get it [here](http://mayhemydg.github.com/4chan-x/).
|
||||
Get it [here](https://4chan-x.just-believe.in/).
|
||||
|
||||
***
|
||||
|
||||
### [MIT License](/4chan-x/blob/master/LICENSE)
|
||||
### [Contribute](/4chan-x/blob/master/CONTRIBUTING.md)
|
||||
### [MIT License](/LICENSE)
|
||||
### [Contribute](/CONTRIBUTING.md)
|
||||
|
||||
@ -60,7 +60,7 @@ a[href="javascript:;"] {
|
||||
#qp, #ihover,
|
||||
#updater, #thread-stats,
|
||||
#navlinks, #header,
|
||||
#qr, #watcher {
|
||||
#qr {
|
||||
position: fixed;
|
||||
}
|
||||
#overlay {
|
||||
@ -81,12 +81,15 @@ a[href="javascript:;"] {
|
||||
#qr {
|
||||
z-index: 30;
|
||||
}
|
||||
#watcher {
|
||||
#watcher:hover {
|
||||
z-index: 20;
|
||||
}
|
||||
#header {
|
||||
z-index: 10;
|
||||
}
|
||||
#watcher {
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.fourchan-x body {
|
||||
@ -104,8 +107,8 @@ a[href="javascript:;"] {
|
||||
display: flex;
|
||||
padding: 3px 4px 4px;
|
||||
position: relative;
|
||||
-webkit-transition: all .1s ease-in-out;
|
||||
transition: all .1s ease-in-out;
|
||||
-webkit-transition: all .1s .05s ease-in-out;
|
||||
transition: all .1s .05s ease-in-out;
|
||||
}
|
||||
#board-list {
|
||||
-webkit-flex: 1;
|
||||
@ -252,6 +255,9 @@ a[href="javascript:;"] {
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
.tab-selected {
|
||||
font-weight: 700;
|
||||
}
|
||||
.section-container {
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
@ -421,7 +427,7 @@ a[href="javascript:;"] {
|
||||
max-height: 300px;
|
||||
max-width: 500px;
|
||||
}
|
||||
.qphl {
|
||||
.qphl > .post {
|
||||
outline: 2px solid rgba(216, 94, 49, .7);
|
||||
}
|
||||
|
||||
@ -435,6 +441,9 @@ a[href="javascript:;"] {
|
||||
.expanding {
|
||||
opacity: .5;
|
||||
}
|
||||
.expanded-image {
|
||||
clear: both;
|
||||
}
|
||||
.expanded-image > .op > .file::after {
|
||||
content: '';
|
||||
clear: both;
|
||||
@ -736,6 +745,11 @@ a[href="javascript:;"] {
|
||||
#file-n-submit > #qr-file-spoiler {
|
||||
margin: 0 2px;
|
||||
}
|
||||
#file-n-submit input[type='submit'] {
|
||||
min-width: 40px;
|
||||
-webkit-order: 1;
|
||||
order: 1;
|
||||
}
|
||||
#qr input[type='file'] {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
|
||||
|
Before Width: | Height: | Size: 463 B After Width: | Height: | Size: 450 B |
|
Before Width: | Height: | Size: 463 B After Width: | Height: | Size: 450 B |
|
Before Width: | Height: | Size: 234 B After Width: | Height: | Size: 232 B |
|
Before Width: | Height: | Size: 280 B After Width: | Height: | Size: 232 B |
|
Before Width: | Height: | Size: 278 B After Width: | Height: | Size: 232 B |
|
Before Width: | Height: | Size: 331 B After Width: | Height: | Size: 274 B |
|
Before Width: | Height: | Size: 346 B After Width: | Height: | Size: 274 B |
|
Before Width: | Height: | Size: 346 B After Width: | Height: | Size: 270 B |
|
Before Width: | Height: | Size: 420 B After Width: | Height: | Size: 349 B |
|
Before Width: | Height: | Size: 452 B After Width: | Height: | Size: 349 B |
|
Before Width: | Height: | Size: 455 B After Width: | Height: | Size: 349 B |
BIN
img/icon128.png
Normal file
|
After Width: | Height: | Size: 196 B |
BIN
img/icon16.png
Normal file
|
After Width: | Height: | Size: 157 B |
BIN
img/icon48.png
Normal file
|
After Width: | Height: | Size: 204 B |
84
lib/$.coffee
@ -51,7 +51,7 @@ $.extend String::,
|
||||
$.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000)))
|
||||
|
||||
$.extend $,
|
||||
engine: /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase()
|
||||
engine: '<% if (type === 'crx') { %>webkit<% } else if (type === 'userjs') { %>presto<% } else { %>gecko<% } %>'
|
||||
id: (id) ->
|
||||
d.getElementById id
|
||||
ready: (fc) ->
|
||||
@ -226,19 +226,8 @@ $.extend $,
|
||||
globalEval: (code) ->
|
||||
script = $.el 'script',
|
||||
textContent: code
|
||||
$.add d.head, script
|
||||
$.add (d.head or doc), script
|
||||
$.rm script
|
||||
# http://mths.be/unsafewindow
|
||||
unsafeWindow:
|
||||
if window.opera # Opera
|
||||
window
|
||||
else if unsafeWindow? # Firefox
|
||||
unsafeWindow
|
||||
else # Chrome
|
||||
do ->
|
||||
p = d.createElement 'p'
|
||||
p.setAttribute 'onclick', 'return window'
|
||||
p.onclick()
|
||||
bytesToString: (size) ->
|
||||
unit = 0 # Bytes
|
||||
while size >= 1024
|
||||
@ -255,11 +244,30 @@ $.extend $,
|
||||
Math.round size
|
||||
"#{size} #{['B', 'KB', 'MB', 'GB'][unit]}"
|
||||
|
||||
if GM_deleteValue?
|
||||
<% if (type === 'crx') { %>
|
||||
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
|
||||
<% } else if (type === 'userjs') { %>
|
||||
do ->
|
||||
# http://www.opera.com/docs/userjs/specs/#scriptstorage
|
||||
# http://www.opera.com/docs/userjs/using/#securepages
|
||||
# The scriptStorage object is available only during
|
||||
# the main User JavaScript thread, being therefore
|
||||
# accessible only in the main body of the user script.
|
||||
# To access the storage object later, keep a reference
|
||||
# to the object.
|
||||
{scriptStorage} = opera
|
||||
$.delete = (name) ->
|
||||
GM_deleteValue g.NAMESPACE + name
|
||||
delete scriptStorage[g.NAMESPACE + name]
|
||||
$.get = (name, defaultValue) ->
|
||||
if value = GM_getValue g.NAMESPACE + name
|
||||
if value = scriptStorage[g.NAMESPACE + name]
|
||||
JSON.parse value
|
||||
else
|
||||
defaultValue
|
||||
@ -268,37 +276,19 @@ if GM_deleteValue?
|
||||
value = JSON.stringify value
|
||||
# for `storage` events
|
||||
localStorage.setItem name, value
|
||||
GM_setValue name, value
|
||||
else if window.opera
|
||||
do ->
|
||||
# http://www.opera.com/docs/userjs/specs/#scriptstorage
|
||||
# http://www.opera.com/docs/userjs/using/#securepages
|
||||
# >The scriptStorage object is available only during
|
||||
# the main User JavaScript thread, being therefore
|
||||
# accessible only in the main body of the user script.
|
||||
# To access the storage object later, keep a reference
|
||||
# to the object.
|
||||
{scriptStorage} = opera
|
||||
$.delete = (name) ->
|
||||
delete scriptStorage[g.NAMESPACE + name]
|
||||
$.get = (name, defaultValue) ->
|
||||
if value = 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
|
||||
scriptStorage[name] = value
|
||||
else
|
||||
$.delete = (name) ->
|
||||
localStorage.removeItem g.NAMESPACE + name
|
||||
$.get = (name, defaultValue) ->
|
||||
if value = localStorage.getItem g.NAMESPACE + name
|
||||
scriptStorage[name] = value
|
||||
<% } else { %>
|
||||
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) ->
|
||||
localStorage.setItem g.NAMESPACE + name, JSON.stringify value
|
||||
set: (name, value) ->
|
||||
name = g.NAMESPACE + name
|
||||
value = JSON.stringify value
|
||||
# for `storage` events
|
||||
localStorage.setItem name, value
|
||||
GM_setValue name, value
|
||||
<% } %>
|
||||
|
||||
12
package.json
@ -5,7 +5,7 @@
|
||||
"meta": {
|
||||
"name": "4chan X Beta",
|
||||
"repo": "https://github.com/MayhemYDG/4chan-x/",
|
||||
"page": "http://mayhemydg.github.com/4chan-x/",
|
||||
"page": "https://4chan-x.just-believe.in/",
|
||||
"mainBranch": "v3",
|
||||
"matches": [
|
||||
"*://api.4chan.org/*",
|
||||
@ -15,12 +15,14 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "~0.4.0",
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-bump": "~0.0.0",
|
||||
"grunt-contrib-clean": "~0.4.0",
|
||||
"grunt-contrib-coffee": "~0.6.2",
|
||||
"grunt-contrib-concat": "~0.1.0",
|
||||
"grunt-contrib-watch": "~0.3.0",
|
||||
"grunt-contrib-coffee": "~0.6.4",
|
||||
"grunt-contrib-compress": "~0.4.5",
|
||||
"grunt-contrib-concat": "~0.1.3",
|
||||
"grunt-contrib-copy": "~0.4.0",
|
||||
"grunt-contrib-watch": "~0.3.1",
|
||||
"grunt-exec": "~0.4.0"
|
||||
},
|
||||
"repository": {
|
||||
|
||||
@ -15,7 +15,7 @@ Config =
|
||||
]
|
||||
'Custom Board Navigation': [
|
||||
true
|
||||
'Disable this to always display the full board list.'
|
||||
'Show custom links instead of the full board list.'
|
||||
]
|
||||
'404 Redirect': [
|
||||
true
|
||||
@ -27,7 +27,7 @@ Config =
|
||||
]
|
||||
'Time Formatting': [
|
||||
true
|
||||
'Localize and format timestamps arbitrarily.'
|
||||
'Localize and format timestamps.'
|
||||
]
|
||||
'Relative Post Dates': [
|
||||
false
|
||||
@ -39,19 +39,15 @@ Config =
|
||||
]
|
||||
'Comment Expansion': [
|
||||
true
|
||||
'Can expand too long comments.'
|
||||
'Add buttons to expand long comments.'
|
||||
]
|
||||
'Thread Expansion': [
|
||||
true
|
||||
'Can expand threads to view all replies.'
|
||||
'Add buttons to expand threads.'
|
||||
]
|
||||
'Index Navigation': [
|
||||
false
|
||||
'Navigate to previous / next thread.'
|
||||
]
|
||||
'Custom CSS': [
|
||||
false
|
||||
'Apply custom CSS to 4chan.'
|
||||
'Add buttons to navigate between threads.'
|
||||
]
|
||||
'Check for Updates': [
|
||||
true
|
||||
@ -75,7 +71,7 @@ Config =
|
||||
'Filtering':
|
||||
'Anonymize': [
|
||||
false
|
||||
'Turn everyone Anonymous.'
|
||||
'Make everyone Anonymous.'
|
||||
]
|
||||
'Filter': [
|
||||
true
|
||||
@ -87,19 +83,19 @@ Config =
|
||||
]
|
||||
'Thread Hiding': [
|
||||
true
|
||||
'Hide entire threads.'
|
||||
'Add buttons to hide entire threads.'
|
||||
]
|
||||
'Reply Hiding': [
|
||||
true
|
||||
'Hide single replies.'
|
||||
'Add buttons to hide single replies.'
|
||||
]
|
||||
'Hiding Buttons': [
|
||||
true
|
||||
'Make buttons to hide threads / replies, in addition to menu links.'
|
||||
'Add buttons to hide threads / replies, in addition to menu links.'
|
||||
]
|
||||
'Stubs': [
|
||||
true
|
||||
'Make stubs of hidden threads / replies.'
|
||||
'Show stubs of hidden threads / replies.'
|
||||
]
|
||||
|
||||
'Images':
|
||||
@ -135,8 +131,16 @@ Config =
|
||||
'Menu':
|
||||
'Menu': [
|
||||
true
|
||||
'Add a drop-down menu in posts.'
|
||||
'Add a drop-down menu to posts.'
|
||||
]
|
||||
'Thread Hiding Link': [
|
||||
true
|
||||
'Add a link to hide entire threads.'
|
||||
]
|
||||
'Reply Hiding Link': [
|
||||
true
|
||||
'Add a link to hide single replies.'
|
||||
]
|
||||
'Report Link': [
|
||||
true
|
||||
'Add a report link to the menu.'
|
||||
@ -222,7 +226,7 @@ Config =
|
||||
'Hide the normal post form.'
|
||||
]
|
||||
|
||||
'Quote links':
|
||||
'Quote Links':
|
||||
'Quote Backlinks': [
|
||||
true
|
||||
'Add quote backlinks.'
|
||||
@ -331,21 +335,23 @@ Config =
|
||||
MD5: ''
|
||||
|
||||
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
|
||||
http://iqdb.org/?url=%TURL
|
||||
https://www.google.com/searchbyimage?image_url=%TURL
|
||||
#//tineye.com/search?url=%TURL
|
||||
#http://saucenao.com/search.php?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
|
||||
#http://imgur.com/upload?url=%URL;text:Upload to imgur
|
||||
#http://ompldr.org/upload?url1=%URL;text:Upload to ompldr
|
||||
# "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/
|
||||
"""
|
||||
|
||||
'Custom CSS': false
|
||||
|
||||
'Header auto-hide': false
|
||||
|
||||
'Header catalog links': false
|
||||
|
||||
@ -31,7 +31,7 @@ Header =
|
||||
className: 'hide-board-list-button brackets-wrap'
|
||||
innerHTML: '<a href=javascript:;> - </a>'
|
||||
$.on btn, 'click', Header.toggleBoardList
|
||||
$.prepend fullBoardList, btn
|
||||
$.add fullBoardList, btn
|
||||
else
|
||||
$.rm $ '#custom-board-list', nav
|
||||
fullBoardList.hidden = false
|
||||
@ -40,8 +40,8 @@ Header =
|
||||
list = $ '#custom-board-list', Header.nav
|
||||
list.innerHTML = null
|
||||
return unless text
|
||||
as = $$('#full-board-list a', Header.nav)
|
||||
nodes = text.match(/[\w@]+(-(all|title|full|text:"[^"]+"))?|[^\w@]+/g).map (t) ->
|
||||
as = $$('#full-board-list a', Header.nav)[0...-2] # ignore the Settings and Home links
|
||||
nodes = text.match(/[\w@]+(-(all|title|full|index|catalog|text:"[^"]+"))*|[^\w@]+/g).map (t) ->
|
||||
if /^[^\w@]/.test t
|
||||
return $.tn t
|
||||
if t is 'toggle-all'
|
||||
@ -58,12 +58,17 @@ Header =
|
||||
for a in as
|
||||
if a.textContent is board
|
||||
a = a.cloneNode true
|
||||
if /-title$/.test t
|
||||
if /-title/.test t
|
||||
a.textContent = a.title
|
||||
else if /-full$/.test t
|
||||
else if /-full/.test t
|
||||
a.textContent = "/#{board}/ - #{a.title}"
|
||||
else if m = t.match /-text:"(.+)"$/
|
||||
a.textContent = m[1]
|
||||
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
|
||||
@ -224,17 +229,15 @@ Settings =
|
||||
links = []
|
||||
for section in Settings.sections
|
||||
link = $.el 'a',
|
||||
className: "tab-#{section.hyphenatedTitle}"
|
||||
textContent: section.title
|
||||
href: 'javascript:;'
|
||||
$.on link, 'click', Settings.openSection.bind section
|
||||
links.push link, $.tn ' | '
|
||||
sectionToOpen = link if section.title is openSection
|
||||
links.pop()
|
||||
if sectionToOpen
|
||||
sectionToOpen.click()
|
||||
else
|
||||
links[0].click()
|
||||
$.add $('.sections-list', overlay), links
|
||||
(if sectionToOpen then sectionToOpen else links[0]).click()
|
||||
|
||||
$.on $('.close', overlay), 'click', Settings.close
|
||||
$.on overlay, 'click', Settings.close
|
||||
@ -254,11 +257,15 @@ Settings =
|
||||
addSection: (title, open) ->
|
||||
if typeof title isnt 'string'
|
||||
{title, open} = title.detail
|
||||
Settings.sections.push {title, open}
|
||||
hyphenatedTitle = title.toLowerCase().replace /\s+/g, '-'
|
||||
Settings.sections.push {title, hyphenatedTitle, open}
|
||||
openSection: ->
|
||||
if selected = $ '.tab-selected', Settings.dialog
|
||||
$.rmClass selected, 'tab-selected'
|
||||
$.addClass $(".tab-#{@hyphenatedTitle}", Settings.dialog), 'tab-selected'
|
||||
section = $ 'section', Settings.dialog
|
||||
section.innerHTML = null
|
||||
section.className = "section-#{@title.toLowerCase().replace /\s+/g, '-'}"
|
||||
section.className = "section-#{@hyphenatedTitle}"
|
||||
@open section, g
|
||||
section.scrollTop = 0
|
||||
|
||||
@ -283,7 +290,7 @@ Settings =
|
||||
description = arr[1]
|
||||
div = $.el 'div',
|
||||
innerHTML: "<label><input type=checkbox name=\"#{key}\" #{checked}>#{key}</label><span class=description>: #{description}</span>"
|
||||
$.on $('input', div), 'click', $.cb.checked
|
||||
$.on $('input', div), 'change', $.cb.checked
|
||||
$.add fs, div
|
||||
$.add section, fs
|
||||
|
||||
@ -311,7 +318,7 @@ Settings =
|
||||
className: 'warning'
|
||||
textContent: 'Save me!'
|
||||
download: "<%= meta.name %> v#{g.VERSION}-#{now}.json"
|
||||
href: "data:application/json;base64,#{btoa unescape encodeURIComponent JSON.stringify data}"
|
||||
href: "data:application/json;base64,#{btoa unescape encodeURIComponent JSON.stringify data, null, 2}"
|
||||
target: '_blank'
|
||||
if $.engine isnt 'gecko'
|
||||
a.click()
|
||||
@ -451,7 +458,7 @@ Settings =
|
||||
$.add div, ta
|
||||
return
|
||||
div.innerHTML = """
|
||||
<div class=warning #{if Conf['Sauce'] then 'hidden' else ''}><code>Filter</code> is disabled.</div>
|
||||
<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>
|
||||
@ -509,6 +516,9 @@ Settings =
|
||||
<div>Title link: <code>board-title</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>
|
||||
|
||||
@ -551,9 +561,9 @@ Settings =
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Custom CSS <span class=warning #{if Conf['Custom CSS'] then 'hidden' else ''}>is disabled.</span></legend>
|
||||
<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></textarea>
|
||||
<textarea name=usercss class=field spellcheck=false #{if Conf['Custom CSS'] then '' else 'disabled'}></textarea>
|
||||
</fieldset>
|
||||
"""
|
||||
for name in ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss']
|
||||
@ -567,6 +577,7 @@ Settings =
|
||||
unless 'usercss' is name
|
||||
$.on input, event, Settings[name]
|
||||
Settings[name].call input
|
||||
$.on $('input[name="Custom CSS"]', section), 'change', Settings.togglecss
|
||||
$.on $.id('apply-css'), 'click', Settings.usercss
|
||||
boardnav: ->
|
||||
Header.generateBoardList @value
|
||||
@ -591,12 +602,20 @@ Settings =
|
||||
favicon: ->
|
||||
Favicon.switch()
|
||||
Unread.update() if g.VIEW is 'thread' and Conf['Unread Tab Icon']
|
||||
@nextElementSibling.innerHTML = "<img src=#{Favicon.unreadSFW}> <img src=#{Favicon.unreadNSFW}> <img src=#{Favicon.unreadDead}>"
|
||||
usercss: ->
|
||||
if Conf['Custom CSS']
|
||||
CustomCSS.update()
|
||||
else
|
||||
@nextElementSibling.innerHTML = """
|
||||
<img src=#{Favicon.default}>
|
||||
<img src=#{Favicon.unreadSFW}>
|
||||
<img src=#{Favicon.unreadNSFW}>
|
||||
<img src=#{Favicon.unreadDead}>
|
||||
"""
|
||||
togglecss: ->
|
||||
if $('textarea', @parentNode.parentNode).disabled = !@checked
|
||||
CustomCSS.rmStyle()
|
||||
else
|
||||
CustomCSS.addStyle()
|
||||
$.cb.checked.call @
|
||||
usercss: ->
|
||||
CustomCSS.update()
|
||||
|
||||
keybinds: (section) ->
|
||||
section.innerHTML = """
|
||||
@ -632,33 +651,40 @@ Fourchan =
|
||||
|
||||
board = g.BOARD.ID
|
||||
if board is 'g'
|
||||
$.globalEval """
|
||||
window.addEventListener('prettyprint', function(e) {
|
||||
var pre = e.detail;
|
||||
pre.innerHTML = prettyPrintOne(pre.innerHTML);
|
||||
}, false);
|
||||
"""
|
||||
Post::callbacks.push
|
||||
name: 'Parse /g/ code'
|
||||
cb: @code
|
||||
if board is 'sci'
|
||||
# https://github.com/MayhemYDG/4chan-x/issues/645#issuecomment-13704562
|
||||
$.globalEval """
|
||||
window.addEventListener('jsmath', function(e) {
|
||||
if (jsMath.loaded) {
|
||||
// process one post
|
||||
jsMath.ProcessBeforeShowing(e.detail);
|
||||
} else {
|
||||
// load jsMath and process whole document
|
||||
jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]);
|
||||
jsMath.Autoload.LoadJsMath();
|
||||
}
|
||||
}, false);
|
||||
"""
|
||||
Post::callbacks.push
|
||||
name: 'Parse /sci/ math'
|
||||
cb: @math
|
||||
code: ->
|
||||
return if @isClone
|
||||
for pre in $$ '.prettyprint', @nodes.comment
|
||||
pre.innerHTML = $.unsafeWindow.prettyPrintOne pre.innerHTML
|
||||
$.event 'prettyprint', pre, window
|
||||
return
|
||||
math: ->
|
||||
return if @isClone or !$ '.math', @nodes.comment
|
||||
# https://github.com/MayhemYDG/4chan-x/issues/645#issuecomment-13704562
|
||||
{jsMath} = $.unsafeWindow
|
||||
if jsMath
|
||||
if jsMath.loaded
|
||||
# process one post
|
||||
jsMath.ProcessBeforeShowing @nodes.post
|
||||
else
|
||||
# load jsMath and process whole document
|
||||
# Yes this requires to be globalEval'd, don't ask me why.
|
||||
$.globalEval """
|
||||
jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]);
|
||||
jsMath.Autoload.LoadJsMath();
|
||||
"""
|
||||
$.event 'jsmath', @nodes.post, window
|
||||
parseThread: (threadID, offset, limit) ->
|
||||
# Fix /sci/
|
||||
# Fix /g/
|
||||
@ -959,7 +985,7 @@ Filter =
|
||||
|
||||
ThreadHiding =
|
||||
init: ->
|
||||
return if g.VIEW isnt 'index' or !Conf['Thread Hiding']
|
||||
return if g.VIEW isnt 'index' or !Conf['Thread Hiding'] and !Conf['Thread Hiding Link']
|
||||
|
||||
Misc.clearThreads "hiddenThreads.#{g.BOARD}"
|
||||
@getHiddenThreads()
|
||||
@ -971,7 +997,7 @@ ThreadHiding =
|
||||
node: ->
|
||||
if data = ThreadHiding.hiddenThreads.threads[@]
|
||||
ThreadHiding.hide @, data.makeStub
|
||||
return unless Conf['Hiding Buttons']
|
||||
return unless Conf['Thread Hiding']
|
||||
$.prepend @OP.nodes.root, ThreadHiding.makeButton @, 'hide'
|
||||
|
||||
getHiddenThreads: ->
|
||||
@ -999,7 +1025,7 @@ ThreadHiding =
|
||||
|
||||
menu:
|
||||
init: ->
|
||||
return if g.VIEW isnt 'index' or !Conf['Menu'] or !Conf['Thread Hiding']
|
||||
return if g.VIEW isnt 'index' or !Conf['Menu'] or !Conf['Thread Hiding Link']
|
||||
|
||||
div = $.el 'div',
|
||||
className: 'hide-thread-link'
|
||||
@ -1101,7 +1127,7 @@ ThreadHiding =
|
||||
|
||||
ReplyHiding =
|
||||
init: ->
|
||||
return if g.VIEW is 'catalog' or !Conf['Reply Hiding']
|
||||
return if g.VIEW is 'catalog' or !Conf['Reply Hiding'] and !Conf['Reply Hiding Link']
|
||||
|
||||
Misc.clearThreads "hiddenPosts.#{g.BOARD}"
|
||||
@getHiddenPosts()
|
||||
@ -1118,7 +1144,7 @@ ReplyHiding =
|
||||
else
|
||||
Recursive.apply ReplyHiding.hide, @, data.makeStub, true
|
||||
Recursive.add ReplyHiding.hide, @, data.makeStub, true
|
||||
return unless Conf['Hiding Buttons']
|
||||
return unless Conf['Reply Hiding']
|
||||
$.replace $('.sideArrows', @nodes.root), ReplyHiding.makeButton @, 'hide'
|
||||
|
||||
getHiddenPosts: ->
|
||||
@ -1126,7 +1152,7 @@ ReplyHiding =
|
||||
|
||||
menu:
|
||||
init: ->
|
||||
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Reply Hiding']
|
||||
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Reply Hiding Link']
|
||||
|
||||
# Hide
|
||||
div = $.el 'div',
|
||||
@ -1336,7 +1362,7 @@ Recursive =
|
||||
|
||||
QuoteStrikeThrough =
|
||||
init: ->
|
||||
return if g.VIEW is 'catalog' or !Conf['Reply Hiding'] and !Conf['Filter']
|
||||
return if g.VIEW is 'catalog' or !Conf['Reply Hiding'] and !Conf['Reply Hiding Link'] and !Conf['Filter']
|
||||
|
||||
Post::callbacks.push
|
||||
name: 'Strike-through Quotes'
|
||||
@ -1624,7 +1650,7 @@ Keybinds =
|
||||
when Conf['Open settings']
|
||||
Settings.open()
|
||||
when Conf['Close']
|
||||
if $.id 'settings'
|
||||
if $.id 'fourchanx-settings'
|
||||
Settings.close()
|
||||
else if (notifications = $$ '.notification').length
|
||||
for notification in notifications
|
||||
@ -1791,7 +1817,6 @@ Keybinds =
|
||||
|
||||
focus: (post) ->
|
||||
$.addClass post, 'highlight'
|
||||
$('a[title="Highlight this post"]', post).focus()
|
||||
|
||||
Nav =
|
||||
init: ->
|
||||
@ -2419,10 +2444,10 @@ Misc = # super semantic
|
||||
|
||||
return if data.lastChecked > Date.now() - 12 * $.HOUR
|
||||
|
||||
$.ajax "//api.4chan.org/#{g.BOARD}/catalog.json", onload: ->
|
||||
$.ajax "//api.4chan.org/#{g.BOARD}/threads.json", onload: ->
|
||||
threads = {}
|
||||
for obj in JSON.parse @response
|
||||
for thread in obj.threads
|
||||
for page in JSON.parse @response
|
||||
for thread in page.threads
|
||||
if thread.no of data.threads
|
||||
threads[thread.no] = data.threads[thread.no]
|
||||
unless Object.keys(threads).length
|
||||
@ -2630,7 +2655,7 @@ QuotePreview =
|
||||
# Remove the clone that's in the qp from the array.
|
||||
posts.pop()
|
||||
for post in posts
|
||||
$.addClass post.nodes.post, 'qphl'
|
||||
$.addClass post.nodes.root, 'qphl'
|
||||
|
||||
quoterID = $.x('ancestor::*[@id][1]', @).id.match(/\d+$/)[0]
|
||||
clone = Get.postFromRoot qp.firstChild
|
||||
@ -2651,7 +2676,7 @@ QuotePreview =
|
||||
|
||||
return unless Conf['Quote Highlighting']
|
||||
for post in [post].concat post.clones
|
||||
$.rmClass post.nodes.post, 'qphl'
|
||||
$.rmClass post.nodes.root, 'qphl'
|
||||
return
|
||||
|
||||
QuoteBacklink =
|
||||
@ -3055,14 +3080,15 @@ Sauce =
|
||||
createSauceLink: (link) ->
|
||||
link = link.replace /%(T?URL|MD5|board)/ig, (parameter) ->
|
||||
switch parameter
|
||||
when '%TURL', '%turl'
|
||||
"' + post.file.thumbURL + '"
|
||||
when '%URL', '%url'
|
||||
"' + post.file.URL + '"
|
||||
when '%MD5', '%md5'
|
||||
|
||||
when '%TURL'
|
||||
"' + encodeURIComponent(post.file.thumbURL) + '"
|
||||
when '%URL'
|
||||
"' + encodeURIComponent(post.file.URL) + '"
|
||||
when '%MD5'
|
||||
"' + encodeURIComponent(post.file.MD5) + '"
|
||||
when '%board'
|
||||
"' + post.board + '"
|
||||
"' + encodeURIComponent(post.board) + '"
|
||||
else
|
||||
parameter
|
||||
text = if m = link.match(/;text:(.+)$/) then m[1] else link.match(/(\w+)\.\w+\//)[1]
|
||||
@ -3171,7 +3197,7 @@ ImageExpand =
|
||||
# Scroll back to the thumbnail when contracting the image
|
||||
# to avoid being left miles away from the relevant post.
|
||||
postRect = post.nodes.root.getBoundingClientRect()
|
||||
headRect = Header.bar.getBoundingClientRect()
|
||||
headRect = Header.toggle.getBoundingClientRect()
|
||||
top = postRect.top - headRect.top - headRect.height - 2
|
||||
root = if $.engine is 'webkit'
|
||||
d.body
|
||||
@ -3222,7 +3248,10 @@ ImageExpand =
|
||||
post = Get.postFromNode @
|
||||
$.rm @
|
||||
delete post.file.fullImage
|
||||
unless $.hasClass post.file.thumb, 'expanding'
|
||||
# Images can error:
|
||||
# - before the image started loading.
|
||||
# - after the image started loading.
|
||||
unless $.hasClass(post.file.thumb, 'expanding') or $.hasClass post.nodes.root, 'expanded-image'
|
||||
# Don't try to re-expend if it was already contracted.
|
||||
return
|
||||
ImageExpand.contract post
|
||||
@ -3445,7 +3474,7 @@ ExpandThread =
|
||||
#goddamit moot
|
||||
num = if thread.isSticky
|
||||
1
|
||||
else switch g.BOARD
|
||||
else switch g.BOARD.ID
|
||||
# XXX boards config
|
||||
when 'b', 'vg', 'q' then 3
|
||||
when 't' then 1
|
||||
@ -3493,7 +3522,7 @@ ExpandThread =
|
||||
|
||||
# Enable 4chan features.
|
||||
if Conf['Enable 4chan\'s Extension']
|
||||
$.unsafeWindow.Parser.parseThread thread.ID, 1, nodes.length
|
||||
$.globalEval "Parser.parseThread(#{thread.ID}, 1, #{nodes.length})"
|
||||
else
|
||||
Fourchan.parseThread thread.ID, 1, nodes.length
|
||||
|
||||
@ -3505,7 +3534,10 @@ ThreadExcerpt =
|
||||
name: 'Thread Excerpt'
|
||||
cb: @node
|
||||
node: ->
|
||||
d.title = Get.threadExcerpt @
|
||||
d.title = if (excerpt = Get.threadExcerpt @).length > 80
|
||||
"#{excerpt[...77]}..."
|
||||
else
|
||||
excerpt
|
||||
|
||||
Unread =
|
||||
init: ->
|
||||
@ -3523,6 +3555,7 @@ Unread =
|
||||
Unread.lastReadPost = $.get("lastReadPosts.#{@board}", threads: {}).threads[@] or 0
|
||||
Unread.posts = []
|
||||
Unread.postsQuotingYou = []
|
||||
Unread.titleEl = $ 'title', d.head
|
||||
Unread.title = d.title
|
||||
posts = []
|
||||
for ID, post of @posts
|
||||
@ -3604,10 +3637,17 @@ Unread =
|
||||
count = Unread.posts.length
|
||||
|
||||
if Conf['Unread Count']
|
||||
d.title = if g.DEAD
|
||||
"(#{Unread.posts.length}) /#{g.BOARD}/ - 404"
|
||||
prefix = if count
|
||||
"(#{count})"
|
||||
else
|
||||
"(#{Unread.posts.length}) #{Unread.title}"
|
||||
''
|
||||
# XXX Chrome bug where it doesn't always update the tab title.
|
||||
# crbug.com/16650
|
||||
# crbug.com/124381
|
||||
Unread.titleEl.textContent = if g.DEAD
|
||||
"#{prefix} /#{g.BOARD}/ - 404"
|
||||
else
|
||||
"#{prefix} #{Unread.title}"
|
||||
|
||||
return unless Conf['Unread Tab Icon']
|
||||
|
||||
@ -3997,9 +4037,9 @@ ThreadUpdater =
|
||||
$.queueTask ->
|
||||
# Enable 4chan features.
|
||||
threadID = ThreadUpdater.thread.ID
|
||||
{length} = ThreadUpdater.root.children
|
||||
{length} = $$ '.thread > .postContainer', ThreadUpdater.root
|
||||
if Conf['Enable 4chan\'s Extension']
|
||||
$.unsafeWindow.Parser.parseThread threadID, -count
|
||||
$.globalEval "Parser.parseThread(#{threadID}, #{-count})"
|
||||
else
|
||||
Fourchan.parseThread threadID, length - count, length
|
||||
|
||||
@ -4038,6 +4078,7 @@ ThreadWatcher =
|
||||
$.delete 'AutoWatch'
|
||||
|
||||
ready: ->
|
||||
return unless Main.isThisPageLegit()
|
||||
ThreadWatcher.refresh()
|
||||
$.add d.body, ThreadWatcher.dialog
|
||||
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
<% if (type === 'userjs') { %>
|
||||
# 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 = {}
|
||||
c = console
|
||||
d = document
|
||||
doc = null
|
||||
g =
|
||||
doc = d.documentElement
|
||||
g =
|
||||
VERSION: '<%= version %>'
|
||||
NAMESPACE: '<%= meta.name %>.'
|
||||
boards: {}
|
||||
|
||||
@ -41,28 +41,28 @@ class Post
|
||||
backlinks: info.getElementsByClassName 'backlink'
|
||||
|
||||
@info = {}
|
||||
if subject = $ '.subject', info
|
||||
if subject = $ '.subject', info
|
||||
@nodes.subject = subject
|
||||
@info.subject = subject.textContent
|
||||
if name = $ '.name', info
|
||||
if name = $ '.name', info
|
||||
@nodes.name = name
|
||||
@info.name = name.textContent
|
||||
if email = $ '.useremail', info
|
||||
if email = $ '.useremail', info
|
||||
@nodes.email = email
|
||||
@info.email = decodeURIComponent email.href[7..]
|
||||
if tripcode = $ '.postertrip', info
|
||||
if tripcode = $ '.postertrip', info
|
||||
@nodes.tripcode = tripcode
|
||||
@info.tripcode = tripcode.textContent
|
||||
if uniqueID = $ '.posteruid', info
|
||||
if uniqueID = $ '.posteruid', info
|
||||
@nodes.uniqueID = uniqueID
|
||||
@info.uniqueID = uniqueID.firstElementChild.textContent
|
||||
if capcode = $ '.capcode', info
|
||||
if capcode = $ '.capcode.hand', info
|
||||
@nodes.capcode = capcode
|
||||
@info.capcode = capcode.textContent
|
||||
if flag = $ '.countryFlag', info
|
||||
@info.capcode = capcode.textContent.replace '## ', ''
|
||||
if flag = $ '.countryFlag', info
|
||||
@nodes.flag = flag
|
||||
@info.flag = flag.title
|
||||
if date = $ '.dateTime', info
|
||||
if date = $ '.dateTime', info
|
||||
@nodes.date = date
|
||||
@info.date = new Date date.dataset.utc * 1000
|
||||
if Conf['Quick Reply']
|
||||
@ -284,9 +284,6 @@ class Clone extends Post
|
||||
|
||||
Main =
|
||||
init: ->
|
||||
$.asap (-> d.documentElement), ->
|
||||
doc = d.documentElement
|
||||
|
||||
# flatten Config into Conf
|
||||
# and get saved or default values
|
||||
flatten = (parent, obj) ->
|
||||
@ -486,7 +483,9 @@ Main =
|
||||
Main.handleErrors errors if errors
|
||||
|
||||
addCallback: (e) ->
|
||||
obj = e.detail
|
||||
obj = e.detail
|
||||
unless typeof obj.callback.name is 'string'
|
||||
throw new Error "Invalid callback name: #{obj.callback.name}"
|
||||
Klass = if obj.type is 'Post'
|
||||
Post
|
||||
else
|
||||
@ -504,7 +503,7 @@ Main =
|
||||
return
|
||||
|
||||
div = $.el 'div',
|
||||
innerHTML: "#{errors.length} errors occured. [<a href=javascript:;>show</a>]"
|
||||
innerHTML: "#{errors.length} errors occurred. [<a href=javascript:;>show</a>]"
|
||||
$.on div.lastElementChild, 'click', ->
|
||||
if @textContent is 'show'
|
||||
@textContent = 'hide'
|
||||
@ -533,7 +532,9 @@ Main =
|
||||
isThisPageLegit: ->
|
||||
# 404 error page or similar.
|
||||
unless 'thisPageIsLegit' of Main
|
||||
Main.thisPageIsLegit = !$('link[href*="favicon-status.ico"]', d.head) and d.title isnt '4chan - Temporarily Offline'
|
||||
Main.thisPageIsLegit = location.hostname is 'boards.4chan.org' and
|
||||
!$('link[href*="favicon-status.ico"]', d.head) and
|
||||
d.title not in ['4chan - Temporarily Offline', '4chan - Error']
|
||||
Main.thisPageIsLegit
|
||||
|
||||
css: """
|
||||
|
||||
@ -3,6 +3,11 @@
|
||||
"version": "<%= version %>",
|
||||
"manifest_version": 2,
|
||||
"description": "<%= description %>",
|
||||
"icons": {
|
||||
"16": "icon16.png",
|
||||
"48": "icon48.png",
|
||||
"128": "icon128.png"
|
||||
},
|
||||
"content_scripts": [{
|
||||
"js": ["script.js"],
|
||||
"matches": <%= JSON.stringify(meta.matches) %>,
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
// @grant GM_deleteValue
|
||||
// @grant GM_openInTab
|
||||
// @run-at document-start
|
||||
// @updateURL <%= meta.repo %>raw/<%= meta.mainBranch %>/<%= name %>.meta.js
|
||||
// @downloadURL <%= meta.repo %>raw/<%= meta.mainBranch %>/<%= name %>.user.js
|
||||
// @icon data:image/gif;base64,<%= grunt.file.read('img/icon.gif', {encoding: 'base64'}) %>
|
||||
// @updateURL <%= meta.page %>builds/<%= name %>.meta.js
|
||||
// @downloadURL <%= meta.page %>builds/<%= name %>.user.js
|
||||
// @icon data:image/png;base64,<%= grunt.file.read('img/icon48.png', {encoding: 'base64'}) %>
|
||||
// ==/UserScript==
|
||||
|
||||
@ -486,6 +486,7 @@ QR =
|
||||
# so we generate thumbnails `s` times bigger then expected
|
||||
# to avoid crappy resized quality.
|
||||
s = 90*2
|
||||
s *= 3 if @file.type is 'image/gif' # let them animate
|
||||
{height, width} = img
|
||||
if height < s or width < s
|
||||
@URL = fileURL if window.URL
|
||||
@ -581,6 +582,12 @@ QR =
|
||||
return unless @isEnabled = !!$.id 'captchaFormPart'
|
||||
$.asap (-> $.id 'recaptcha_challenge_field_holder'), @ready.bind @
|
||||
ready: ->
|
||||
setLifetime = (e) => @lifetime = e.detail
|
||||
$.on window, 'captcha:timeout', setLifetime
|
||||
$.globalEval 'window.dispatchEvent(new CustomEvent("captcha:timeout", {detail: RecaptchaState.timeout}))'
|
||||
$.off window, 'captcha:timeout', setLifetime
|
||||
c.log @lifetime
|
||||
|
||||
imgContainer = $.el 'div',
|
||||
className: 'captcha-img'
|
||||
title: 'Reload'
|
||||
@ -648,7 +655,7 @@ QR =
|
||||
load: ->
|
||||
return unless @nodes.challenge.firstChild
|
||||
# -1 minute to give upload some time.
|
||||
@timeout = Date.now() + $.unsafeWindow.RecaptchaState.timeout * $.SECOND - $.MINUTE
|
||||
@timeout = Date.now() + @lifetime * $.SECOND - $.MINUTE
|
||||
challenge = @nodes.challenge.firstChild.value
|
||||
@nodes.img.alt = challenge
|
||||
@nodes.img.src = "//www.google.com/recaptcha/api/image?c=#{challenge}"
|
||||
@ -666,7 +673,7 @@ QR =
|
||||
@nodes.input.alt = count # For XTRM RICE.
|
||||
reload: (focus) ->
|
||||
# the 't' argument prevents the input from being focused
|
||||
$.unsafeWindow.Recaptcha.reload 't'
|
||||
$.globalEval 'Recaptcha.reload("t")'
|
||||
# Focus if we meant to.
|
||||
@nodes.input.focus() if focus
|
||||
keydown: (e) ->
|
||||
@ -704,14 +711,14 @@ QR =
|
||||
<span id=char-count></span>
|
||||
</div>
|
||||
<div id=file-n-submit>
|
||||
<input type=submit>
|
||||
<input id=qr-file-button type=button value='Choose files'>
|
||||
<span id=qr-filename-container>
|
||||
<span id=qr-no-file>No selected file</span>
|
||||
<span id=qr-filename></span>
|
||||
</span>
|
||||
<a id=qr-filerm href=javascript:; title='Remove file' tabindex=-1>×</a>
|
||||
<input type=checkbox id=qr-file-spoiler title='Spoiler image' tabindex=-1>
|
||||
<input type=submit>
|
||||
<a id=qr-filerm href=javascript:; title='Remove file'>×</a>
|
||||
<input type=checkbox id=qr-file-spoiler title='Spoiler image'>
|
||||
</div>
|
||||
<input type=file multiple>
|
||||
</form>
|
||||
@ -877,12 +884,14 @@ QR =
|
||||
callbacks =
|
||||
onload: QR.response
|
||||
onerror: ->
|
||||
# Connection error, or
|
||||
# www.4chan.org/banned
|
||||
delete QR.req
|
||||
post.unlock()
|
||||
QR.cooldown.auto = false
|
||||
QR.status()
|
||||
# Connection error.
|
||||
QR.error 'Network error.'
|
||||
QR.error $.el 'span',
|
||||
innerHTML: 'Connection error. You may have been <a href=//www.4chan.org/banned target=_blank>banned</a>.'
|
||||
opts =
|
||||
form: $.formData postData
|
||||
upCallbacks:
|
||||
@ -966,6 +975,7 @@ QR =
|
||||
[_, threadID, postID] = h1.nextSibling.textContent.match /thread:(\d+),no:(\d+)/
|
||||
postID = +postID
|
||||
threadID = +threadID or postID
|
||||
isReply = threadID isnt postID
|
||||
|
||||
(QR.yourPosts.threads[threadID] or= []).push postID
|
||||
$.set "yourPosts.#{g.BOARD}", QR.yourPosts
|
||||
@ -980,18 +990,15 @@ QR =
|
||||
}, QR.nodes.el
|
||||
|
||||
# Enable auto-posting if we have stuff to post, disable it otherwise.
|
||||
QR.cooldown.auto = QR.posts.length > 1
|
||||
QR.cooldown.auto = QR.posts.length > 1 and isReply
|
||||
|
||||
post.rm()
|
||||
|
||||
QR.cooldown.set
|
||||
req: req
|
||||
post: post
|
||||
isReply: !!threadID
|
||||
QR.cooldown.set {req, post, isReply}
|
||||
|
||||
if threadID is postID # new thread
|
||||
URL = "/#{g.BOARD}/res/#{threadID}"
|
||||
else if g.VIEW is 'index' and !QR.cooldown.auto # posting from the index
|
||||
else if g.VIEW is 'index' and !QR.cooldown.auto and Conf['Open Post in New Tab'] # replying from the index
|
||||
URL = "/#{g.BOARD}/res/#{threadID}#p#{postID}"
|
||||
if URL
|
||||
if Conf['Open Post in New Tab']
|
||||
|
||||
@ -6,7 +6,7 @@ Report =
|
||||
form = $ 'form'
|
||||
field = $.id 'recaptcha_response_field'
|
||||
$.on field, 'keydown', (e) ->
|
||||
$.unsafeWindow.Recaptcha.reload 't' if e.keyCode is 8 and not field.value
|
||||
$.globalEval 'Recaptcha.reload("t")' if e.keyCode is 8 and not field.value
|
||||
$.on form, 'submit', (e) ->
|
||||
e.preventDefault()
|
||||
response = field.value.trim()
|
||||
|
||||