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
This commit is contained in:
Zixaphir 2013-03-28 20:42:02 -07:00
commit 1662401958
29 changed files with 339 additions and 231 deletions

View File

@ -3,10 +3,10 @@
1. Make sure both your **browser** and **4chan X** are up to date. 1. Make sure both your **browser** and **4chan X** are up to date.
2. Disable your other extensions & scripts to identify conflicts. 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: 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. 1. Precise steps to reproduce the problem.
2. Report console errors, if any. 2. Console errors, if any.
3. Report browser version. 3. Browser version.
4. Include your exported settings. 4. Your exported settings.
Open your console with: Open your console with:
- `Ctrl + Shift + J` on Chrome. - `Ctrl + Shift + J` on Chrome.

View File

@ -23,31 +23,18 @@ module.exports = (grunt) ->
] ]
dest: 'tmp/script.coffee' 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: crx:
options: options:
process: process:
data: pkg data: pkg
src: [ files:
'src/banner.js' 'builds/crx/manifest.json': 'src/manifest.json'
'tmp/script.js' 'builds/crx/script.js': [
] 'src/banner.js'
dest: 'builds/crx/script.js' 'tmp/script.js'
]
userscript: userjs:
options: options:
process: process:
data: pkg data: pkg
@ -56,13 +43,27 @@ module.exports = (grunt) ->
'src/banner.js' 'src/banner.js'
'tmp/script.js' 'tmp/script.js'
] ]
dest: '<%= pkg.name %>.user.js'
userjs:
# Lazily copy the userscript
src: '<%= pkg.name %>.user.js'
dest: 'builds/<%= pkg.name %>.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: coffee:
script: script:
src: 'tmp/script.coffee' src: 'tmp/script.coffee'
@ -89,35 +90,73 @@ module.exports = (grunt) ->
options: options:
interrupt: true interrupt: true
files: [ files: [
'Gruntfile.js' 'Gruntfile.coffee'
'package.json' 'package.json'
'lib/**/*.coffee' 'lib/**/*'
'src/**/*.coffee' 'src/**/*'
'src/**/*.js' 'css/**/*'
'css/**/*.css' 'img/**/*'
'img/*'
] ]
tasks: 'default' tasks: 'build'
compress:
crx:
options:
archive: 'builds/4chan-X.zip'
level: 9
pretty: true
expand: true
cwd: 'builds/crx/'
src: '**'
clean: clean:
tmp: 'tmp' builds: 'builds'
tmp: 'tmp'
grunt.loadNpmTasks 'grunt-bump' grunt.loadNpmTasks 'grunt-bump'
grunt.loadNpmTasks 'grunt-contrib-clean' grunt.loadNpmTasks 'grunt-contrib-clean'
grunt.loadNpmTasks 'grunt-contrib-coffee' grunt.loadNpmTasks 'grunt-contrib-coffee'
grunt.loadNpmTasks 'grunt-contrib-compress'
grunt.loadNpmTasks 'grunt-contrib-concat' grunt.loadNpmTasks 'grunt-contrib-concat'
grunt.loadNpmTasks 'grunt-contrib-copy'
grunt.loadNpmTasks 'grunt-contrib-watch' grunt.loadNpmTasks 'grunt-contrib-watch'
grunt.loadNpmTasks 'grunt-exec' grunt.loadNpmTasks 'grunt-exec'
grunt.registerTask 'default', [ grunt.registerTask 'default', ['build']
'concat:coffee',
'coffee:script', grunt.registerTask 'set-build', 'Set the build type variable', (type) ->
'concat:manifest', pkg.type = type;
'concat:crx', grunt.log.ok 'pkg.type = %s', type
'concat:userscript',
'concat:userjs', grunt.registerTask 'build', [
'concat:metadata', 'build-crx'
'clean' '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', [ grunt.registerTask 'release', [
@ -140,6 +179,7 @@ module.exports = (grunt) ->
'bump:major' 'bump:major'
'updcl:1' 'updcl:1'
] ]
grunt.registerTask 'updcl', 'Update the changelog', (i) -> grunt.registerTask 'updcl', 'Update the changelog', (i) ->
# Update the `pkg` object with the new version. # Update the `pkg` object with the new version.
pkg = grunt.file.readJSON('package.json'); pkg = grunt.file.readJSON('package.json');

View File

@ -1,8 +1,8 @@
# 4chan X # 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) ### [MIT License](/LICENSE)
### [Contribute](/4chan-x/blob/master/CONTRIBUTING.md) ### [Contribute](/CONTRIBUTING.md)

View File

@ -60,7 +60,7 @@ a[href="javascript:;"] {
#qp, #ihover, #qp, #ihover,
#updater, #thread-stats, #updater, #thread-stats,
#navlinks, #header, #navlinks, #header,
#qr, #watcher { #qr {
position: fixed; position: fixed;
} }
#overlay { #overlay {
@ -81,12 +81,15 @@ a[href="javascript:;"] {
#qr { #qr {
z-index: 30; z-index: 30;
} }
#watcher { #watcher:hover {
z-index: 20; z-index: 20;
} }
#header { #header {
z-index: 10; z-index: 10;
} }
#watcher {
z-index: 5;
}
/* Header */ /* Header */
.fourchan-x body { .fourchan-x body {
@ -104,8 +107,8 @@ a[href="javascript:;"] {
display: flex; display: flex;
padding: 3px 4px 4px; padding: 3px 4px 4px;
position: relative; position: relative;
-webkit-transition: all .1s ease-in-out; -webkit-transition: all .1s .05s ease-in-out;
transition: all .1s ease-in-out; transition: all .1s .05s ease-in-out;
} }
#board-list { #board-list {
-webkit-flex: 1; -webkit-flex: 1;
@ -252,6 +255,9 @@ a[href="javascript:;"] {
-webkit-flex: 1; -webkit-flex: 1;
flex: 1; flex: 1;
} }
.tab-selected {
font-weight: 700;
}
.section-container { .section-container {
-webkit-flex: 1; -webkit-flex: 1;
flex: 1; flex: 1;
@ -421,7 +427,7 @@ a[href="javascript:;"] {
max-height: 300px; max-height: 300px;
max-width: 500px; max-width: 500px;
} }
.qphl { .qphl > .post {
outline: 2px solid rgba(216, 94, 49, .7); outline: 2px solid rgba(216, 94, 49, .7);
} }
@ -435,6 +441,9 @@ a[href="javascript:;"] {
.expanding { .expanding {
opacity: .5; opacity: .5;
} }
.expanded-image {
clear: both;
}
.expanded-image > .op > .file::after { .expanded-image > .op > .file::after {
content: ''; content: '';
clear: both; clear: both;
@ -736,6 +745,11 @@ a[href="javascript:;"] {
#file-n-submit > #qr-file-spoiler { #file-n-submit > #qr-file-spoiler {
margin: 0 2px; margin: 0 2px;
} }
#file-n-submit input[type='submit'] {
min-width: 40px;
-webkit-order: 1;
order: 1;
}
#qr input[type='file'] { #qr input[type='file'] {
position: absolute; position: absolute;
visibility: hidden; visibility: hidden;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 463 B

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 463 B

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 B

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 B

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 B

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 B

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 B

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 B

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 B

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 B

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 455 B

After

Width:  |  Height:  |  Size: 349 B

BIN
img/icon128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

BIN
img/icon16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

BIN
img/icon48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

View File

@ -51,7 +51,7 @@ $.extend String::,
$.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000))) $.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000)))
$.extend $, $.extend $,
engine: /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase() engine: '<% if (type === 'crx') { %>webkit<% } else if (type === 'userjs') { %>presto<% } else { %>gecko<% } %>'
id: (id) -> id: (id) ->
d.getElementById id d.getElementById id
ready: (fc) -> ready: (fc) ->
@ -226,19 +226,8 @@ $.extend $,
globalEval: (code) -> globalEval: (code) ->
script = $.el 'script', script = $.el 'script',
textContent: code textContent: code
$.add d.head, script $.add (d.head or doc), script
$.rm 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) -> bytesToString: (size) ->
unit = 0 # Bytes unit = 0 # Bytes
while size >= 1024 while size >= 1024
@ -255,11 +244,30 @@ $.extend $,
Math.round size Math.round size
"#{size} #{['B', 'KB', 'MB', 'GB'][unit]}" "#{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) -> $.delete = (name) ->
GM_deleteValue g.NAMESPACE + name delete scriptStorage[g.NAMESPACE + name]
$.get = (name, defaultValue) -> $.get = (name, defaultValue) ->
if value = GM_getValue g.NAMESPACE + name if value = scriptStorage[g.NAMESPACE + name]
JSON.parse value JSON.parse value
else else
defaultValue defaultValue
@ -268,37 +276,19 @@ if GM_deleteValue?
value = JSON.stringify value value = JSON.stringify value
# for `storage` events # for `storage` events
localStorage.setItem name, value localStorage.setItem name, value
GM_setValue name, value scriptStorage[name] = value
else if window.opera <% } else { %>
do -> delete: (name) ->
# http://www.opera.com/docs/userjs/specs/#scriptstorage GM_deleteValue g.NAMESPACE + name
# http://www.opera.com/docs/userjs/using/#securepages get: (name, defaultValue) ->
# >The scriptStorage object is available only during if value = GM_getValue g.NAMESPACE + name
# 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
JSON.parse value JSON.parse value
else else
defaultValue defaultValue
$.set = (name, value) -> set: (name, value) ->
localStorage.setItem g.NAMESPACE + name, JSON.stringify value name = g.NAMESPACE + name
value = JSON.stringify value
# for `storage` events
localStorage.setItem name, value
GM_setValue name, value
<% } %>

