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.
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.

View File

@ -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');

View File

@ -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)

View File

@ -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;

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)))
$.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
<% } %>

View File

@ -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": {

View File

@ -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

View File

@ -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

View File

@ -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: {}

View File

@ -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: """

View File

@ -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) %>,

View File

@ -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==

View File

@ -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']

View File

@ -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()