Merge branch 'v3' of git://github.com/MayhemYDG/4chan-x into v3
Conflicts: CHANGELOG.md CONTRIBUTING.md Gruntfile.js README.md changelog-old css/style.css html/Monitoring/ThreadStats.html html/Posting/QR.html json/archives.json package.json src/Archive/Redirect.coffee src/General/Header.coffee src/General/Main.coffee src/General/Settings.coffee src/General/UI.coffee src/General/html/Settings/Filter-guide.html src/General/lib/$.coffee src/General/lib/clone.class src/General/lib/post.class src/Images/ImageHover.coffee src/Menu/Menu.coffee src/Monitoring/ThreadUpdater.coffee src/Posting/QuickReply.coffee src/Quotelinks/QuotePreview.coffee src/Quotelinks/Quotify.coffee
This commit is contained in:
commit
582d067a10
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,8 +2,7 @@ node_modules/
|
|||||||
*~
|
*~
|
||||||
*.db
|
*.db
|
||||||
tmp-crx/
|
tmp-crx/
|
||||||
tmp-userjs/
|
|
||||||
tmp-userscript/
|
tmp-userscript/
|
||||||
builds/4chan-X-Chrome.zip
|
builds/4chan-X-Chrome.zip
|
||||||
builds/4chan-X-Opera.nex
|
builds/4chan-X-Opera.nex
|
||||||
Gruntfile.js
|
Gruntfile.js
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
|
**MayhemYDG**:
|
||||||
|
- Fix impossibility to create new threads when in dead threads.
|
||||||
|
- Drop Opera <15 support.
|
||||||
|
- Fix flag filtering on /sp/ and /int/.
|
||||||
|
- Minor fixes.
|
||||||
|
|
||||||
### v1.2.17
|
### v1.2.17
|
||||||
*2013-06-17*
|
*2013-06-17*
|
||||||
|
|
||||||
**seaweedchan**:
|
**seaweedchan**:
|
||||||
- Fix full images being forced onto their own line
|
- Fix full images being forced onto their own line
|
||||||
|
|
||||||
|
|||||||
49
CONTRIBUTING.md
Normal file
49
CONTRIBUTING.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
## Reporting bugs and suggestions
|
||||||
|
|
||||||
|
Reporting bugs:
|
||||||
|
|
||||||
|
1. Make sure both your **browser** and **4chan X** are up to date.
|
||||||
|
2. Disable your other extensions & scripts to identify [conflicts](https://github.com/MayhemYDG/4chan-x/wiki/FAQ#known-conflicting-extensions).
|
||||||
|
3. If your issue persists, open a [new issue](https://github.com/MayhemYDG/4chan-x/issues) with the following information:
|
||||||
|
1. Precise steps to reproduce the problem, with the expected and actual results.
|
||||||
|
2. Console errors, if any.
|
||||||
|
3. Browser version.
|
||||||
|
4. Your exported settings. If your settings contains sensible information (e.g. personas), edit the text file manually.
|
||||||
|
|
||||||
|
Open your console with:
|
||||||
|
- `Ctrl + Shift + J` on Chrome and Opera.
|
||||||
|
- `Ctrl + Shift + K` on Firefox.
|
||||||
|
|
||||||
|
Respect these guidelines:
|
||||||
|
- Describe the issue clearly, put some effort into it. A one-liner isn't a good enough description.
|
||||||
|
- If you want to get your suggestion implemented sooner, make it convincing.
|
||||||
|
- If you want to criticize, make it convincing and constructive.
|
||||||
|
- Be mature. Act like an idiot and you will be blocked without warning.
|
||||||
|
|
||||||
|
## Development & Contribution
|
||||||
|
|
||||||
|
### Get started
|
||||||
|
|
||||||
|
- Install [node.js](http://nodejs.org/).
|
||||||
|
- Install [Grunt's CLI](http://gruntjs.com/) with `npm install -g grunt-cli`.
|
||||||
|
- Clone 4chan X.
|
||||||
|
- `cd` into it.
|
||||||
|
- Install/Update 4chan X dependencies with `npm install`.
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
- Build with `grunt`.
|
||||||
|
- Continuously build with `grunt watch`.
|
||||||
|
|
||||||
|
### Release
|
||||||
|
|
||||||
|
- Update the version with `grunt patch`, `grunt minor` or `grunt major`.
|
||||||
|
- Release with `grunt release`.
|
||||||
|
|
||||||
|
Note: this is only used to release new 4chan X versions, and is **not** needed or wanted in pull requests.
|
||||||
|
|
||||||
|
### Contribute
|
||||||
|
|
||||||
|
- Edit the CoffeeScript sources.
|
||||||
|
- If the edits affect regular users, edit the changelog.
|
||||||
|
- Open a pull request.
|
||||||
@ -54,16 +54,6 @@ module.exports = (grunt) ->
|
|||||||
'tmp-<%= pkg.type %>/script.js'
|
'tmp-<%= pkg.type %>/script.js'
|
||||||
]
|
]
|
||||||
|
|
||||||
userjs:
|
|
||||||
options: concatOptions
|
|
||||||
src: [
|
|
||||||
'src/General/meta/botproc.js'
|
|
||||||
'src/General/meta/metadata.js'
|
|
||||||
'src/General/meta/banner.js'
|
|
||||||
'tmp-<%= pkg.type %>/script.js'
|
|
||||||
]
|
|
||||||
dest: 'builds/<%= pkg.name %>.js'
|
|
||||||
|
|
||||||
userscript:
|
userscript:
|
||||||
options: concatOptions
|
options: concatOptions
|
||||||
files:
|
files:
|
||||||
@ -94,7 +84,6 @@ module.exports = (grunt) ->
|
|||||||
build: [
|
build: [
|
||||||
'concat:meta'
|
'concat:meta'
|
||||||
'build-crx'
|
'build-crx'
|
||||||
'build-userjs'
|
|
||||||
'build-userscript'
|
'build-userscript'
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -137,7 +126,6 @@ module.exports = (grunt) ->
|
|||||||
clean:
|
clean:
|
||||||
builds: 'builds'
|
builds: 'builds'
|
||||||
tmpcrx: 'tmp-crx'
|
tmpcrx: 'tmp-crx'
|
||||||
tmpuserjs: 'tmp-userjs'
|
|
||||||
tmpuserscript: 'tmp-userscript'
|
tmpuserscript: 'tmp-userscript'
|
||||||
|
|
||||||
grunt.loadNpmTasks 'grunt-bump'
|
grunt.loadNpmTasks 'grunt-bump'
|
||||||
@ -171,14 +159,6 @@ module.exports = (grunt) ->
|
|||||||
'clean:tmpcrx'
|
'clean:tmpcrx'
|
||||||
]
|
]
|
||||||
|
|
||||||
grunt.registerTask 'build-userjs', [
|
|
||||||
'set-build:userjs'
|
|
||||||
'concat:coffee'
|
|
||||||
'coffee:script'
|
|
||||||
'concat:userjs'
|
|
||||||
'clean:tmpuserjs'
|
|
||||||
]
|
|
||||||
|
|
||||||
grunt.registerTask 'build-userscript', [
|
grunt.registerTask 'build-userscript', [
|
||||||
'set-build:userscript'
|
'set-build:userscript'
|
||||||
'concat:coffee'
|
'concat:coffee'
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* 4chan X - Version 1.2.17 - 2013-06-18
|
* 4chan X - Version 1.2.17 - 2013-07-21
|
||||||
*
|
*
|
||||||
* Licensed under the MIT license.
|
* Licensed under the MIT license.
|
||||||
* https://github.com/seaweedchan/4chan-x/blob/master/LICENSE
|
* https://github.com/seaweedchan/4chan-x/blob/master/LICENSE
|
||||||
|
|||||||
@ -41,4 +41,4 @@ Note: this is only used to release new 4chan X versions, and is **not** needed o
|
|||||||
|
|
||||||
- Edit the CoffeeScript sources.
|
- Edit the CoffeeScript sources.
|
||||||
- If the edits affect regular users, edit the changelog.
|
- If the edits affect regular users, edit the changelog.
|
||||||
- Open a pull request.
|
- Open a pull request.
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -15,7 +15,8 @@
|
|||||||
"run_at": "document_start"
|
"run_at": "document_start"
|
||||||
}],
|
}],
|
||||||
"homepage_url": "http://seaweedchan.github.io/4chan-x/",
|
"homepage_url": "http://seaweedchan.github.io/4chan-x/",
|
||||||
"minimum_chrome_version": "26",
|
"minimum_chrome_version": "27",
|
||||||
|
"minimum_opera_version": "15",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"storage"
|
"storage"
|
||||||
]
|
]
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
1327
changelog-old
Normal file
1327
changelog-old
Normal file
File diff suppressed because it is too large
Load Diff
3
html/Monitoring/ThreadStats.html
Normal file
3
html/Monitoring/ThreadStats.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<div class="move" title="Post count / File count / Page count">
|
||||||
|
<span id="post-count">...</span> / <span id="file-count">...</span> / <span id="page-count">...</span>
|
||||||
|
</div>
|
||||||
38
html/Posting/QR.html
Normal file
38
html/Posting/QR.html
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<div>
|
||||||
|
<input type="checkbox" id="autohide" title="Auto-hide">
|
||||||
|
<select data-name="thread" title="Create a new thread / Reply">
|
||||||
|
<option value="new">New thread</option>
|
||||||
|
</select>
|
||||||
|
<span class="move"></span>
|
||||||
|
<a href="javascript:;" class="close" title="Close">×</a>
|
||||||
|
</div>
|
||||||
|
<form>
|
||||||
|
<div class="persona">
|
||||||
|
<input type="button" id="dump-button" title="Dump list" value="+">
|
||||||
|
<input data-name="name" list="list-name" placeholder="Name" class="field" size="1">
|
||||||
|
<input data-name="email" list="list-email" placeholder="E-mail" class="field" size="1">
|
||||||
|
<input data-name="sub" list="list-sub" placeholder="Subject" class="field" size="1">
|
||||||
|
</div>
|
||||||
|
<div id="dump-list-container">
|
||||||
|
<div id="dump-list"></div>
|
||||||
|
<a id="add-post" href="javascript:;" title="Add a post">+</a>
|
||||||
|
</div>
|
||||||
|
<div class="textarea">
|
||||||
|
<textarea data-name="com" placeholder="Comment" class="field"></textarea>
|
||||||
|
<span id="char-count"></span>
|
||||||
|
</div>
|
||||||
|
<div id="file-n-submit">
|
||||||
|
<input type="submit">
|
||||||
|
<input type="button" id="qr-file-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">×</a>
|
||||||
|
<input type="checkbox" id="qr-file-spoiler" title="Spoiler image">
|
||||||
|
</div>
|
||||||
|
<input type="file" multiple hidden>
|
||||||
|
</form>
|
||||||
|
<datalist id="list-name"></datalist>
|
||||||
|
<datalist id="list-email"></datalist>
|
||||||
|
<datalist id="list-sub"></datalist>
|
||||||
12
package.json
12
package.json
@ -21,15 +21,15 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"grunt": "~0.4.1",
|
"grunt": "~0.4.1",
|
||||||
"grunt-bump": "~0.0.2",
|
"grunt-bump": "~0.0.11",
|
||||||
"grunt-concurrent": "~0.2.0",
|
"grunt-concurrent": "~0.3.0",
|
||||||
"grunt-contrib-clean": "~0.4.1",
|
"grunt-contrib-clean": "~0.5.0",
|
||||||
"grunt-contrib-coffee": "~0.7.0",
|
"grunt-contrib-coffee": "~0.7.0",
|
||||||
"grunt-contrib-compress": "~0.5.1",
|
"grunt-contrib-compress": "~0.5.2",
|
||||||
"grunt-contrib-concat": "~0.3.0",
|
"grunt-contrib-concat": "~0.3.0",
|
||||||
"grunt-contrib-copy": "~0.4.1",
|
"grunt-contrib-copy": "~0.4.1",
|
||||||
"grunt-contrib-watch": "~0.4.4",
|
"grunt-contrib-watch": "~0.5.0",
|
||||||
"grunt-shell": "~0.2.2"
|
"grunt-shell": "~0.3.1"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@ -21,7 +21,6 @@ Redirect =
|
|||||||
Redirect.post[boardID] = archive
|
Redirect.post[boardID] = archive
|
||||||
unless boardID of Redirect.file or !archive.files.contains boardID
|
unless boardID of Redirect.file or !archive.files.contains boardID
|
||||||
Redirect.file[boardID] = archive
|
Redirect.file[boardID] = archive
|
||||||
return
|
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
'Foolz':
|
'Foolz':
|
||||||
@ -29,7 +28,7 @@ Redirect =
|
|||||||
'http': false
|
'http': false
|
||||||
'https': true
|
'https': true
|
||||||
'software': 'foolfuuka'
|
'software': 'foolfuuka'
|
||||||
'boards': ['a', 'co', 'gd', 'jp', 'm', 'q', 'sp', 'tg', 'tv', 'v', 'vg', 'vp', 'vr', 'wsg']
|
'boards': ['a', 'co', 'gd', 'jp', 'm', 'q', 'sp', 'tg', 'tv', 'vg', 'vp', 'vr', 'wsg']
|
||||||
'files': ['a', 'gd', 'jp', 'm', 'q', 'tg', 'vg', 'vp', 'vr', 'wsg']
|
'files': ['a', 'gd', 'jp', 'm', 'q', 'tg', 'vg', 'vp', 'vr', 'wsg']
|
||||||
|
|
||||||
'NSFW Foolz':
|
'NSFW Foolz':
|
||||||
@ -63,14 +62,6 @@ Redirect =
|
|||||||
'boards': ['c', 'w', 'wg']
|
'boards': ['c', 'w', 'wg']
|
||||||
'files': ['c', 'w', 'wg']
|
'files': ['c', 'w', 'wg']
|
||||||
|
|
||||||
'Love is Over':
|
|
||||||
'domain': 'loveisover.me'
|
|
||||||
'http': true
|
|
||||||
'https': true
|
|
||||||
'software': 'foolfuuka'
|
|
||||||
'boards': ['d', 'h', 'v']
|
|
||||||
'files': ['d', 'h', 'v']
|
|
||||||
|
|
||||||
'Foolz a Shit':
|
'Foolz a Shit':
|
||||||
'domain': 'archive.foolzashit.com'
|
'domain': 'archive.foolzashit.com'
|
||||||
'http': true
|
'http': true
|
||||||
@ -82,7 +73,7 @@ Redirect =
|
|||||||
'Install Gentoo':
|
'Install Gentoo':
|
||||||
'domain': 'archive.installgentoo.net'
|
'domain': 'archive.installgentoo.net'
|
||||||
'http': true
|
'http': true
|
||||||
'https': true
|
'https': false
|
||||||
'software': 'fuuka'
|
'software': 'fuuka'
|
||||||
'boards': ['diy', 'g', 'sci']
|
'boards': ['diy', 'g', 'sci']
|
||||||
'files': []
|
'files': []
|
||||||
@ -109,6 +100,14 @@ Redirect =
|
|||||||
'software': 'fuuka'
|
'software': 'fuuka'
|
||||||
'boards': ['3', 'cgl', 'ck', 'fa', 'ic', 'jp', 'lit', 'q', 'tg', 'vr']
|
'boards': ['3', 'cgl', 'ck', 'fa', 'ic', 'jp', 'lit', 'q', 'tg', 'vr']
|
||||||
'files': ['3', 'cgl', 'ck', 'fa', 'ic', 'jp', 'lit', 'q', 'vr']
|
'files': ['3', 'cgl', 'ck', 'fa', 'ic', 'jp', 'lit', 'q', 'vr']
|
||||||
|
|
||||||
|
'worldathleticproject':
|
||||||
|
'domain': 'fuuka.worldathleticproject.org'
|
||||||
|
'http': true
|
||||||
|
'https': true
|
||||||
|
'software': 'foolfuuka'
|
||||||
|
'boards': ['e', 'h', 'p', 's', 'u']
|
||||||
|
'files': ['e', 'h', 'p', 's', 'u']
|
||||||
|
|
||||||
to: (dest, data) ->
|
to: (dest, data) ->
|
||||||
archive = (if dest is 'search' then Redirect.thread else Redirect[dest])[data.boardID]
|
archive = (if dest is 'search' then Redirect.thread else Redirect[dest])[data.boardID]
|
||||||
|
|||||||
@ -209,7 +209,7 @@ Filter =
|
|||||||
el = $.el 'a',
|
el = $.el 'a',
|
||||||
href: 'javascript:;'
|
href: 'javascript:;'
|
||||||
textContent: text
|
textContent: text
|
||||||
el.setAttribute 'data-type', type
|
el.dataset.type = type
|
||||||
$.on el, 'click', Filter.menu.makeFilter
|
$.on el, 'click', Filter.menu.makeFilter
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -113,7 +113,7 @@ ThreadHiding =
|
|||||||
className: "#{type}-thread-button"
|
className: "#{type}-thread-button"
|
||||||
innerHTML: "<span class=fourchanx-link> #{if type is 'hide' then '-' else '+'} </span>"
|
innerHTML: "<span class=fourchanx-link> #{if type is 'hide' then '-' else '+'} </span>"
|
||||||
href: 'javascript:;'
|
href: 'javascript:;'
|
||||||
a.setAttribute 'data-fullid', thread.fullID
|
a.dataset.fullID = thread.fullID
|
||||||
$.on a, 'click', ThreadHiding.toggle
|
$.on a, 'click', ThreadHiding.toggle
|
||||||
a
|
a
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ ThreadHiding =
|
|||||||
|
|
||||||
toggle: (thread) ->
|
toggle: (thread) ->
|
||||||
unless thread instanceof Thread
|
unless thread instanceof Thread
|
||||||
thread = g.threads[@dataset.fullid]
|
thread = g.threads[@dataset.fullID]
|
||||||
if thread.isHidden
|
if thread.isHidden
|
||||||
ThreadHiding.show thread
|
ThreadHiding.show thread
|
||||||
else
|
else
|
||||||
|
|||||||
@ -108,12 +108,12 @@ Build =
|
|||||||
capcodeStart = ''
|
capcodeStart = ''
|
||||||
capcode = ''
|
capcode = ''
|
||||||
|
|
||||||
flag =
|
flag = unless flagCode
|
||||||
if flagCode
|
''
|
||||||
" <img src='#{staticPath}country/#{if boardID is 'pol' then 'troll/' else ''}" +
|
else if boardID is 'pol'
|
||||||
flagCode.toLowerCase() + ".gif' alt=#{flagCode} title='#{flagName}' class=countryFlag>"
|
" <img src='#{staticPath}country/troll/#{flagCode.toLowerCase()}.gif' alt=#{flagCode} title='#{flagName}' class=countryFlag>"
|
||||||
else
|
else
|
||||||
''
|
" <span title='#{flagName}' class='flag flag-#{flagCode.toLowerCase()}'></span>"
|
||||||
|
|
||||||
if file?.isDeleted
|
if file?.isDeleted
|
||||||
fileHTML = if isOP
|
fileHTML = if isOP
|
||||||
|
|||||||
@ -19,7 +19,7 @@ Get =
|
|||||||
if index then post.clones[index] else post
|
if index then post.clones[index] else post
|
||||||
postFromNode: (root) ->
|
postFromNode: (root) ->
|
||||||
Get.postFromRoot $.x 'ancestor::div[contains(@class,"postContainer")][1]', root
|
Get.postFromRoot $.x 'ancestor::div[contains(@class,"postContainer")][1]', root
|
||||||
contextFromLink: (quotelink) ->
|
contextFromNode: (quotelink) ->
|
||||||
Get.postFromRoot $.x 'ancestor::div[parent::div[@class="thread"]][1]', quotelink
|
Get.postFromRoot $.x 'ancestor::div[parent::div[@class="thread"]][1]', quotelink
|
||||||
postDataFromLink: (link) ->
|
postDataFromLink: (link) ->
|
||||||
if link.hostname is 'boards.4chan.org'
|
if link.hostname is 'boards.4chan.org'
|
||||||
@ -28,9 +28,8 @@ Get =
|
|||||||
threadID = path[3]
|
threadID = path[3]
|
||||||
postID = link.hash[2..]
|
postID = link.hash[2..]
|
||||||
else # resurrected quote
|
else # resurrected quote
|
||||||
boardID = link.dataset.boardid
|
{boardID, threadID, postID} = link.dataset
|
||||||
threadID = link.dataset.threadid or 0
|
threadID or= 0
|
||||||
postID = link.dataset.postid
|
|
||||||
return {
|
return {
|
||||||
boardID: boardID
|
boardID: boardID
|
||||||
threadID: +threadID
|
threadID: +threadID
|
||||||
@ -185,7 +184,7 @@ Get =
|
|||||||
# quotes
|
# quotes
|
||||||
.replace /((>){2}(>\/[a-z\d]+\/)?\d+)/g, '<span class=deadlink>$1</span>'
|
.replace /((>){2}(>\/[a-z\d]+\/)?\d+)/g, '<span class=deadlink>$1</span>'
|
||||||
|
|
||||||
threadID = data.thread_num
|
threadID = +data.thread_num
|
||||||
o =
|
o =
|
||||||
# id
|
# id
|
||||||
postID: "#{postID}"
|
postID: "#{postID}"
|
||||||
|
|||||||
@ -1,9 +1,3 @@
|
|||||||
<% 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 = {}
|
Conf = {}
|
||||||
c = console
|
c = console
|
||||||
d = document
|
d = document
|
||||||
|
|||||||
@ -70,7 +70,7 @@ Header =
|
|||||||
return unless Main.isThisPageLegit()
|
return unless Main.isThisPageLegit()
|
||||||
# Wait for #boardNavMobile instead of #boardNavDesktop,
|
# Wait for #boardNavMobile instead of #boardNavDesktop,
|
||||||
# it might be incomplete otherwise.
|
# it might be incomplete otherwise.
|
||||||
$.asap (-> $.id('boardNavMobile') or d.readyState in ['interactive', 'complete']), Header.setBoardList
|
$.asap (-> $.id('boardNavMobile') or d.readyState isnt 'loading'), Header.setBoardList
|
||||||
$.prepend d.body, @bar
|
$.prepend d.body, @bar
|
||||||
$.add d.body, Header.hover
|
$.add d.body, Header.hover
|
||||||
@setBarPosition Conf['Bottom Header']
|
@setBarPosition Conf['Bottom Header']
|
||||||
@ -131,7 +131,7 @@ Header =
|
|||||||
list = $ '#custom-board-list', Header.bar
|
list = $ '#custom-board-list', Header.bar
|
||||||
$.rmAll list
|
$.rmAll list
|
||||||
return unless text
|
return unless text
|
||||||
as = $$('#full-board-list a', Header.bar)
|
as = $$ '#full-board-list a[title]', Header.bar
|
||||||
nodes = text.match(/[\w@]+((-(all|title|replace|full|index|catalog|url:"[^"]+[^"]"|text:"[^"]+")|\,"[^"]+[^"]"))*|[^\w@]+/g).map (t) ->
|
nodes = text.match(/[\w@]+((-(all|title|replace|full|index|catalog|url:"[^"]+[^"]"|text:"[^"]+")|\,"[^"]+[^"]"))*|[^\w@]+/g).map (t) ->
|
||||||
if /^[^\w@]/.test t
|
if /^[^\w@]/.test t
|
||||||
return $.tn t
|
return $.tn t
|
||||||
@ -166,7 +166,7 @@ Header =
|
|||||||
a.textContent
|
a.textContent
|
||||||
|
|
||||||
if m = t.match /-(index|catalog)/
|
if m = t.match /-(index|catalog)/
|
||||||
a.setAttribute 'data-only', m[1]
|
a.dataset.only = m[1]
|
||||||
a.href = "//boards.4chan.org/#{board}/"
|
a.href = "//boards.4chan.org/#{board}/"
|
||||||
if m[1] is 'catalog'
|
if m[1] is 'catalog'
|
||||||
a.href += 'catalog'
|
a.href += 'catalog'
|
||||||
|
|||||||
@ -19,7 +19,7 @@ Main =
|
|||||||
$.get Conf, Main.initFeatures
|
$.get Conf, Main.initFeatures
|
||||||
|
|
||||||
$.on d, '4chanMainInit', Main.initStyle
|
$.on d, '4chanMainInit', Main.initStyle
|
||||||
$.asap (-> d.head and $('link[rel="shortcut icon"]', d.head) or d.readyState in ['interactive', 'complete']),
|
$.asap (-> d.head and $('link[rel="shortcut icon"]', d.head) or d.readyState isnt 'loading'),
|
||||||
Main.initStyle
|
Main.initStyle
|
||||||
|
|
||||||
initFeatures: (items) ->
|
initFeatures: (items) ->
|
||||||
@ -141,8 +141,6 @@ Main =
|
|||||||
<% if (type === 'crx') { %>
|
<% if (type === 'crx') { %>
|
||||||
$.addClass doc, 'webkit'
|
$.addClass doc, 'webkit'
|
||||||
$.addClass doc, 'blink'
|
$.addClass doc, 'blink'
|
||||||
<% } else if (type === 'userjs') { %>
|
|
||||||
$.addClass doc, 'presto'
|
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
$.addClass doc, 'gecko'
|
$.addClass doc, 'gecko'
|
||||||
<% } %>
|
<% } %>
|
||||||
@ -166,13 +164,9 @@ Main =
|
|||||||
$.addClass doc, style
|
$.addClass doc, style
|
||||||
setStyle()
|
setStyle()
|
||||||
return unless mainStyleSheet
|
return unless mainStyleSheet
|
||||||
if window.MutationObserver
|
new MutationObserver(setStyle).observe mainStyleSheet,
|
||||||
observer = new MutationObserver setStyle
|
attributes: true
|
||||||
observer.observe mainStyleSheet,
|
attributeFilter: ['href']
|
||||||
attributes: true
|
|
||||||
attributeFilter: ['href']
|
|
||||||
else
|
|
||||||
$.on mainStyleSheet, 'DOMAttrModified', setStyle
|
|
||||||
|
|
||||||
initReady: ->
|
initReady: ->
|
||||||
if d.title is '4chan - 404 Not Found'
|
if d.title is '4chan - 404 Not Found'
|
||||||
@ -192,20 +186,18 @@ Main =
|
|||||||
threads = []
|
threads = []
|
||||||
posts = []
|
posts = []
|
||||||
|
|
||||||
for boardChild in board.children
|
for threadRoot in $$ '.board > .thread', board
|
||||||
continue unless $.hasClass boardChild, 'thread'
|
thread = new Thread +threadRoot.id[1..], g.BOARD
|
||||||
thread = new Thread boardChild.id[1..], g.BOARD
|
|
||||||
threads.push thread
|
threads.push thread
|
||||||
for threadChild in boardChild.children
|
for postRoot in $$ '.thread > .postContainer', threadRoot
|
||||||
continue unless $.hasClass threadChild, 'postContainer'
|
|
||||||
try
|
try
|
||||||
posts.push new Post threadChild, thread, g.BOARD
|
posts.push new Post postRoot, thread, g.BOARD
|
||||||
catch err
|
catch err
|
||||||
# Skip posts that we failed to parse.
|
# Skip posts that we failed to parse.
|
||||||
unless errors
|
unless errors
|
||||||
errors = []
|
errors = []
|
||||||
errors.push
|
errors.push
|
||||||
message: "Parsing of Post No.#{threadChild.id.match(/\d+/)} failed. Post will be skipped."
|
message: "Parsing of Post No.#{postRoot.id.match(/\d+/)} failed. Post will be skipped."
|
||||||
error: err
|
error: err
|
||||||
Main.handleErrors errors if errors
|
Main.handleErrors errors if errors
|
||||||
|
|
||||||
@ -373,7 +365,7 @@ Main =
|
|||||||
unless 'thisPageIsLegit' of Main
|
unless 'thisPageIsLegit' of Main
|
||||||
Main.thisPageIsLegit = location.hostname is 'boards.4chan.org' and
|
Main.thisPageIsLegit = location.hostname is 'boards.4chan.org' and
|
||||||
!$('link[href*="favicon-status.ico"]', d.head) and
|
!$('link[href*="favicon-status.ico"]', d.head) and
|
||||||
d.title not in ['4chan - Temporarily Offline', '4chan - Error']
|
d.title not in ['4chan - Temporarily Offline', '4chan - Error', '504 Gateway Time-out']
|
||||||
Main.thisPageIsLegit
|
Main.thisPageIsLegit
|
||||||
|
|
||||||
css: """
|
css: """
|
||||||
|
|||||||
@ -466,7 +466,6 @@ Settings =
|
|||||||
$.cb.checked.call @
|
$.cb.checked.call @
|
||||||
usercss: ->
|
usercss: ->
|
||||||
CustomCSS.update()
|
CustomCSS.update()
|
||||||
|
|
||||||
keybinds: (section) ->
|
keybinds: (section) ->
|
||||||
section.innerHTML = """
|
section.innerHTML = """
|
||||||
<%= grunt.file.read('src/General/html/Settings/Keybinds.html').replace(/>\s+</g, '><').trim() %>
|
<%= grunt.file.read('src/General/html/Settings/Keybinds.html').replace(/>\s+</g, '><').trim() %>
|
||||||
|
|||||||
@ -306,12 +306,12 @@ UI = do ->
|
|||||||
|
|
||||||
hoverstart = ({root, el, latestEvent, endEvents, asapTest, cb}) ->
|
hoverstart = ({root, el, latestEvent, endEvents, asapTest, cb}) ->
|
||||||
o = {
|
o = {
|
||||||
root: root
|
root
|
||||||
el: el
|
el
|
||||||
style: el.style
|
style: el.style
|
||||||
cb: cb
|
cb
|
||||||
endEvents: endEvents
|
endEvents
|
||||||
latestEvent: latestEvent
|
latestEvent
|
||||||
clientHeight: doc.clientHeight
|
clientHeight: doc.clientHeight
|
||||||
clientWidth: doc.clientWidth
|
clientWidth: doc.clientWidth
|
||||||
}
|
}
|
||||||
@ -327,6 +327,11 @@ UI = do ->
|
|||||||
if $.x 'ancestor::div[contains(@class,"inline")][1]', root
|
if $.x 'ancestor::div[contains(@class,"inline")][1]', root
|
||||||
$.on d, 'keydown', o.hoverend
|
$.on d, 'keydown', o.hoverend
|
||||||
$.on root, 'mousemove', o.hover
|
$.on root, 'mousemove', o.hover
|
||||||
|
<% if (type === 'userscript') { %>
|
||||||
|
# Workaround for https://github.com/MayhemYDG/4chan-x/issues/377
|
||||||
|
o.workaround = (e) -> o.hoverend() unless root.contains e.target
|
||||||
|
$.on doc, 'mousemove', o.workaround
|
||||||
|
<% } %>
|
||||||
|
|
||||||
hover = (e) ->
|
hover = (e) ->
|
||||||
@latestEvent = e
|
@latestEvent = e
|
||||||
@ -357,6 +362,10 @@ UI = do ->
|
|||||||
$.off @root, @endEvents, @hoverend
|
$.off @root, @endEvents, @hoverend
|
||||||
$.off d, 'keydown', @hoverend
|
$.off d, 'keydown', @hoverend
|
||||||
$.off @root, 'mousemove', @hover
|
$.off @root, 'mousemove', @hover
|
||||||
|
<% if (type === 'userscript') { %>
|
||||||
|
# Workaround for https://github.com/MayhemYDG/4chan-x/issues/377
|
||||||
|
$.off doc, 'mousemove', @workaround
|
||||||
|
<% } %>
|
||||||
@cb.call @ if @cb
|
@cb.call @ if @cb
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -301,10 +301,8 @@ a {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
box-shadow: 0 0 15px rgba(0, 0, 0, .15);
|
box-shadow: 0 0 15px rgba(0, 0, 0, .15);
|
||||||
height: 600px;
|
height: 600px;
|
||||||
min-height: 0;
|
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
width: 900px;
|
width: 900px;
|
||||||
min-width: 0;
|
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
@ -312,7 +310,6 @@ a {
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
-moz-transform: translate(-50%, -50%);
|
-moz-transform: translate(-50%, -50%);
|
||||||
-webkit-transform: translate(-50%, -50%);
|
-webkit-transform: translate(-50%, -50%);
|
||||||
-o-transform: translate(-50%, -50%);
|
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
#fourchanx-settings > nav {
|
#fourchanx-settings > nav {
|
||||||
@ -389,14 +386,17 @@ a {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
.section-advanced .note {
|
.section-advanced .note {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
.section-advanced .note code {
|
.section-advanced .note code {
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
.section-keybinds .field {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
#fourchanx-settings fieldset {
|
#fourchanx-settings fieldset {
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@ -530,7 +530,8 @@ a.hide-announcement {
|
|||||||
.deadlink {
|
.deadlink {
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
}
|
}
|
||||||
.backlink.deadlink:not(.forwardlink), .quotelink.deadlink:not(.forwardlink) {
|
.backlink.deadlink:not(.forwardlink),
|
||||||
|
.quotelink.deadlink:not(.forwardlink) {
|
||||||
text-decoration: underline !important;
|
text-decoration: underline !important;
|
||||||
}
|
}
|
||||||
.inlined {
|
.inlined {
|
||||||
@ -573,8 +574,6 @@ a.hide-announcement {
|
|||||||
padding: 2px 2px 5px;
|
padding: 2px 2px 5px;
|
||||||
}
|
}
|
||||||
#qp img {
|
#qp img {
|
||||||
max-height: 300px;
|
|
||||||
max-width: 500px;
|
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
max-width: 50vw;
|
max-width: 50vw;
|
||||||
}
|
}
|
||||||
@ -610,8 +609,7 @@ a.hide-announcement {
|
|||||||
:root.fit-width .full-image {
|
:root.fit-width .full-image {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
:root.gecko.fit-width .full-image,
|
:root.gecko.fit-width .full-image {
|
||||||
:root.presto.fit-width .full-image {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
#ihover {
|
#ihover {
|
||||||
@ -668,7 +666,10 @@ a.hide-announcement {
|
|||||||
#file-n-submit:not(.has-file) #qr-filerm {
|
#file-n-submit:not(.has-file) #qr-filerm {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#qr select, #dump-button, .remove, .captcha-img {
|
#qr select,
|
||||||
|
#dump-button,
|
||||||
|
.remove,
|
||||||
|
.captcha-img {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
#qr {
|
#qr {
|
||||||
@ -725,7 +726,8 @@ a.hide-announcement {
|
|||||||
height: 9em;
|
height: 9em;
|
||||||
}
|
}
|
||||||
input.field.tripped:not(:hover):not(:focus) {
|
input.field.tripped:not(:hover):not(:focus) {
|
||||||
color: transparent !important; text-shadow: none !important;
|
color: transparent !important;
|
||||||
|
text-shadow: none !important;
|
||||||
}
|
}
|
||||||
#qr textarea {
|
#qr textarea {
|
||||||
resize: both;
|
resize: both;
|
||||||
@ -907,7 +909,9 @@ a:only-of-type > .remove {
|
|||||||
.qr-preview > label {
|
.qr-preview > label {
|
||||||
background: rgba(0,0,0,.5);
|
background: rgba(0,0,0,.5);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
right: 0; bottom: 0; left: 0;
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<div class=warning #{if Conf['Filter'] 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-US/docs/Web/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>
|
||||||
For example, <code>/weeaboo/i</code> will filter posts containing the string `<code>weeaboo</code>`, case-insensitive.<br>
|
For example, <code>/weeaboo/i</code> will filter posts containing the string `<code>weeaboo</code>`, case-insensitive.<br>
|
||||||
MD5 filtering uses exact string matching, not regular expressions.
|
MD5 filtering uses exact string matching, not regular expressions.
|
||||||
@ -26,4 +26,4 @@
|
|||||||
Highlighted OPs will have their threads put on top of board pages by default.<br>
|
Highlighted OPs will have their threads put on top of board pages by default.<br>
|
||||||
For example: <code>top:yes;</code> or <code>top:no;</code>.
|
For example: <code>top:yes;</code> or <code>top:no;</code>.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -49,7 +49,7 @@ $.id = (id) ->
|
|||||||
d.getElementById id
|
d.getElementById id
|
||||||
|
|
||||||
$.ready = (fc) ->
|
$.ready = (fc) ->
|
||||||
if d.readyState in ['interactive', 'complete']
|
unless d.readyState is 'loading'
|
||||||
$.queueTask fc
|
$.queueTask fc
|
||||||
return
|
return
|
||||||
cb = ->
|
cb = ->
|
||||||
@ -221,9 +221,7 @@ $.event = (event, detail, root=d) ->
|
|||||||
|
|
||||||
$.open = (URL) ->
|
$.open = (URL) ->
|
||||||
<% if (type === 'userscript') { %>
|
<% if (type === 'userscript') { %>
|
||||||
# XXX fix GM opening file://// for protocol-less URLs.
|
$.open = (URL) -> GM_openInTab URL
|
||||||
# https://github.com/greasemonkey/greasemonkey/issues/1719
|
|
||||||
GM_openInTab ($.el 'a', href: URL).href
|
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
window.open URL, '_blank'
|
window.open URL, '_blank'
|
||||||
<% } %>
|
<% } %>
|
||||||
@ -298,29 +296,21 @@ $.minmax = (value, min, max) ->
|
|||||||
value
|
value
|
||||||
)
|
)
|
||||||
|
|
||||||
$.syncing = {}
|
$.item = (key, val) ->
|
||||||
|
item = {}
|
||||||
|
item[key] = val
|
||||||
|
item
|
||||||
|
|
||||||
$.sync = do ->
|
$.syncing = {}
|
||||||
<% if (type === 'crx') { %>
|
<% if (type === 'crx') { %>
|
||||||
|
$.sync = do ->
|
||||||
chrome.storage.onChanged.addListener (changes) ->
|
chrome.storage.onChanged.addListener (changes) ->
|
||||||
for key of changes
|
for key of changes
|
||||||
if cb = $.syncing[key]
|
if cb = $.syncing[key]
|
||||||
cb changes[key].newValue
|
cb changes[key].newValue
|
||||||
return
|
return
|
||||||
(key, cb) -> $.syncing[key] = cb
|
(key, cb) -> $.syncing[key] = cb
|
||||||
<% } else { %>
|
|
||||||
window.addEventListener 'storage', (e) ->
|
|
||||||
if cb = $.syncing[e.key]
|
|
||||||
cb JSON.parse e.newValue
|
|
||||||
, false
|
|
||||||
(key, cb) -> $.syncing[g.NAMESPACE + key] = cb
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
$.item = (key, val) ->
|
|
||||||
item = {}
|
|
||||||
item[key] = val
|
|
||||||
item
|
|
||||||
<% if (type === 'crx') { %>
|
|
||||||
$.localKeys = [
|
$.localKeys = [
|
||||||
# filters
|
# filters
|
||||||
'name',
|
'name',
|
||||||
@ -372,6 +362,7 @@ $.get = (key, val, cb) ->
|
|||||||
if syncItems
|
if syncItems
|
||||||
count++
|
count++
|
||||||
chrome.storage.sync.get syncItems, done
|
chrome.storage.sync.get syncItems, done
|
||||||
|
|
||||||
$.set = do ->
|
$.set = do ->
|
||||||
items = {}
|
items = {}
|
||||||
localItems = {}
|
localItems = {}
|
||||||
@ -396,54 +387,14 @@ $.set = do ->
|
|||||||
$.extend items, key
|
$.extend items, key
|
||||||
set()
|
set()
|
||||||
|
|
||||||
<% } 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 = (keys) ->
|
|
||||||
unless keys instanceof Array
|
|
||||||
keys = [keys]
|
|
||||||
for key in keys
|
|
||||||
key = g.NAMESPACE + key
|
|
||||||
localStorage.removeItem key
|
|
||||||
delete scriptStorage[key]
|
|
||||||
return
|
|
||||||
$.get = (key, val, cb) ->
|
|
||||||
if typeof cb is 'function'
|
|
||||||
items = $.item key, val
|
|
||||||
else
|
|
||||||
items = key
|
|
||||||
cb = val
|
|
||||||
$.queueTask ->
|
|
||||||
for key of items
|
|
||||||
if val = scriptStorage[g.NAMESPACE + key]
|
|
||||||
items[key] = JSON.parse val
|
|
||||||
cb items
|
|
||||||
$.set = do ->
|
|
||||||
set = (key, val) ->
|
|
||||||
key = g.NAMESPACE + key
|
|
||||||
val = JSON.stringify val
|
|
||||||
if key of $.syncing
|
|
||||||
# for `storage` events
|
|
||||||
localStorage.setItem key, val
|
|
||||||
scriptStorage[key] = val
|
|
||||||
(keys, val) ->
|
|
||||||
if typeof keys is 'string'
|
|
||||||
set keys, val
|
|
||||||
return
|
|
||||||
for key, val of keys
|
|
||||||
set key, val
|
|
||||||
return
|
|
||||||
return
|
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
|
|
||||||
# http://wiki.greasespot.net/Main_Page
|
# http://wiki.greasespot.net/Main_Page
|
||||||
|
$.sync = do ->
|
||||||
|
$.on window, 'storage', (e) ->
|
||||||
|
if cb = $.syncing[e.key]
|
||||||
|
cb JSON.parse e.newValue
|
||||||
|
(key, cb) -> $.syncing[g.NAMESPACE + key] = cb
|
||||||
|
|
||||||
$.delete = (keys) ->
|
$.delete = (keys) ->
|
||||||
unless keys instanceof Array
|
unless keys instanceof Array
|
||||||
keys = [keys]
|
keys = [keys]
|
||||||
|
|||||||
@ -59,5 +59,4 @@ class Clone extends Post
|
|||||||
|
|
||||||
@isDead = true if origin.isDead
|
@isDead = true if origin.isDead
|
||||||
@isClone = true
|
@isClone = true
|
||||||
index = origin.clones.push(@) - 1
|
root.dataset.clone = origin.clones.push(@) - 1
|
||||||
root.setAttribute 'data-clone', index
|
|
||||||
|
|||||||
@ -16,29 +16,34 @@ class Post
|
|||||||
quotelinks: []
|
quotelinks: []
|
||||||
backlinks: info.getElementsByClassName 'backlink'
|
backlinks: info.getElementsByClassName 'backlink'
|
||||||
|
|
||||||
|
unless @isReply = $.hasClass post, 'reply'
|
||||||
|
@thread.OP = @
|
||||||
|
@thread.isSticky = !!$ '.stickyIcon', info
|
||||||
|
@thread.isClosed = !!$ '.closedIcon', info
|
||||||
|
|
||||||
@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.hand', info
|
if capcode = $ '.capcode.hand', info
|
||||||
@nodes.capcode = capcode
|
@nodes.capcode = capcode
|
||||||
@info.capcode = capcode.textContent.replace '## ', ''
|
@info.capcode = capcode.textContent.replace '## ', ''
|
||||||
if flag = $ '.countryFlag', info
|
if flag = $ '.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']
|
||||||
@ -50,43 +55,7 @@ class Post
|
|||||||
|
|
||||||
@parseComment()
|
@parseComment()
|
||||||
@parseQuotes()
|
@parseQuotes()
|
||||||
|
@parseFile(that)
|
||||||
if (file = $ '.file', post) and thumb = $ 'img[data-md5]', file
|
|
||||||
# Supports JPG/PNG/GIF/PDF.
|
|
||||||
# Flash files are not supported.
|
|
||||||
alt = thumb.alt
|
|
||||||
anchor = thumb.parentNode
|
|
||||||
fileInfo = file.firstElementChild
|
|
||||||
@file =
|
|
||||||
info: fileInfo
|
|
||||||
text: fileInfo.firstElementChild
|
|
||||||
thumb: thumb
|
|
||||||
URL: anchor.href
|
|
||||||
size: alt.match(/[\d.]+\s\w+/)[0]
|
|
||||||
MD5: thumb.dataset.md5
|
|
||||||
isSpoiler: $.hasClass anchor, 'imgspoiler'
|
|
||||||
size = +@file.size.match(/[\d.]+/)[0]
|
|
||||||
unit = ['B', 'KB', 'MB', 'GB'].indexOf @file.size.match(/\w+$/)[0]
|
|
||||||
size *= 1024 while unit-- > 0
|
|
||||||
@file.sizeInBytes = size
|
|
||||||
@file.thumbURL =
|
|
||||||
if that.isArchived
|
|
||||||
thumb.src
|
|
||||||
else
|
|
||||||
"#{location.protocol}//thumbs.4chan.org/#{board}/thumb/#{@file.URL.match(/(\d+)\./)[1]}s.jpg"
|
|
||||||
# replace %22 with quotes, see:
|
|
||||||
# crbug.com/81193
|
|
||||||
# webk.it/62107
|
|
||||||
# https://www.w3.org/Bugs/Public/show_bug.cgi?id=16909
|
|
||||||
# http://www.whatwg.org/specs/web-apps/current-work/#multipart-form-data
|
|
||||||
@file.name = $('span[title]', fileInfo).title.replace /%22/g, '"'
|
|
||||||
if @file.isImage = /(jpg|png|gif)$/i.test @file.name
|
|
||||||
@file.dimensions = @file.text.textContent.match(/\d+x\d+/)[0]
|
|
||||||
|
|
||||||
unless @isReply = $.hasClass post, 'reply'
|
|
||||||
@thread.OP = @
|
|
||||||
@thread.isSticky = !!$ '.stickyIcon', @nodes.info
|
|
||||||
@thread.isClosed = !!$ '.closedIcon', @nodes.info
|
|
||||||
|
|
||||||
@clones = []
|
@clones = []
|
||||||
g.posts[@fullID] = thread.posts[@] = board.posts[@] = @
|
g.posts[@fullID] = thread.posts[@] = board.posts[@] = @
|
||||||
@ -110,18 +79,18 @@ class Post
|
|||||||
nodes = d.evaluate './/br|.//text()', bq, null, 7, null
|
nodes = d.evaluate './/br|.//text()', bq, null, 7, null
|
||||||
i = 0
|
i = 0
|
||||||
while i < nodes.snapshotLength
|
while i < nodes.snapshotLength
|
||||||
text.push if data = nodes.snapshotItem(i++).data then data else '\n'
|
text.push nodes.snapshotItem(i++).data or '\n'
|
||||||
@info.comment = text.join('').trim().replace /\s+$/gm, ''
|
@info.comment = text.join('').trim().replace /\s+$/gm, ''
|
||||||
|
|
||||||
parseQuotes: ->
|
parseQuotes: ->
|
||||||
quotes = {}
|
quotes = {}
|
||||||
for quotelink in $$ '.quotelink', @nodes.comment
|
for quotelink in $$ '.quotelink', @nodes.comment
|
||||||
# Don't add board links. (>>>/b/)
|
# Don't add board links. (>>>/b/)
|
||||||
hash = quotelink.hash
|
{hash} = quotelink
|
||||||
continue unless hash
|
continue unless hash
|
||||||
|
|
||||||
# Don't add catalog links. (>>>/b/catalog or >>>/b/search)
|
# Don't add catalog links. (>>>/b/catalog or >>>/b/search)
|
||||||
pathname = quotelink.pathname
|
{pathname} = quotelink
|
||||||
continue if /catalog$/.test pathname
|
continue if /catalog$/.test pathname
|
||||||
|
|
||||||
# Don't add rules links. (>>>/a/rules)
|
# Don't add rules links. (>>>/a/rules)
|
||||||
@ -130,14 +99,49 @@ class Post
|
|||||||
|
|
||||||
@nodes.quotelinks.push quotelink
|
@nodes.quotelinks.push quotelink
|
||||||
|
|
||||||
# Don't count capcode replies as quotes. (Admin/Mod/Dev Replies: ...)
|
# Don't count capcode replies as quotes in OPs. (Admin/Mod/Dev Replies: ...)
|
||||||
continue if quotelink.parentNode.parentNode.className is 'capcodeReplies'
|
continue if !@isReply and $.hasClass quotelink.parentNode.parentNode, 'capcodeReplies'
|
||||||
|
|
||||||
# Basically, only add quotes that link to posts on an imageboard.
|
# Basically, only add quotes that link to posts on an imageboard.
|
||||||
quotes["#{pathname.split('/')[1]}.#{hash[2..]}"] = true
|
quotes["#{pathname.split('/')[1]}.#{hash[2..]}"] = true
|
||||||
return if @isClone
|
return if @isClone
|
||||||
@quotes = Object.keys quotes
|
@quotes = Object.keys quotes
|
||||||
|
|
||||||
|
parseFile: (that) ->
|
||||||
|
return unless (fileEl = $ '.file', @nodes.post) and thumb = $ 'img[data-md5]', fileEl
|
||||||
|
# Supports JPG/PNG/GIF/PDF.
|
||||||
|
# Flash files are not supported.
|
||||||
|
alt = thumb.alt
|
||||||
|
anchor = thumb.parentNode
|
||||||
|
fileInfo = fileEl.firstElementChild
|
||||||
|
@file =
|
||||||
|
info: fileInfo
|
||||||
|
text: fileInfo.firstElementChild
|
||||||
|
thumb: thumb
|
||||||
|
URL: anchor.href
|
||||||
|
size: alt.match(/[\d.]+\s\w+/)[0]
|
||||||
|
MD5: thumb.dataset.md5
|
||||||
|
isSpoiler: $.hasClass anchor, 'imgspoiler'
|
||||||
|
size = +@file.size.match(/[\d.]+/)[0]
|
||||||
|
unit = ['B', 'KB', 'MB', 'GB'].indexOf @file.size.match(/\w+$/)[0]
|
||||||
|
size *= 1024 while unit-- > 0
|
||||||
|
@file.sizeInBytes = size
|
||||||
|
@file.thumbURL = if that.isArchived
|
||||||
|
thumb.src
|
||||||
|
else
|
||||||
|
"#{location.protocol}//thumbs.4chan.org/#{@board}/thumb/#{@file.URL.match(/(\d+)\./)[1]}s.jpg"
|
||||||
|
@file.name = $('span[title]', fileInfo).title
|
||||||
|
<% if (type === 'crx') { %>
|
||||||
|
# replace %22 with quotes, see:
|
||||||
|
# crbug.com/81193
|
||||||
|
# webk.it/62107
|
||||||
|
# https://www.w3.org/Bugs/Public/show_bug.cgi?id=16909
|
||||||
|
# http://www.whatwg.org/specs/web-apps/current-work/#multipart-form-data
|
||||||
|
@file.name = @file.name.replace /%22/g, '"'
|
||||||
|
<% } %>
|
||||||
|
if @file.isImage = /(jpg|png|gif)$/i.test @file.name
|
||||||
|
@file.dimensions = @file.text.textContent.match(/\d+x\d+/)[0]
|
||||||
|
|
||||||
kill: (file, now) ->
|
kill: (file, now) ->
|
||||||
now or= new Date()
|
now or= new Date()
|
||||||
if file
|
if file
|
||||||
@ -192,10 +196,12 @@ class Post
|
|||||||
quotelink.textContent = quotelink.textContent.replace '\u00A0(Dead)', ''
|
quotelink.textContent = quotelink.textContent.replace '\u00A0(Dead)', ''
|
||||||
$.rmClass quotelink, 'deadlink'
|
$.rmClass quotelink, 'deadlink'
|
||||||
return
|
return
|
||||||
|
|
||||||
addClone: (context) ->
|
addClone: (context) ->
|
||||||
new Clone @, context
|
new Clone @, context
|
||||||
|
|
||||||
rmClone: (index) ->
|
rmClone: (index) ->
|
||||||
@clones.splice index, 1
|
@clones.splice index, 1
|
||||||
for clone in @clones[index..]
|
for clone in @clones[index..]
|
||||||
clone.nodes.root.setAttribute 'data-clone', index++
|
clone.nodes.root.dataset.clone = index++
|
||||||
return
|
return
|
||||||
|
|||||||
@ -2,8 +2,7 @@ class Thread
|
|||||||
callbacks: []
|
callbacks: []
|
||||||
toString: -> @ID
|
toString: -> @ID
|
||||||
|
|
||||||
constructor: (ID, @board) ->
|
constructor: (@ID, @board) ->
|
||||||
@ID = +ID
|
|
||||||
@fullID = "#{@board}.#{@ID}"
|
@fullID = "#{@board}.#{@ID}"
|
||||||
@posts = {}
|
@posts = {}
|
||||||
|
|
||||||
@ -11,4 +10,4 @@ class Thread
|
|||||||
|
|
||||||
kill: ->
|
kill: ->
|
||||||
@isDead = true
|
@isDead = true
|
||||||
@timeOfDeath = Date.now()
|
@timeOfDeath = Date.now()
|
||||||
|
|||||||
@ -15,7 +15,8 @@
|
|||||||
"run_at": "document_start"
|
"run_at": "document_start"
|
||||||
}],
|
}],
|
||||||
"homepage_url": "<%= meta.page %>",
|
"homepage_url": "<%= meta.page %>",
|
||||||
"minimum_chrome_version": "26",
|
"minimum_chrome_version": "27",
|
||||||
|
"minimum_opera_version": "15",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"storage"
|
"storage"
|
||||||
]
|
]
|
||||||
|
|||||||
@ -52,19 +52,6 @@ ImageExpand =
|
|||||||
return
|
return
|
||||||
setFitness: ->
|
setFitness: ->
|
||||||
(if @checked then $.addClass else $.rmClass) doc, @name.toLowerCase().replace /\s+/g, '-'
|
(if @checked then $.addClass else $.rmClass) doc, @name.toLowerCase().replace /\s+/g, '-'
|
||||||
<% if (type === 'userjs') { %>
|
|
||||||
# XXX Opera doesn't support CSS vh.
|
|
||||||
return unless @name is 'Fit height'
|
|
||||||
if @checked
|
|
||||||
$.on window, 'resize', ImageExpand.resize
|
|
||||||
unless ImageExpand.style
|
|
||||||
ImageExpand.style = $.addStyle null
|
|
||||||
ImageExpand.resize()
|
|
||||||
else
|
|
||||||
$.off window, 'resize', ImageExpand.resize
|
|
||||||
resize: ->
|
|
||||||
ImageExpand.style.textContent = ":root.fit-height .full-image {max-height:#{doc.clientHeight}px}"
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
toggle: (post) ->
|
toggle: (post) ->
|
||||||
{thumb} = post.file
|
{thumb} = post.file
|
||||||
|
|||||||
@ -13,7 +13,7 @@ ImageHover =
|
|||||||
el = $.el 'img',
|
el = $.el 'img',
|
||||||
id: 'ihover'
|
id: 'ihover'
|
||||||
src: post.file.URL
|
src: post.file.URL
|
||||||
el.setAttribute 'data-fullid', post.fullID
|
el.dataset.fullID = post.fullID
|
||||||
$.add Header.hover, el
|
$.add Header.hover, el
|
||||||
UI.hover
|
UI.hover
|
||||||
root: @
|
root: @
|
||||||
@ -24,7 +24,7 @@ ImageHover =
|
|||||||
$.on el, 'error', ImageHover.error
|
$.on el, 'error', ImageHover.error
|
||||||
error: ->
|
error: ->
|
||||||
return unless doc.contains @
|
return unless doc.contains @
|
||||||
post = g.posts[@dataset.fullid]
|
post = g.posts[@dataset.fullID]
|
||||||
|
|
||||||
src = @src.split '/'
|
src = @src.split '/'
|
||||||
if src[2] is 'images.4chan.org'
|
if src[2] is 'images.4chan.org'
|
||||||
@ -48,4 +48,4 @@ ImageHover =
|
|||||||
post.kill()
|
post.kill()
|
||||||
else if postObj.filedeleted
|
else if postObj.filedeleted
|
||||||
clearTimeout timeoutID
|
clearTimeout timeoutID
|
||||||
post.kill true
|
post.kill true
|
||||||
|
|||||||
@ -4,12 +4,10 @@ Sauce =
|
|||||||
|
|
||||||
links = []
|
links = []
|
||||||
for link in Conf['sauces'].split '\n'
|
for link in Conf['sauces'].split '\n'
|
||||||
continue if link[0] is '#'
|
|
||||||
try
|
try
|
||||||
links.push @createSauceLink link.trim()
|
links.push @createSauceLink link.trim() if link[0] isnt '#'
|
||||||
catch err
|
catch err
|
||||||
# Don't add random text plz.
|
# Don't add random text plz.
|
||||||
continue
|
|
||||||
return unless links.length
|
return unless links.length
|
||||||
@links = links
|
@links = links
|
||||||
@link = $.el 'a', target: '_blank'
|
@link = $.el 'a', target: '_blank'
|
||||||
@ -44,9 +44,8 @@ DeleteLink =
|
|||||||
return if DeleteLink.cooldown.counting is post
|
return if DeleteLink.cooldown.counting is post
|
||||||
|
|
||||||
$.off @, 'click', DeleteLink.delete
|
$.off @, 'click', DeleteLink.delete
|
||||||
@textContent = "Deleting #{@textContent}..."
|
|
||||||
|
|
||||||
fileOnly = $.hasClass @, 'delete-file'
|
fileOnly = $.hasClass @, 'delete-file'
|
||||||
|
@textContent = "Deleting #{if fileOnly then 'file' else 'post'}..."
|
||||||
|
|
||||||
form =
|
form =
|
||||||
mode: 'usrdel'
|
mode: 'usrdel'
|
||||||
|
|||||||
@ -8,29 +8,21 @@ Menu =
|
|||||||
cb: @node
|
cb: @node
|
||||||
|
|
||||||
node: ->
|
node: ->
|
||||||
button = Menu.makeButton @
|
|
||||||
if @isClone
|
if @isClone
|
||||||
$.replace $('.menu-button', @nodes.info), button
|
button = $ '.menu-button', @nodes.info
|
||||||
return
|
else
|
||||||
$.add @nodes.info, [$.tn('\u00A0'), button]
|
button = Menu.makeButton @
|
||||||
|
$.add @nodes.info, [$.tn('\u00A0'), button]
|
||||||
|
$.on button, 'click', Menu.toggle
|
||||||
|
|
||||||
makeButton: do ->
|
makeButton: do ->
|
||||||
a = null
|
a = null
|
||||||
(post) ->
|
->
|
||||||
a or= $.el 'a',
|
a or= $.el 'a',
|
||||||
className: 'menu-button fourchanx-link'
|
className: 'menu-button fourchanx-link'
|
||||||
innerHTML: '<i></i>'
|
innerHTML: '<i></i>'
|
||||||
href: 'javascript:;'
|
href: 'javascript:;'
|
||||||
clone = a.cloneNode true
|
a.cloneNode true
|
||||||
clone.setAttribute 'data-postid', post.fullID
|
|
||||||
clone.setAttribute 'data-clone', true if post.isClone
|
|
||||||
$.on clone, 'click', Menu.toggle
|
|
||||||
clone
|
|
||||||
|
|
||||||
toggle: (e) ->
|
toggle: (e) ->
|
||||||
post =
|
Menu.menu.toggle e, @, Get.postFromNode @
|
||||||
if @dataset.clone
|
|
||||||
Get.postFromNode @
|
|
||||||
else
|
|
||||||
g.posts[@dataset.postid]
|
|
||||||
Menu.menu.toggle e, @, post
|
|
||||||
|
|||||||
@ -95,10 +95,10 @@ Keybinds =
|
|||||||
window.location = "/#{g.BOARD}/catalog"
|
window.location = "/#{g.BOARD}/catalog"
|
||||||
# Thread Navigation
|
# Thread Navigation
|
||||||
when Conf['Next thread']
|
when Conf['Next thread']
|
||||||
return if g.VIEW is 'thread'
|
return if g.VIEW isnt 'index'
|
||||||
Nav.scroll +1
|
Nav.scroll +1
|
||||||
when Conf['Previous thread']
|
when Conf['Previous thread']
|
||||||
return if g.VIEW is 'thread'
|
return if g.VIEW isnt 'index'
|
||||||
Nav.scroll -1
|
Nav.scroll -1
|
||||||
when Conf['Expand thread']
|
when Conf['Expand thread']
|
||||||
ExpandThread.toggle thread
|
ExpandThread.toggle thread
|
||||||
|
|||||||
@ -60,8 +60,7 @@ Nav =
|
|||||||
# unless we're not at the beginning of the current thread
|
# unless we're not at the beginning of the current thread
|
||||||
# (and thus wanting to move to beginning)
|
# (and thus wanting to move to beginning)
|
||||||
# or we're above the first thread and don't want to skip it
|
# or we're above the first thread and don't want to skip it
|
||||||
unless (delta is -1 and Math.ceil(top) < 0) or (delta is +1 and top > 1)
|
if (delta is -1 and top > -5) or (delta is +1 and top < 5)
|
||||||
i += delta
|
top = threads[i + delta]?.getBoundingClientRect().top - topMargin
|
||||||
|
|
||||||
top = threads[i]?.getBoundingClientRect().top - topMargin
|
|
||||||
window.scrollBy 0, top
|
window.scrollBy 0, top
|
||||||
|
|||||||
@ -157,8 +157,7 @@ ThreadUpdater =
|
|||||||
By sending the `If-Modified-Since` header we get a proper status code, and no response.
|
By sending the `If-Modified-Since` header we get a proper status code, and no response.
|
||||||
This saves bandwidth for both the user and the servers and avoid unnecessary computation.
|
This saves bandwidth for both the user and the servers and avoid unnecessary computation.
|
||||||
###
|
###
|
||||||
# XXX 304 -> 0 in Opera
|
[text, klass] = if req.status is 304
|
||||||
[text, klass] = if [0, 304].contains req.status
|
|
||||||
[null, null]
|
[null, null]
|
||||||
else
|
else
|
||||||
["#{req.statusText} (#{req.status})", 'warning']
|
["#{req.statusText} (#{req.status})", 'warning']
|
||||||
|
|||||||
@ -36,13 +36,11 @@ Unread =
|
|||||||
# Let the header's onload callback handle it.
|
# Let the header's onload callback handle it.
|
||||||
return if (hash = location.hash.match /\d+/) and hash[0] of Unread.thread.posts
|
return if (hash = location.hash.match /\d+/) and hash[0] of Unread.thread.posts
|
||||||
if Unread.posts.length
|
if Unread.posts.length
|
||||||
# Scroll to before the first unread post.
|
# Scroll to a non-hidden, non-OP post that's before the first unread post.
|
||||||
prevID = 0
|
post = Unread.posts[0]
|
||||||
while root = $.x 'preceding-sibling::div[contains(@class,"postContainer")][1]', Unread.posts[0].nodes.root
|
while root = $.x 'preceding-sibling::div[contains(@class,"replyContainer")][1]', post.nodes.root
|
||||||
post = Get.postFromRoot root
|
break unless (post = Get.postFromRoot root).isHidden
|
||||||
break if prevID is post.ID
|
return unless root
|
||||||
prevID = post.ID
|
|
||||||
break unless post.isHidden
|
|
||||||
onload = -> root.scrollIntoView false if checkPosition root
|
onload = -> root.scrollIntoView false if checkPosition root
|
||||||
else
|
else
|
||||||
# Scroll to the last read post.
|
# Scroll to the last read post.
|
||||||
@ -188,9 +186,7 @@ Unread =
|
|||||||
else
|
else
|
||||||
Favicon.default
|
Favicon.default
|
||||||
|
|
||||||
<% if (type !== 'crx') { %>
|
<% if (type === 'userscript') { %>
|
||||||
# `favicon.href = href` doesn't work on Firefox.
|
# `favicon.href = href` doesn't work on Firefox.
|
||||||
# `favicon.href = href` isn't enough on Opera.
|
|
||||||
# Opera won't always update the favicon if the href didn't change.
|
|
||||||
$.add d.head, Favicon.el
|
$.add d.head, Favicon.el
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|||||||
@ -101,8 +101,8 @@ QR =
|
|||||||
$.rmClass QR.captcha.nodes.input, 'error'
|
$.rmClass QR.captcha.nodes.input, 'error'
|
||||||
if Conf['QR Shortcut']
|
if Conf['QR Shortcut']
|
||||||
$.toggleClass $('.qr-shortcut'), 'disabled'
|
$.toggleClass $('.qr-shortcut'), 'disabled'
|
||||||
for i in QR.posts
|
for post in QR.posts.splice 0, QR.posts.length, new QR.post true
|
||||||
QR.posts[0].rm()
|
post.delete()
|
||||||
QR.cooldown.auto = false
|
QR.cooldown.auto = false
|
||||||
QR.status()
|
QR.status()
|
||||||
focusin: ->
|
focusin: ->
|
||||||
@ -150,7 +150,8 @@ QR =
|
|||||||
|
|
||||||
status: ->
|
status: ->
|
||||||
return unless QR.nodes
|
return unless QR.nodes
|
||||||
if g.DEAD
|
{thread} = QR.posts[0]
|
||||||
|
if thread isnt 'new' and g.threads["#{g.BOARD}.#{thread}"].isDead
|
||||||
value = 404
|
value = 404
|
||||||
disabled = true
|
disabled = true
|
||||||
QR.cooldown.auto = false
|
QR.cooldown.auto = false
|
||||||
@ -371,14 +372,10 @@ QR =
|
|||||||
e?.preventDefault()
|
e?.preventDefault()
|
||||||
return unless QR.postingIsEnabled
|
return unless QR.postingIsEnabled
|
||||||
|
|
||||||
sel = d.getSelection()
|
sel = d.getSelection()
|
||||||
selectionRoot = $.x 'ancestor::div[contains(@class,"postContainer")][1]', sel.anchorNode
|
post = Get.postFromNode @
|
||||||
post = Get.postFromNode @
|
text = ">>#{post}\n"
|
||||||
{OP} = Get.contextFromLink(@).thread
|
if (s = sel.toString().trim()) and post is Get.postFromNode sel.anchorNode
|
||||||
|
|
||||||
text = ">>#{post}\n"
|
|
||||||
if (s = sel.toString().trim()) and post.nodes.root is selectionRoot
|
|
||||||
# XXX Opera doesn't retain `\n`s?
|
|
||||||
s = s.replace /\n/g, '\n>'
|
s = s.replace /\n/g, '\n>'
|
||||||
text += ">#{s}\n"
|
text += ">#{s}\n"
|
||||||
|
|
||||||
@ -389,7 +386,7 @@ QR =
|
|||||||
$.addClass QR.nodes.el, 'dump'
|
$.addClass QR.nodes.el, 'dump'
|
||||||
QR.cooldown.auto = true
|
QR.cooldown.auto = true
|
||||||
{com, thread} = QR.nodes
|
{com, thread} = QR.nodes
|
||||||
thread.value = OP.ID unless com.value
|
thread.value = Get.contextFromNode(@).thread unless com.value
|
||||||
|
|
||||||
caretPos = com.selectionStart
|
caretPos = com.selectionStart
|
||||||
# Replace selection for text.
|
# Replace selection for text.
|
||||||
@ -447,7 +444,7 @@ QR =
|
|||||||
QR.nodes.fileInput.click()
|
QR.nodes.fileInput.click()
|
||||||
|
|
||||||
fileInput: (files) ->
|
fileInput: (files) ->
|
||||||
if @ instanceof Element #or files instanceof Event # file input
|
if files instanceof Event # file input
|
||||||
files = [@files...]
|
files = [@files...]
|
||||||
QR.nodes.fileInput.value = null # Don't hold the files from being modified on windows
|
QR.nodes.fileInput.value = null # Don't hold the files from being modified on windows
|
||||||
{length} = files
|
{length} = files
|
||||||
@ -504,7 +501,7 @@ QR =
|
|||||||
for elm in $$ '*', el
|
for elm in $$ '*', el
|
||||||
$.on elm, 'blur', QR.focusout
|
$.on elm, 'blur', QR.focusout
|
||||||
$.on elm, 'focus', QR.focusin
|
$.on elm, 'focus', QR.focusin
|
||||||
<% } %>
|
<% } %>
|
||||||
$.on el, 'click', @select.bind @
|
$.on el, 'click', @select.bind @
|
||||||
$.on @nodes.rm, 'click', (e) => e.stopPropagation(); @rm()
|
$.on @nodes.rm, 'click', (e) => e.stopPropagation(); @rm()
|
||||||
$.on @nodes.label, 'click', (e) => e.stopPropagation()
|
$.on @nodes.label, 'click', (e) => e.stopPropagation()
|
||||||
@ -553,7 +550,7 @@ QR =
|
|||||||
@unlock()
|
@unlock()
|
||||||
|
|
||||||
rm: ->
|
rm: ->
|
||||||
$.rm @nodes.el
|
@delete()
|
||||||
index = QR.posts.indexOf @
|
index = QR.posts.indexOf @
|
||||||
if QR.posts.length is 1
|
if QR.posts.length is 1
|
||||||
new QR.post true
|
new QR.post true
|
||||||
@ -561,7 +558,9 @@ QR =
|
|||||||
else if @ is QR.selected
|
else if @ is QR.selected
|
||||||
(QR.posts[index-1] or QR.posts[index+1]).select()
|
(QR.posts[index-1] or QR.posts[index+1]).select()
|
||||||
QR.posts.splice index, 1
|
QR.posts.splice index, 1
|
||||||
return unless window.URL
|
QR.status()
|
||||||
|
delete: ->
|
||||||
|
$.rm @nodes.el
|
||||||
URL.revokeObjectURL @URL
|
URL.revokeObjectURL @URL
|
||||||
|
|
||||||
lock: (lock=true) ->
|
lock: (lock=true) ->
|
||||||
@ -603,15 +602,18 @@ QR =
|
|||||||
if input.type is 'checkbox'
|
if input.type is 'checkbox'
|
||||||
@spoiler = input.checked
|
@spoiler = input.checked
|
||||||
return
|
return
|
||||||
{value} = input
|
{name} = input.dataset
|
||||||
@[input.dataset.name] = value
|
@[name] = input.value
|
||||||
return if input.nodeName isnt 'TEXTAREA'
|
switch name
|
||||||
@nodes.span.textContent = value
|
when 'thread'
|
||||||
QR.characterCount()
|
QR.status()
|
||||||
# Disable auto-posting if you're typing in the first post
|
when 'com'
|
||||||
# during the last 5 seconds of the cooldown.
|
@nodes.span.textContent = @com
|
||||||
if QR.cooldown.auto and @ is QR.posts[0] and 0 < QR.cooldown.seconds <= 5
|
QR.characterCount()
|
||||||
QR.cooldown.auto = false
|
# Disable auto-posting if you're typing in the first post
|
||||||
|
# during the last 5 seconds of the cooldown.
|
||||||
|
if QR.cooldown.auto and @ is QR.posts[0] and 0 < QR.cooldown.seconds <= 5
|
||||||
|
QR.cooldown.auto = false
|
||||||
|
|
||||||
forceSave: ->
|
forceSave: ->
|
||||||
return unless @ is QR.selected
|
return unless @ is QR.selected
|
||||||
@ -625,26 +627,15 @@ QR =
|
|||||||
@filename = "#{file.name} (#{$.bytesToString file.size})"
|
@filename = "#{file.name} (#{$.bytesToString file.size})"
|
||||||
@nodes.el.title = @filename
|
@nodes.el.title = @filename
|
||||||
@nodes.label.hidden = false if QR.spoiler
|
@nodes.label.hidden = false if QR.spoiler
|
||||||
URL.revokeObjectURL @URL if window.URL
|
URL.revokeObjectURL @URL
|
||||||
@showFileData()
|
@showFileData()
|
||||||
unless /^image/.test file.type
|
unless /^image/.test file.type
|
||||||
@nodes.el.style.backgroundImage = null
|
@nodes.el.style.backgroundImage = null
|
||||||
return
|
return
|
||||||
@setThumbnail()
|
@setThumbnail()
|
||||||
|
|
||||||
setThumbnail: (fileURL) ->
|
setThumbnail: ->
|
||||||
# XXX Opera does not support blob URL
|
|
||||||
# Create a redimensioned thumbnail.
|
# Create a redimensioned thumbnail.
|
||||||
unless window.URL
|
|
||||||
unless fileURL
|
|
||||||
reader = new FileReader()
|
|
||||||
reader.onload = (e) =>
|
|
||||||
@setThumbnail e.target.result
|
|
||||||
reader.readAsDataURL @file
|
|
||||||
return
|
|
||||||
else
|
|
||||||
fileURL = URL.createObjectURL @file
|
|
||||||
|
|
||||||
img = $.el 'img'
|
img = $.el 'img'
|
||||||
|
|
||||||
img.onload = =>
|
img.onload = =>
|
||||||
@ -656,7 +647,7 @@ QR =
|
|||||||
s *= 3 if @file.type is 'image/gif' # let them animate
|
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
|
||||||
@nodes.el.style.backgroundImage = "url(#{@URL})"
|
@nodes.el.style.backgroundImage = "url(#{@URL})"
|
||||||
return
|
return
|
||||||
if height <= width
|
if height <= width
|
||||||
@ -669,10 +660,6 @@ QR =
|
|||||||
cv.height = img.height = height
|
cv.height = img.height = height
|
||||||
cv.width = img.width = width
|
cv.width = img.width = width
|
||||||
cv.getContext('2d').drawImage img, 0, 0, width, height
|
cv.getContext('2d').drawImage img, 0, 0, width, height
|
||||||
unless window.URL
|
|
||||||
@nodes.el.style.backgroundImage = "url(#{cv.toDataURL()})"
|
|
||||||
delete @URL
|
|
||||||
return
|
|
||||||
URL.revokeObjectURL fileURL
|
URL.revokeObjectURL fileURL
|
||||||
applyBlob = (blob) =>
|
applyBlob = (blob) =>
|
||||||
@URL = URL.createObjectURL blob
|
@URL = URL.createObjectURL blob
|
||||||
@ -690,6 +677,7 @@ QR =
|
|||||||
|
|
||||||
applyBlob new Blob [ui8a], type: 'image/png'
|
applyBlob new Blob [ui8a], type: 'image/png'
|
||||||
|
|
||||||
|
fileURL = URL.createObjectURL @file
|
||||||
img.src = fileURL
|
img.src = fileURL
|
||||||
|
|
||||||
rmFile: ->
|
rmFile: ->
|
||||||
@ -699,7 +687,6 @@ QR =
|
|||||||
@nodes.el.style.backgroundImage = null
|
@nodes.el.style.backgroundImage = null
|
||||||
@nodes.label.hidden = true if QR.spoiler
|
@nodes.label.hidden = true if QR.spoiler
|
||||||
@showFileData()
|
@showFileData()
|
||||||
return unless window.URL
|
|
||||||
URL.revokeObjectURL @URL
|
URL.revokeObjectURL @URL
|
||||||
|
|
||||||
showFileData: ->
|
showFileData: ->
|
||||||
@ -724,33 +711,26 @@ QR =
|
|||||||
@nodes.span.textContent = @com
|
@nodes.span.textContent = @com
|
||||||
reader.readAsText file
|
reader.readAsText file
|
||||||
|
|
||||||
dragStart: ->
|
dragStart: -> $.addClass @, 'drag'
|
||||||
$.addClass @, 'drag'
|
dragEnd: -> $.rmClass @, 'drag'
|
||||||
|
dragEnter: -> $.addClass @, 'over'
|
||||||
|
dragLeave: -> $.rmClass @, 'over'
|
||||||
|
|
||||||
dragEnd: ->
|
|
||||||
$.rmClass @, 'drag'
|
|
||||||
|
|
||||||
dragEnter: ->
|
|
||||||
$.addClass @, 'over'
|
|
||||||
|
|
||||||
dragLeave: ->
|
|
||||||
$.rmClass @, 'over'
|
|
||||||
|
|
||||||
dragOver: (e) ->
|
dragOver: (e) ->
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.dataTransfer.dropEffect = 'move'
|
e.dataTransfer.dropEffect = 'move'
|
||||||
|
|
||||||
drop: ->
|
drop: ->
|
||||||
el = $ '.drag', @parentNode
|
$.rmClass @, 'over'
|
||||||
$.rmClass el, 'drag' # Opera doesn't fire dragEnd if we drop it on something else
|
|
||||||
$.rmClass @, 'over'
|
|
||||||
return unless @draggable
|
return unless @draggable
|
||||||
|
el = $ '.drag', @parentNode
|
||||||
index = (el) -> [el.parentNode.children...].indexOf el
|
index = (el) -> [el.parentNode.children...].indexOf el
|
||||||
oldIndex = index el
|
oldIndex = index el
|
||||||
newIndex = index @
|
newIndex = index @
|
||||||
(if oldIndex < newIndex then $.after else $.before) @, el
|
(if oldIndex < newIndex then $.after else $.before) @, el
|
||||||
post = QR.posts.splice(oldIndex, 1)[0]
|
post = QR.posts.splice(oldIndex, 1)[0]
|
||||||
QR.posts.splice newIndex, 0, post
|
QR.posts.splice newIndex, 0, post
|
||||||
|
QR.status()
|
||||||
|
|
||||||
captcha:
|
captcha:
|
||||||
init: ->
|
init: ->
|
||||||
@ -779,19 +759,15 @@ QR =
|
|||||||
img: imgContainer.firstChild
|
img: imgContainer.firstChild
|
||||||
input: input
|
input: input
|
||||||
|
|
||||||
if window.MutationObserver
|
new MutationObserver(@load.bind @).observe @nodes.challenge,
|
||||||
observer = new MutationObserver @load.bind @
|
childList: true
|
||||||
observer.observe @nodes.challenge,
|
|
||||||
childList: true
|
|
||||||
else
|
|
||||||
$.on @nodes.challenge, 'DOMNodeInserted', @load.bind @
|
|
||||||
|
|
||||||
$.on imgContainer, 'click', @reload.bind @
|
$.on imgContainer, 'click', @reload.bind @
|
||||||
$.on input, 'keydown', @keydown.bind @
|
$.on input, 'keydown', @keydown.bind @
|
||||||
$.on input, 'focus', -> $.addClass QR.nodes.el, 'focus'
|
$.on input, 'focus', -> $.addClass QR.nodes.el, 'focus'
|
||||||
$.on input, 'blur', -> $.rmClass QR.nodes.el, 'focus'
|
$.on input, 'blur', -> $.rmClass QR.nodes.el, 'focus'
|
||||||
$.get 'captchas', [], (item) =>
|
$.get 'captchas', [], ({captchas}) =>
|
||||||
@sync item['captchas']
|
@sync captchas
|
||||||
$.sync 'captchas', @sync
|
$.sync 'captchas', @sync
|
||||||
# start with an uncached captcha
|
# start with an uncached captcha
|
||||||
@reload()
|
@reload()
|
||||||
@ -800,12 +776,13 @@ QR =
|
|||||||
# XXX Firefox lacks focusin/focusout support.
|
# XXX Firefox lacks focusin/focusout support.
|
||||||
$.on input, 'blur', QR.focusout
|
$.on input, 'blur', QR.focusout
|
||||||
$.on input, 'focus', QR.focusin
|
$.on input, 'focus', QR.focusin
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
$.addClass QR.nodes.el, 'has-captcha'
|
$.addClass QR.nodes.el, 'has-captcha'
|
||||||
$.after QR.nodes.com.parentNode, [imgContainer, input]
|
$.after QR.nodes.com.parentNode, [imgContainer, input]
|
||||||
|
|
||||||
sync: (@captchas) ->
|
sync: (captchas) ->
|
||||||
|
QR.captcha.captchas = captchas
|
||||||
QR.captcha.count()
|
QR.captcha.count()
|
||||||
|
|
||||||
getOne: ->
|
getOne: ->
|
||||||
@ -922,10 +899,6 @@ QR =
|
|||||||
# Add empty mimeType to avoid errors with URLs selected in Window's file dialog.
|
# Add empty mimeType to avoid errors with URLs selected in Window's file dialog.
|
||||||
QR.mimeTypes.push ''
|
QR.mimeTypes.push ''
|
||||||
nodes.fileInput.max = $('input[name=MAX_FILE_SIZE]').value
|
nodes.fileInput.max = $('input[name=MAX_FILE_SIZE]').value
|
||||||
<% if (type !== 'userjs') { %>
|
|
||||||
# Opera's accept attribute is fucked up
|
|
||||||
nodes.fileInput.accept = "text/*, #{mimeTypes}"
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
QR.spoiler = !!$ 'input[name=spoiler]'
|
QR.spoiler = !!$ 'input[name=spoiler]'
|
||||||
if QR.spoiler
|
if QR.spoiler
|
||||||
@ -960,7 +933,7 @@ QR =
|
|||||||
for elm in $$ '*', QR.nodes.el
|
for elm in $$ '*', QR.nodes.el
|
||||||
$.on elm, 'blur', QR.focusout
|
$.on elm, 'blur', QR.focusout
|
||||||
$.on elm, 'focus', QR.focusin
|
$.on elm, 'focus', QR.focusin
|
||||||
<% } %>
|
<% } %>
|
||||||
$.on dialog, 'focusin', QR.focusin
|
$.on dialog, 'focusin', QR.focusin
|
||||||
$.on dialog, 'focusout', QR.focusout
|
$.on dialog, 'focusout', QR.focusout
|
||||||
$.on nodes.autohide, 'change', QR.toggleHide
|
$.on nodes.autohide, 'change', QR.toggleHide
|
||||||
@ -1109,11 +1082,6 @@ QR =
|
|||||||
QR.status()
|
QR.status()
|
||||||
|
|
||||||
response: ->
|
response: ->
|
||||||
<% if (type === 'userjs') { %>
|
|
||||||
# The upload.onload callback is not called
|
|
||||||
# or at least not in time with Opera.
|
|
||||||
QR.req.upload.onload()
|
|
||||||
<% } %>
|
|
||||||
{req} = QR
|
{req} = QR
|
||||||
delete QR.req
|
delete QR.req
|
||||||
|
|
||||||
@ -1206,15 +1174,15 @@ QR =
|
|||||||
|
|
||||||
QR.cooldown.set {req, post, isReply}
|
QR.cooldown.set {req, post, isReply}
|
||||||
|
|
||||||
if threadID is postID # new thread
|
URL = if threadID is postID # new thread
|
||||||
URL = "/#{g.BOARD}/res/#{threadID}"
|
"/#{g.BOARD}/res/#{threadID}"
|
||||||
else if g.VIEW is 'index' and !QR.cooldown.auto and Conf['Open Post in New Tab'] # replying 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}"
|
"/#{g.BOARD}/res/#{threadID}#p#{postID}"
|
||||||
if URL
|
if URL
|
||||||
if Conf['Open Post in New Tab']
|
if Conf['Open Post in New Tab']
|
||||||
$.open "/#{g.BOARD}/res/#{threadID}"
|
$.open URL
|
||||||
else
|
else
|
||||||
window.location = "/#{g.BOARD}/res/#{threadID}"
|
window.location = URL
|
||||||
|
|
||||||
QR.status()
|
QR.status()
|
||||||
|
|
||||||
|
|||||||
@ -35,7 +35,7 @@ QuoteInline =
|
|||||||
return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0
|
return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
{boardID, threadID, postID} = Get.postDataFromLink @
|
{boardID, threadID, postID} = Get.postDataFromLink @
|
||||||
context = Get.contextFromLink @
|
context = Get.contextFromNode @
|
||||||
if $.hasClass @, 'inlined'
|
if $.hasClass @, 'inlined'
|
||||||
QuoteInline.rm @, boardID, threadID, postID, context
|
QuoteInline.rm @, boardID, threadID, postID, context
|
||||||
else
|
else
|
||||||
@ -107,4 +107,4 @@ QuoteInline =
|
|||||||
{boardID, threadID, postID} = Get.postDataFromLink inlined
|
{boardID, threadID, postID} = Get.postDataFromLink inlined
|
||||||
QuoteInline.rm inlined, boardID, threadID, postID, context
|
QuoteInline.rm inlined, boardID, threadID, postID, context
|
||||||
$.rmClass inlined, 'inlined'
|
$.rmClass inlined, 'inlined'
|
||||||
return
|
return
|
||||||
|
|||||||
@ -22,8 +22,9 @@ QuotePreview =
|
|||||||
qp = $.el 'div',
|
qp = $.el 'div',
|
||||||
id: 'qp'
|
id: 'qp'
|
||||||
className: 'dialog'
|
className: 'dialog'
|
||||||
|
|
||||||
$.add Header.hover, qp
|
$.add Header.hover, qp
|
||||||
Get.postClone boardID, threadID, postID, qp, Get.contextFromLink @
|
Get.postClone boardID, threadID, postID, qp, Get.contextFromNode @
|
||||||
|
|
||||||
UI.hover
|
UI.hover
|
||||||
root: @
|
root: @
|
||||||
@ -33,21 +34,6 @@ QuotePreview =
|
|||||||
cb: QuotePreview.mouseout
|
cb: QuotePreview.mouseout
|
||||||
asapTest: -> qp.firstElementChild
|
asapTest: -> qp.firstElementChild
|
||||||
|
|
||||||
<% if (type === 'userjs') { %>
|
|
||||||
# XXX Opera workaround for "no mouseout fired" bug.
|
|
||||||
# Remove it once Opera uses Blink.
|
|
||||||
root = @
|
|
||||||
workaround = (e) ->
|
|
||||||
if @ is root
|
|
||||||
e.stopPropagation()
|
|
||||||
return
|
|
||||||
$.event 'mouseout', null, root
|
|
||||||
$.off d, 'mousemove', workaround
|
|
||||||
$.off root, 'mousemove', workaround
|
|
||||||
$.on d, 'mousemove', workaround
|
|
||||||
$.on root, 'mousemove', workaround
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
return unless origin = g.posts["#{boardID}.#{postID}"]
|
return unless origin = g.posts["#{boardID}.#{postID}"]
|
||||||
|
|
||||||
if Conf['Quote Highlighting']
|
if Conf['Quote Highlighting']
|
||||||
|
|||||||
@ -21,6 +21,9 @@ Quotify =
|
|||||||
if deadlink.parentNode.className is 'prettyprint'
|
if deadlink.parentNode.className is 'prettyprint'
|
||||||
# Don't quotify deadlinks inside code tags,
|
# Don't quotify deadlinks inside code tags,
|
||||||
# un-`span` them.
|
# un-`span` them.
|
||||||
|
# This won't be necessary once 4chan
|
||||||
|
# stops quotifying inside code tags:
|
||||||
|
# https://github.com/4chan/4chan-JS/issues/77
|
||||||
$.replace deadlink, [deadlink.childNodes...]
|
$.replace deadlink, [deadlink.childNodes...]
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -48,9 +51,8 @@ Quotify =
|
|||||||
target: '_blank'
|
target: '_blank'
|
||||||
textContent: "#{quote}\u00A0(Dead)"
|
textContent: "#{quote}\u00A0(Dead)"
|
||||||
|
|
||||||
a.setAttribute 'data-boardid', boardID
|
$.extend a.dataset, {boardID, threadID: post.thread.ID, postID}
|
||||||
a.setAttribute 'data-threadid', post.thread.ID
|
|
||||||
a.setAttribute 'data-postid', postID
|
|
||||||
else if redirect = Redirect.to 'thread', {boardID, threadID: 0, postID}
|
else if redirect = Redirect.to 'thread', {boardID, threadID: 0, postID}
|
||||||
# Replace the .deadlink span if we can redirect.
|
# Replace the .deadlink span if we can redirect.
|
||||||
a = $.el 'a',
|
a = $.el 'a',
|
||||||
@ -60,9 +62,8 @@ Quotify =
|
|||||||
textContent: "#{quote}\u00A0(Dead)"
|
textContent: "#{quote}\u00A0(Dead)"
|
||||||
if Redirect.to 'post', {boardID, postID}
|
if Redirect.to 'post', {boardID, postID}
|
||||||
# Make it function as a normal quote if we can fetch the post.
|
# Make it function as a normal quote if we can fetch the post.
|
||||||
$.addClass a, 'quotelink'
|
$.addClass a, 'quotelink'
|
||||||
a.setAttribute 'data-boardid', boardID
|
$.extend a.dataset, {boardID, postID}
|
||||||
a.setAttribute 'data-postid', postID
|
|
||||||
|
|
||||||
unless @quotes.contains quoteID
|
unless @quotes.contains quoteID
|
||||||
@quotes.push quoteID
|
@quotes.push quoteID
|
||||||
@ -73,4 +74,4 @@ Quotify =
|
|||||||
|
|
||||||
$.replace deadlink, a
|
$.replace deadlink, a
|
||||||
if $.hasClass a, 'quotelink'
|
if $.hasClass a, 'quotelink'
|
||||||
@nodes.quotelinks.push a
|
@nodes.quotelinks.push a
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user