View File

@ -5,7 +5,7 @@
"meta": { "meta": {
"name": "4chan X Beta", "name": "4chan X Beta",
"repo": "https://github.com/MayhemYDG/4chan-x/", "repo": "https://github.com/MayhemYDG/4chan-x/",
"page": "http://mayhemydg.github.com/4chan-x/", "page": "https://4chan-x.just-believe.in/",
"mainBranch": "v3", "mainBranch": "v3",
"matches": [ "matches": [
"*://api.4chan.org/*", "*://api.4chan.org/*",
@ -15,12 +15,14 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"grunt": "~0.4.0", "grunt": "~0.4.1",
"grunt-bump": "~0.0.0", "grunt-bump": "~0.0.0",
"grunt-contrib-clean": "~0.4.0", "grunt-contrib-clean": "~0.4.0",
"grunt-contrib-coffee": "~0.6.2", "grunt-contrib-coffee": "~0.6.4",
"grunt-contrib-concat": "~0.1.0", "grunt-contrib-compress": "~0.4.5",
"grunt-contrib-watch": "~0.3.0", "grunt-contrib-concat": "~0.1.3",
"grunt-contrib-copy": "~0.4.0",
"grunt-contrib-watch": "~0.3.1",
"grunt-exec": "~0.4.0" "grunt-exec": "~0.4.0"
}, },
"repository": { "repository": {

View File

@ -15,7 +15,7 @@ Config =
] ]
'Custom Board Navigation': [ 'Custom Board Navigation': [
true true
'Disable this to always display the full board list.' 'Show custom links instead of the full board list.'
] ]
'404 Redirect': [ '404 Redirect': [
true true
@ -27,7 +27,7 @@ Config =
] ]
'Time Formatting': [ 'Time Formatting': [
true true
'Localize and format timestamps arbitrarily.' 'Localize and format timestamps.'
] ]
'Relative Post Dates': [ 'Relative Post Dates': [
false false
@ -39,19 +39,15 @@ Config =
] ]
'Comment Expansion': [ 'Comment Expansion': [
true true
'Can expand too long comments.' 'Add buttons to expand long comments.'
] ]
'Thread Expansion': [ 'Thread Expansion': [
true true
'Can expand threads to view all replies.' 'Add buttons to expand threads.'
] ]
'Index Navigation': [ 'Index Navigation': [
false false
'Navigate to previous / next thread.' 'Add buttons to navigate between threads.'
]
'Custom CSS': [
false
'Apply custom CSS to 4chan.'
] ]
'Check for Updates': [ 'Check for Updates': [
true true
@ -75,7 +71,7 @@ Config =
'Filtering': 'Filtering':
'Anonymize': [ 'Anonymize': [
false false
'Turn everyone Anonymous.' 'Make everyone Anonymous.'
] ]
'Filter': [ 'Filter': [
true true
@ -87,19 +83,19 @@ Config =
] ]
'Thread Hiding': [ 'Thread Hiding': [
true true
'Hide entire threads.' 'Add buttons to hide entire threads.'
] ]
'Reply Hiding': [ 'Reply Hiding': [
true true
'Hide single replies.' 'Add buttons to hide single replies.'
] ]
'Hiding Buttons': [ 'Hiding Buttons': [
true true
'Make buttons to hide threads / replies, in addition to menu links.' 'Add buttons to hide threads / replies, in addition to menu links.'
] ]
'Stubs': [ 'Stubs': [
true true
'Make stubs of hidden threads / replies.' 'Show stubs of hidden threads / replies.'
] ]
'Images': 'Images':
@ -135,8 +131,16 @@ Config =
'Menu': 'Menu':
'Menu': [ 'Menu': [
true 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': [ 'Report Link': [
true true
'Add a report link to the menu.' 'Add a report link to the menu.'
@ -222,7 +226,7 @@ Config =
'Hide the normal post form.' 'Hide the normal post form.'
] ]
'Quote links': 'Quote Links':
'Quote Backlinks': [ 'Quote Backlinks': [
true true
'Add quote backlinks.' 'Add quote backlinks.'
@ -331,21 +335,23 @@ Config =
MD5: '' MD5: ''
sauces: """ sauces: """
http://iqdb.org/?url=%turl http://iqdb.org/?url=%TURL
http://www.google.com/searchbyimage?image_url=%turl https://www.google.com/searchbyimage?image_url=%TURL
#http://tineye.com/search?url=%turl #//tineye.com/search?url=%TURL
#http://saucenao.com/search.php?db=999&url=%turl #http://saucenao.com/search.php?url=%TURL
#http://3d.iqdb.org/?url=%turl #http://3d.iqdb.org/?url=%TURL
#http://regex.info/exif.cgi?imgurl=%url #http://regex.info/exif.cgi?imgurl=%URL
# uploaders: # uploaders:
#http://imgur.com/upload?url=%url;text:Upload to imgur #http://imgur.com/upload?url=%URL;text:Upload to imgur
#http://omploader.org/upload?url1=%url;text:Upload to omploader #http://ompldr.org/upload?url1=%URL;text:Upload to ompldr
# "View Same" in archives: # "View Same" in archives:
#//archive.foolz.us/_/search/image/%MD5/;text:View same on foolz #//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.foolz.us/%board/search/image/%MD5/;text:View same on foolz /%board/
#//archive.installgentoo.net/%board/image/%MD5;text:View same on installgentoo /%board/ #//archive.installgentoo.net/%board/image/%MD5;text:View same on installgentoo /%board/
""" """
'Custom CSS': false
'Header auto-hide': false 'Header auto-hide': false
'Header catalog links': false 'Header catalog links': false

View File

@ -31,7 +31,7 @@ Header =
className: 'hide-board-list-button brackets-wrap' className: 'hide-board-list-button brackets-wrap'
innerHTML: '<a href=javascript:;> - </a>' innerHTML: '<a href=javascript:;> - </a>'
$.on btn, 'click', Header.toggleBoardList $.on btn, 'click', Header.toggleBoardList
$.prepend fullBoardList, btn $.add fullBoardList, btn
else else
$.rm $ '#custom-board-list', nav $.rm $ '#custom-board-list', nav
fullBoardList.hidden = false fullBoardList.hidden = false
@ -40,8 +40,8 @@ Header =
list = $ '#custom-board-list', Header.nav list = $ '#custom-board-list', Header.nav
list.innerHTML = null list.innerHTML = null
return unless text return unless text
as = $$('#full-board-list a', Header.nav) as = $$('#full-board-list a', Header.nav)[0...-2] # ignore the Settings and Home links
nodes = text.match(/[\w@]+(-(all|title|full|text:"[^"]+"))?|[^\w@]+/g).map (t) -> nodes = text.match(/[\w@]+(-(all|title|full|index|catalog|text:"[^"]+"))*|[^\w@]+/g).map (t) ->
if /^[^\w@]/.test t if /^[^\w@]/.test t
return $.tn t return $.tn t
if t is 'toggle-all' if t is 'toggle-all'
@ -58,12 +58,17 @@ Header =
for a in as for a in as
if a.textContent is board if a.textContent is board
a = a.cloneNode true a = a.cloneNode true
if /-title$/.test t if /-title/.test t
a.textContent = a.title a.textContent = a.title
else if /-full$/.test t else if /-full/.test t
a.textContent = "/#{board}/ - #{a.title}" a.textContent = "/#{board}/ - #{a.title}"
else if m = t.match /-text:"(.+)"$/ else if /-(index|catalog|text)/.test t
a.textContent = m[1] 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 '@' else if board is '@'
$.addClass a, 'navSmall' $.addClass a, 'navSmall'
return a return a
@ -224,17 +229,15 @@ Settings =
links = [] links = []
for section in Settings.sections for section in Settings.sections
link = $.el 'a', link = $.el 'a',
className: "tab-#{section.hyphenatedTitle}"
textContent: section.title textContent: section.title
href: 'javascript:;' href: 'javascript:;'
$.on link, 'click', Settings.openSection.bind section $.on link, 'click', Settings.openSection.bind section
links.push link, $.tn ' | ' links.push link, $.tn ' | '
sectionToOpen = link if section.title is openSection sectionToOpen = link if section.title is openSection
links.pop() links.pop()
if sectionToOpen
sectionToOpen.click()
else
links[0].click()
$.add $('.sections-list', overlay), links $.add $('.sections-list', overlay), links
(if sectionToOpen then sectionToOpen else links[0]).click()
$.on $('.close', overlay), 'click', Settings.close $.on $('.close', overlay), 'click', Settings.close
$.on overlay, 'click', Settings.close $.on overlay, 'click', Settings.close
@ -254,11 +257,15 @@ Settings =
addSection: (title, open) -> addSection: (title, open) ->
if typeof title isnt 'string' if typeof title isnt 'string'
{title, open} = title.detail {title, open} = title.detail
Settings.sections.push {title, open} hyphenatedTitle = title.toLowerCase().replace /\s+/g, '-'
Settings.sections.push {title, hyphenatedTitle, open}
openSection: -> openSection: ->
if selected = $ '.tab-selected', Settings.dialog
$.rmClass selected, 'tab-selected'
$.addClass $(".tab-#{@hyphenatedTitle}", Settings.dialog), 'tab-selected'
section = $ 'section', Settings.dialog section = $ 'section', Settings.dialog
section.innerHTML = null section.innerHTML = null
section.className = "section-#{@title.toLowerCase().replace /\s+/g, '-'}" section.className = "section-#{@hyphenatedTitle}"
@open section, g @open section, g
section.scrollTop = 0 section.scrollTop = 0
@ -283,7 +290,7 @@ Settings =
description = arr[1] description = arr[1]
div = $.el 'div', div = $.el 'div',
innerHTML: "<label><input type=checkbox name=\"#{key}\" #{checked}>#{key}</label><span class=description>: #{description}</span>" 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 fs, div
$.add section, fs $.add section, fs
@ -311,7 +318,7 @@ Settings =
className: 'warning' className: 'warning'
textContent: 'Save me!' textContent: 'Save me!'
download: "<%= meta.name %> v#{g.VERSION}-#{now}.json" 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' target: '_blank'
if $.engine isnt 'gecko' if $.engine isnt 'gecko'
a.click() a.click()
@ -451,7 +458,7 @@ Settings =
$.add div, ta $.add div, ta
return return
div.innerHTML = """ 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> <p>
Use <a href=https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions>regular expressions</a>, one per line.<br> 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> Lines starting with a <code>#</code> will be ignored.<br>
@ -509,6 +516,9 @@ Settings =
<div>Title link: <code>board-title</code></div> <div>Title link: <code>board-title</code></div>
<div>Full text link: <code>board-full</code></div> <div>Full text link: <code>board-full</code></div>
<div>Custom text link: <code>board-text:"VIP Board"</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> <div>Full board list toggle: <code>toggle-all</code></div>
</fieldset> </fieldset>
@ -551,9 +561,9 @@ Settings =
</fieldset> </fieldset>
<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> <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> </fieldset>
""" """
for name in ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss'] for name in ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss']
@ -567,6 +577,7 @@ Settings =
unless 'usercss' is name unless 'usercss' is name
$.on input, event, Settings[name] $.on input, event, Settings[name]
Settings[name].call input Settings[name].call input
$.on $('input[name="Custom CSS"]', section), 'change', Settings.togglecss
$.on $.id('apply-css'), 'click', Settings.usercss $.on $.id('apply-css'), 'click', Settings.usercss
boardnav: -> boardnav: ->
Header.generateBoardList @value Header.generateBoardList @value
@ -591,12 +602,20 @@ Settings =
favicon: -> favicon: ->
Favicon.switch() Favicon.switch()
Unread.update() if g.VIEW is 'thread' and Conf['Unread Tab Icon'] 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}>" @nextElementSibling.innerHTML = """
usercss: -> <img src=#{Favicon.default}>
if Conf['Custom CSS'] <img src=#{Favicon.unreadSFW}>
CustomCSS.update() <img src=#{Favicon.unreadNSFW}>
else <img src=#{Favicon.unreadDead}>
"""
togglecss: ->
if $('textarea', @parentNode.parentNode).disabled = !@checked
CustomCSS.rmStyle() CustomCSS.rmStyle()
else
CustomCSS.addStyle()
$.cb.checked.call @
usercss: ->
CustomCSS.update()
keybinds: (section) -> keybinds: (section) ->
section.innerHTML = """ section.innerHTML = """
@ -632,33 +651,40 @@ Fourchan =
board = g.BOARD.ID board = g.BOARD.ID
if board is 'g' if board is 'g'
$.globalEval """
window.addEventListener('prettyprint', function(e) {
var pre = e.detail;
pre.innerHTML = prettyPrintOne(pre.innerHTML);
}, false);
"""
Post::callbacks.push Post::callbacks.push
name: 'Parse /g/ code' name: 'Parse /g/ code'
cb: @code cb: @code
if board is 'sci' 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 Post::callbacks.push
name: 'Parse /sci/ math' name: 'Parse /sci/ math'
cb: @math cb: @math
code: -> code: ->
return if @isClone return if @isClone
for pre in $$ '.prettyprint', @nodes.comment for pre in $$ '.prettyprint', @nodes.comment
pre.innerHTML = $.unsafeWindow.prettyPrintOne pre.innerHTML $.event 'prettyprint', pre, window
return return
math: -> math: ->
return if @isClone or !$ '.math', @nodes.comment return if @isClone or !$ '.math', @nodes.comment
# https://github.com/MayhemYDG/4chan-x/issues/645#issuecomment-13704562 $.event 'jsmath', @nodes.post, window
{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();
"""
parseThread: (threadID, offset, limit) -> parseThread: (threadID, offset, limit) ->
# Fix /sci/ # Fix /sci/
# Fix /g/ # Fix /g/
@ -959,7 +985,7 @@ Filter =
ThreadHiding = ThreadHiding =
init: -> 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}" Misc.clearThreads "hiddenThreads.#{g.BOARD}"
@getHiddenThreads() @getHiddenThreads()
@ -971,7 +997,7 @@ ThreadHiding =
node: -> node: ->
if data = ThreadHiding.hiddenThreads.threads[@] if data = ThreadHiding.hiddenThreads.threads[@]
ThreadHiding.hide @, data.makeStub ThreadHiding.hide @, data.makeStub
return unless Conf['Hiding Buttons'] return unless Conf['Thread Hiding']
$.prepend @OP.nodes.root, ThreadHiding.makeButton @, 'hide' $.prepend @OP.nodes.root, ThreadHiding.makeButton @, 'hide'
getHiddenThreads: -> getHiddenThreads: ->
@ -999,7 +1025,7 @@ ThreadHiding =
menu: menu:
init: -> 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', div = $.el 'div',
className: 'hide-thread-link' className: 'hide-thread-link'
@ -1101,7 +1127,7 @@ ThreadHiding =
ReplyHiding = ReplyHiding =
init: -> 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}" Misc.clearThreads "hiddenPosts.#{g.BOARD}"
@getHiddenPosts() @getHiddenPosts()
@ -1118,7 +1144,7 @@ ReplyHiding =
else else
Recursive.apply ReplyHiding.hide, @, data.makeStub, true Recursive.apply ReplyHiding.hide, @, data.makeStub, true
Recursive.add 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' $.replace $('.sideArrows', @nodes.root), ReplyHiding.makeButton @, 'hide'
getHiddenPosts: -> getHiddenPosts: ->
@ -1126,7 +1152,7 @@ ReplyHiding =
menu: menu:
init: -> 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 # Hide
div = $.el 'div', div = $.el 'div',
@ -1336,7 +1362,7 @@ Recursive =
QuoteStrikeThrough = QuoteStrikeThrough =
init: -> 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 Post::callbacks.push
name: 'Strike-through Quotes' name: 'Strike-through Quotes'
@ -1624,7 +1650,7 @@ Keybinds =
when Conf['Open settings'] when Conf['Open settings']
Settings.open() Settings.open()
when Conf['Close'] when Conf['Close']
if $.id 'settings' if $.id 'fourchanx-settings'
Settings.close() Settings.close()
else if (notifications = $$ '.notification').length else if (notifications = $$ '.notification').length
for notification in notifications for notification in notifications
@ -1791,7 +1817,6 @@ Keybinds =
focus: (post) -> focus: (post) ->
$.addClass post, 'highlight' $.addClass post, 'highlight'
$('a[title="Highlight this post"]', post).focus()
Nav = Nav =
init: -> init: ->
@ -2419,10 +2444,10 @@ Misc = # super semantic
return if data.lastChecked > Date.now() - 12 * $.HOUR 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 = {} threads = {}
for obj in JSON.parse @response for page in JSON.parse @response
for thread in obj.threads for thread in page.threads
if thread.no of data.threads if thread.no of data.threads
threads[thread.no] = data.threads[thread.no] threads[thread.no] = data.threads[thread.no]
unless Object.keys(threads).length unless Object.keys(threads).length
@ -2630,7 +2655,7 @@ QuotePreview =
# Remove the clone that's in the qp from the array. # Remove the clone that's in the qp from the array.
posts.pop() posts.pop()
for post in posts for post in posts
$.addClass post.nodes.post, 'qphl' $.addClass post.nodes.root, 'qphl'
quoterID = $.x('ancestor::*[@id][1]', @).id.match(/\d+$/)[0] quoterID = $.x('ancestor::*[@id][1]', @).id.match(/\d+$/)[0]
clone = Get.postFromRoot qp.firstChild clone = Get.postFromRoot qp.firstChild
@ -2651,7 +2676,7 @@ QuotePreview =
return unless Conf['Quote Highlighting'] return unless Conf['Quote Highlighting']
for post in [post].concat post.clones for post in [post].concat post.clones
$.rmClass post.nodes.post, 'qphl' $.rmClass post.nodes.root, 'qphl'
return return
QuoteBacklink = QuoteBacklink =
@ -3055,14 +3080,15 @@ Sauce =
createSauceLink: (link) -> createSauceLink: (link) ->
link = link.replace /%(T?URL|MD5|board)/ig, (parameter) -> link = link.replace /%(T?URL|MD5|board)/ig, (parameter) ->
switch parameter switch parameter
when '%TURL', '%turl'
"' + post.file.thumbURL + '" when '%TURL'
when '%URL', '%url' "' + encodeURIComponent(post.file.thumbURL) + '"
"' + post.file.URL + '" when '%URL'
when '%MD5', '%md5' "' + encodeURIComponent(post.file.URL) + '"
when '%MD5'
"' + encodeURIComponent(post.file.MD5) + '" "' + encodeURIComponent(post.file.MD5) + '"
when '%board' when '%board'
"' + post.board + '" "' + encodeURIComponent(post.board) + '"
else else
parameter parameter
text = if m = link.match(/;text:(.+)$/) then m[1] else link.match(/(\w+)\.\w+\//)[1] 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 # Scroll back to the thumbnail when contracting the image
# to avoid being left miles away from the relevant post. # to avoid being left miles away from the relevant post.
postRect = post.nodes.root.getBoundingClientRect() postRect = post.nodes.root.getBoundingClientRect()
headRect = Header.bar.getBoundingClientRect() headRect = Header.toggle.getBoundingClientRect()
top = postRect.top - headRect.top - headRect.height - 2 top = postRect.top - headRect.top - headRect.height - 2
root = if $.engine is 'webkit' root = if $.engine is 'webkit'
d.body d.body
@ -3222,7 +3248,10 @@ ImageExpand =
post = Get.postFromNode @ post = Get.postFromNode @
$.rm @ $.rm @
delete post.file.fullImage 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. # Don't try to re-expend if it was already contracted.
return return
ImageExpand.contract post ImageExpand.contract post
@ -3445,7 +3474,7 @@ ExpandThread =
#goddamit moot #goddamit moot
num = if thread.isSticky num = if thread.isSticky
1 1
else switch g.BOARD else switch g.BOARD.ID
# XXX boards config # XXX boards config
when 'b', 'vg', 'q' then 3 when 'b', 'vg', 'q' then 3
when 't' then 1 when 't' then 1
@ -3493,7 +3522,7 @@ ExpandThread =
# Enable 4chan features. # Enable 4chan features.
if Conf['Enable 4chan\'s Extension'] if Conf['Enable 4chan\'s Extension']
$.unsafeWindow.Parser.parseThread thread.ID, 1, nodes.length $.globalEval "Parser.parseThread(#{thread.ID}, 1, #{nodes.length})"
else else
Fourchan.parseThread thread.ID, 1, nodes.length Fourchan.parseThread thread.ID, 1, nodes.length
@ -3505,7 +3534,10 @@ ThreadExcerpt =
name: 'Thread Excerpt' name: 'Thread Excerpt'
cb: @node cb: @node
node: -> node: ->
d.title = Get.threadExcerpt @ d.title = if (excerpt = Get.threadExcerpt @).length > 80
"#{excerpt[...77]}..."
else
excerpt
Unread = Unread =
init: -> init: ->
@ -3523,6 +3555,7 @@ Unread =
Unread.lastReadPost = $.get("lastReadPosts.#{@board}", threads: {}).threads[@] or 0 Unread.lastReadPost = $.get("lastReadPosts.#{@board}", threads: {}).threads[@] or 0
Unread.posts = [] Unread.posts = []
Unread.postsQuotingYou = [] Unread.postsQuotingYou = []
Unread.titleEl = $ 'title', d.head
Unread.title = d.title Unread.title = d.title
posts = [] posts = []
for ID, post of @posts for ID, post of @posts
@ -3604,10 +3637,17 @@ Unread =
count = Unread.posts.length count = Unread.posts.length
if Conf['Unread Count'] if Conf['Unread Count']
d.title = if g.DEAD prefix = if count
"(#{Unread.posts.length}) /#{g.BOARD}/ - 404" "(#{count})"
else 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'] return unless Conf['Unread Tab Icon']
@ -3997,9 +4037,9 @@ ThreadUpdater =
$.queueTask -> $.queueTask ->
# Enable 4chan features. # Enable 4chan features.
threadID = ThreadUpdater.thread.ID threadID = ThreadUpdater.thread.ID
{length} = ThreadUpdater.root.children {length} = $$ '.thread > .postContainer', ThreadUpdater.root
if Conf['Enable 4chan\'s Extension'] if Conf['Enable 4chan\'s Extension']
$.unsafeWindow.Parser.parseThread threadID, -count $.globalEval "Parser.parseThread(#{threadID}, #{-count})"
else else
Fourchan.parseThread threadID, length - count, length Fourchan.parseThread threadID, length - count, length
@ -4038,6 +4078,7 @@ ThreadWatcher =
$.delete 'AutoWatch' $.delete 'AutoWatch'
ready: -> ready: ->
return unless Main.isThisPageLegit()
ThreadWatcher.refresh() ThreadWatcher.refresh()
$.add d.body, ThreadWatcher.dialog $.add d.body, ThreadWatcher.dialog

View File

@ -1,12 +1,14 @@
<% if (type === 'userjs') { %>
# Opera doesn't support the @match metadata key, # Opera doesn't support the @match metadata key,
# return 4chan X here if we're not on 4chan. # return 4chan X here if we're not on 4chan.
return unless /^(boards|images|sys)\.4chan\.org$/.test location.hostname return unless /^(boards|images|sys)\.4chan\.org$/.test location.hostname
<% } %>
Conf = {} Conf = {}
c = console c = console
d = document d = document
doc = null doc = d.documentElement
g = g =
VERSION: '<%= version %>' VERSION: '<%= version %>'
NAMESPACE: '<%= meta.name %>.' NAMESPACE: '<%= meta.name %>.'
boards: {} boards: {}

View File

@ -41,28 +41,28 @@ class Post
backlinks: info.getElementsByClassName 'backlink' backlinks: info.getElementsByClassName 'backlink'
@info = {} @info = {}
if subject = $ '.subject', info if subject = $ '.subject', info
@nodes.subject = subject @nodes.subject = subject
@info.subject = subject.textContent @info.subject = subject.textContent
if name = $ '.name', info if name = $ '.name', info
@nodes.name = name @nodes.name = name
@info.name = name.textContent @info.name = name.textContent
if email = $ '.useremail', info if email = $ '.useremail', info
@nodes.email = email @nodes.email = email
@info.email = decodeURIComponent email.href[7..] @info.email = decodeURIComponent email.href[7..]
if tripcode = $ '.postertrip', info if tripcode = $ '.postertrip', info
@nodes.tripcode = tripcode @nodes.tripcode = tripcode
@info.tripcode = tripcode.textContent @info.tripcode = tripcode.textContent
if uniqueID = $ '.posteruid', info if uniqueID = $ '.posteruid', info
@nodes.uniqueID = uniqueID @nodes.uniqueID = uniqueID
@info.uniqueID = uniqueID.firstElementChild.textContent @info.uniqueID = uniqueID.firstElementChild.textContent
if capcode = $ '.capcode', info if capcode = $ '.capcode.hand', info
@nodes.capcode = capcode @nodes.capcode = capcode
@info.capcode = capcode.textContent @info.capcode = capcode.textContent.replace '## ', ''
if flag = $ '.countryFlag', info if flag = $ '.countryFlag', info
@nodes.flag = flag @nodes.flag = flag
@info.flag = flag.title @info.flag = flag.title
if date = $ '.dateTime', info if date = $ '.dateTime', info
@nodes.date = date @nodes.date = date
@info.date = new Date date.dataset.utc * 1000 @info.date = new Date date.dataset.utc * 1000
if Conf['Quick Reply'] if Conf['Quick Reply']
@ -284,9 +284,6 @@ class Clone extends Post
Main = Main =
init: -> init: ->
$.asap (-> d.documentElement), ->
doc = d.documentElement
# flatten Config into Conf # flatten Config into Conf
# and get saved or default values # and get saved or default values
flatten = (parent, obj) -> flatten = (parent, obj) ->
@ -486,7 +483,9 @@ Main =
Main.handleErrors errors if errors Main.handleErrors errors if errors
addCallback: (e) -> 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' Klass = if obj.type is 'Post'
Post Post
else else
@ -504,7 +503,7 @@ Main =
return return
div = $.el 'div', 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', -> $.on div.lastElementChild, 'click', ->
if @textContent is 'show' if @textContent is 'show'
@textContent = 'hide' @textContent = 'hide'
@ -533,7 +532,9 @@ Main =
isThisPageLegit: -> isThisPageLegit: ->
# 404 error page or similar. # 404 error page or similar.
unless 'thisPageIsLegit' of Main 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 Main.thisPageIsLegit
css: """ css: """

View File

@ -3,6 +3,11 @@
"version": "<%= version %>", "version": "<%= version %>",
"manifest_version": 2, "manifest_version": 2,
"description": "<%= description %>", "description": "<%= description %>",
"icons": {
"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png"
},
"content_scripts": [{ "content_scripts": [{
"js": ["script.js"], "js": ["script.js"],
"matches": <%= JSON.stringify(meta.matches) %>, "matches": <%= JSON.stringify(meta.matches) %>,

View File

@ -16,7 +16,7 @@
// @grant GM_deleteValue // @grant GM_deleteValue
// @grant GM_openInTab // @grant GM_openInTab
// @run-at document-start // @run-at document-start
// @updateURL <%= meta.repo %>raw/<%= meta.mainBranch %>/<%= name %>.meta.js // @updateURL <%= meta.page %>builds/<%= name %>.meta.js
// @downloadURL <%= meta.repo %>raw/<%= meta.mainBranch %>/<%= name %>.user.js // @downloadURL <%= meta.page %>builds/<%= name %>.user.js
// @icon data:image/gif;base64,<%= grunt.file.read('img/icon.gif', {encoding: 'base64'}) %> // @icon data:image/png;base64,<%= grunt.file.read('img/icon48.png', {encoding: 'base64'}) %>
// ==/UserScript== // ==/UserScript==

View File

@ -486,6 +486,7 @@ QR =
# so we generate thumbnails `s` times bigger then expected # so we generate thumbnails `s` times bigger then expected
# to avoid crappy resized quality. # to avoid crappy resized quality.
s = 90*2 s = 90*2
s *= 3 if @file.type is 'image/gif' # let them animate
{height, width} = img {height, width} = img
if height < s or width < s if height < s or width < s
@URL = fileURL if window.URL @URL = fileURL if window.URL
@ -581,6 +582,12 @@ QR =
return unless @isEnabled = !!$.id 'captchaFormPart' return unless @isEnabled = !!$.id 'captchaFormPart'
$.asap (-> $.id 'recaptcha_challenge_field_holder'), @ready.bind @ $.asap (-> $.id 'recaptcha_challenge_field_holder'), @ready.bind @
ready: -> 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', imgContainer = $.el 'div',
className: 'captcha-img' className: 'captcha-img'
title: 'Reload' title: 'Reload'
@ -648,7 +655,7 @@ QR =
load: -> load: ->
return unless @nodes.challenge.firstChild return unless @nodes.challenge.firstChild
# -1 minute to give upload some time. # -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 challenge = @nodes.challenge.firstChild.value
@nodes.img.alt = challenge @nodes.img.alt = challenge
@nodes.img.src = "//www.google.com/recaptcha/api/image?c=#{challenge}" @nodes.img.src = "//www.google.com/recaptcha/api/image?c=#{challenge}"
@ -666,7 +673,7 @@ QR =
@nodes.input.alt = count # For XTRM RICE. @nodes.input.alt = count # For XTRM RICE.
reload: (focus) -> reload: (focus) ->
# the 't' argument prevents the input from being focused # the 't' argument prevents the input from being focused
$.unsafeWindow.Recaptcha.reload 't' $.globalEval 'Recaptcha.reload("t")'
# Focus if we meant to. # Focus if we meant to.
@nodes.input.focus() if focus @nodes.input.focus() if focus
keydown: (e) -> keydown: (e) ->
@ -704,14 +711,14 @@ QR =
<span id=char-count></span> <span id=char-count></span>
</div> </div>
<div id=file-n-submit> <div id=file-n-submit>
<input type=submit>
<input id=qr-file-button type=button value='Choose files'> <input id=qr-file-button type=button value='Choose files'>
<span id=qr-filename-container> <span id=qr-filename-container>
<span id=qr-no-file>No selected file</span> <span id=qr-no-file>No selected file</span>
<span id=qr-filename></span> <span id=qr-filename></span>
</span> </span>
<a id=qr-filerm href=javascript:; title='Remove file' tabindex=-1>×</a> <a id=qr-filerm href=javascript:; title='Remove file'>×</a>
<input type=checkbox id=qr-file-spoiler title='Spoiler image' tabindex=-1> <input type=checkbox id=qr-file-spoiler title='Spoiler image'>
<input type=submit>
</div> </div>
<input type=file multiple> <input type=file multiple>
</form> </form>
@ -877,12 +884,14 @@ QR =
callbacks = callbacks =
onload: QR.response onload: QR.response
onerror: -> onerror: ->
# Connection error, or
# www.4chan.org/banned
delete QR.req delete QR.req
post.unlock() post.unlock()
QR.cooldown.auto = false QR.cooldown.auto = false
QR.status() QR.status()
# Connection error. QR.error $.el 'span',
QR.error 'Network error.' innerHTML: 'Connection error. You may have been <a href=//www.4chan.org/banned target=_blank>banned</a>.'
opts = opts =
form: $.formData postData form: $.formData postData
upCallbacks: upCallbacks:
@ -966,6 +975,7 @@ QR =
[_, threadID, postID] = h1.nextSibling.textContent.match /thread:(\d+),no:(\d+)/ [_, threadID, postID] = h1.nextSibling.textContent.match /thread:(\d+),no:(\d+)/
postID = +postID postID = +postID
threadID = +threadID or postID threadID = +threadID or postID
isReply = threadID isnt postID
(QR.yourPosts.threads[threadID] or= []).push postID (QR.yourPosts.threads[threadID] or= []).push postID
$.set "yourPosts.#{g.BOARD}", QR.yourPosts $.set "yourPosts.#{g.BOARD}", QR.yourPosts
@ -980,18 +990,15 @@ QR =
}, QR.nodes.el }, QR.nodes.el
# Enable auto-posting if we have stuff to post, disable it otherwise. # 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() post.rm()
QR.cooldown.set QR.cooldown.set {req, post, isReply}
req: req
post: post
isReply: !!threadID
if threadID is postID # new thread if threadID is postID # new thread
URL = "/#{g.BOARD}/res/#{threadID}" 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}" URL = "/#{g.BOARD}/res/#{threadID}#p#{postID}"
if URL if URL
if Conf['Open Post in New Tab'] if Conf['Open Post in New Tab']

View File

@ -6,7 +6,7 @@ Report =
form = $ 'form' form = $ 'form'
field = $.id 'recaptcha_response_field' field = $.id 'recaptcha_response_field'
$.on field, 'keydown', (e) -> $.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) -> $.on form, 'submit', (e) ->
e.preventDefault() e.preventDefault()
response = field.value.trim() response = field.value.trim()