Merge branch 'v3'
Conflicts: .gitignore CHANGELOG.md Gruntfile.coffee LICENSE builds/crx/manifest.json builds/crx/script.js latest.js package.json src/General/Config.coffee src/General/Globals.coffee src/General/Header.coffee src/General/Main.coffee src/General/UI.coffee src/General/css/style.css src/General/meta/manifest.json src/Posting/QuickReply.coffee
This commit is contained in:
commit
16b35dffce
9
.gitignore
vendored
9
.gitignore
vendored
@ -2,9 +2,14 @@ node_modules/
|
||||
*~
|
||||
*.db
|
||||
tmp-crx/
|
||||
tmp-userjs/
|
||||
tmp-userscript/
|
||||
<<<<<<< HEAD
|
||||
builds/4chan-X.zip
|
||||
Gruntfile.js
|
||||
builds/4chan-*
|
||||
Gruntfile.js
|
||||
Gruntfile.js
|
||||
=======
|
||||
builds/4chan-X-Chrome.zip
|
||||
builds/4chan-X-Opera.nex
|
||||
Gruntfile.js
|
||||
>>>>>>> v3
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
**MayhemYDG**:
|
||||
- Remove /s4s/ from warosu archive
|
||||
- Fix CAPTCHA duplication on the report page
|
||||
- Small bug fixes
|
||||
- Fix impossibility to create new threads when in dead threads.
|
||||
- Drop Opera <15 support.
|
||||
- Fix flag filtering on /sp/ and /int/.
|
||||
- Minor fixes.
|
||||
|
||||
**seaweedchan**:
|
||||
- Add `.active` class to `.menu-button` when clicked (and remove on menu close)
|
||||
@ -9,6 +12,10 @@
|
||||
- Revert Mayhem's updater changes which caused silly issues
|
||||
- Rename `Indicate Spoilers` to `Reveal Spoilers`
|
||||
- If `Reveal Spoilers` is enabled but `Remove Spoilers` is not, act as if the spoiler is hovered
|
||||
- Add a new option to hide "4chan X has been updated to ____" notifications for those having issues with them.
|
||||
- Update archives
|
||||
- Add `.active` class to `.menu-button` when clicked (and remove on menu close)
|
||||
- Move /v/ and /vg/ back to Foolz archive
|
||||
|
||||
**Tracerneo**:
|
||||
- Add ID styling for IDs with black text
|
||||
|
||||
@ -183,7 +183,6 @@ module.exports = (grunt) ->
|
||||
grunt.registerTask 'release', [
|
||||
'default'
|
||||
'compress:crx'
|
||||
'copy:opera'
|
||||
'shell:commit'
|
||||
'shell:push'
|
||||
]
|
||||
|
||||
2
LICENSE
2
LICENSE
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* appchan x - Version 2.1.3 - 2013-07-07
|
||||
* appchan x - Version 2.1.3 - 2013-07-21
|
||||
*
|
||||
* Licensed under the MIT license.
|
||||
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
// ==UserScript==
|
||||
// @name 4chan X
|
||||
// @version 1.2.17
|
||||
// @version 1.2.19
|
||||
// @namespace 4chan-X
|
||||
// @description Cross-browser userscript for maximum lurking on 4chan.
|
||||
// @license MIT; https://github.com/seaweedchan/4chan-x/blob/master/LICENSE
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,7 @@
|
||||
}],
|
||||
"homepage_url": "http://zixaphir.github.com/appchan-x/",
|
||||
"minimum_chrome_version": "24",
|
||||
"minimum_opera_version": "15",
|
||||
"permissions": [
|
||||
"storage"
|
||||
]
|
||||
|
||||
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
@ -20,15 +20,15 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-bump": "~0.0.2",
|
||||
"grunt-concurrent": "~0.2.0",
|
||||
"grunt-contrib-clean": "~0.4.1",
|
||||
"grunt-bump": "~0.0.11",
|
||||
"grunt-concurrent": "~0.3.0",
|
||||
"grunt-contrib-clean": "~0.5.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-copy": "~0.4.1",
|
||||
"grunt-contrib-watch": "~0.4.4",
|
||||
"grunt-shell": "~0.2.2"
|
||||
"grunt-contrib-watch": "~0.5.0",
|
||||
"grunt-shell": "~0.3.1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@ -21,7 +21,6 @@ Redirect =
|
||||
Redirect.post[boardID] = archive
|
||||
unless boardID of Redirect.file or !archive.files.contains boardID
|
||||
Redirect.file[boardID] = archive
|
||||
return
|
||||
|
||||
archives:
|
||||
'Foolz':
|
||||
@ -29,7 +28,7 @@ Redirect =
|
||||
'http': false
|
||||
'https': true
|
||||
'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']
|
||||
|
||||
'NSFW Foolz':
|
||||
@ -63,25 +62,17 @@ Redirect =
|
||||
'boards': ['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':
|
||||
'domain': 'archive.foolzashit.com'
|
||||
'http': true
|
||||
'https': true
|
||||
'software': 'foolfuuka'
|
||||
'boards': ['adv', 'asp', 'cm', 'e', 'i', 'lgbt', 'n', 'o', 'p', 'pol', 's', 's4s', 't', 'trv', 'y']
|
||||
'files': ['adv', 'asp', 'cm', 'e', 'i', 'lgbt', 'n', 'o', 'p', 's', 's4s', 't', 'trv', 'y']
|
||||
'boards': ['adv', 'asp', 'cm', 'i', 'lgbt', 'n', 'o', 'p', 's4s', 't', 'trv']
|
||||
'files': ['adv', 'asp', 'cm', 'i', 'lgbt', 'n', 'o', 'p', 's4s', 't', 'trv']
|
||||
|
||||
'Install Gentoo':
|
||||
'domain': 'archive.installgentoo.net'
|
||||
'http': true
|
||||
'http': false
|
||||
'https': true
|
||||
'software': 'fuuka'
|
||||
'boards': ['diy', 'g', 'sci']
|
||||
@ -109,6 +100,14 @@ Redirect =
|
||||
'software': 'fuuka'
|
||||
'boards': ['3', 'cgl', 'ck', 'fa', 'ic', 'jp', 'lit', 'q', 'tg', '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) ->
|
||||
archive = (if dest is 'search' then Redirect.thread else Redirect[dest])[data.boardID]
|
||||
|
||||
@ -209,7 +209,7 @@ Filter =
|
||||
el = $.el 'a',
|
||||
href: 'javascript:;'
|
||||
textContent: text
|
||||
el.setAttribute 'data-type', type
|
||||
el.dataset.type = type
|
||||
$.on el, 'click', Filter.menu.makeFilter
|
||||
|
||||
return {
|
||||
|
||||
@ -113,7 +113,7 @@ ThreadHiding =
|
||||
className: "#{type}-thread-button"
|
||||
innerHTML: "<span class=brackets-wrap> #{if type is 'hide' then '-' else '+'} </span>"
|
||||
href: 'javascript:;'
|
||||
a.setAttribute 'data-fullid', thread.fullID
|
||||
a.dataset.fullID = thread.fullID
|
||||
$.on a, 'click', ThreadHiding.toggle
|
||||
a
|
||||
|
||||
@ -134,7 +134,7 @@ ThreadHiding =
|
||||
|
||||
toggle: (thread) ->
|
||||
unless thread instanceof Thread
|
||||
thread = g.threads[@dataset.fullid]
|
||||
thread = g.threads[@dataset.fullID]
|
||||
if thread.isHidden
|
||||
ThreadHiding.show thread
|
||||
else
|
||||
|
||||
@ -108,12 +108,12 @@ Build =
|
||||
capcodeStart = ''
|
||||
capcode = ''
|
||||
|
||||
flag =
|
||||
if flagCode
|
||||
" <img src='#{staticPath}country/#{if boardID is 'pol' then 'troll/' else ''}" +
|
||||
flagCode.toLowerCase() + ".gif' alt=#{flagCode} title='#{flagName}' class=countryFlag>"
|
||||
else
|
||||
''
|
||||
flag = unless flagCode
|
||||
''
|
||||
else if boardID is 'pol'
|
||||
" <img src='#{staticPath}country/troll/#{flagCode.toLowerCase()}.gif' alt=#{flagCode} title='#{flagName}' class=countryFlag>"
|
||||
else
|
||||
" <span title='#{flagName}' class='flag flag-#{flagCode.toLowerCase()}'></span>"
|
||||
|
||||
if file?.isDeleted
|
||||
fileHTML = if isOP
|
||||
|
||||
@ -19,7 +19,7 @@ Get =
|
||||
if index then post.clones[index] else post
|
||||
postFromNode: (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
|
||||
postDataFromLink: (link) ->
|
||||
if link.hostname is 'boards.4chan.org'
|
||||
@ -28,9 +28,8 @@ Get =
|
||||
threadID = path[3]
|
||||
postID = link.hash[2..]
|
||||
else # resurrected quote
|
||||
boardID = link.dataset.boardid
|
||||
threadID = link.dataset.threadid or 0
|
||||
postID = link.dataset.postid
|
||||
{boardID, threadID, postID} = link.dataset
|
||||
threadID or= 0
|
||||
return {
|
||||
boardID: boardID
|
||||
threadID: +threadID
|
||||
@ -185,7 +184,7 @@ Get =
|
||||
# quotes
|
||||
.replace /((>){2}(>\/[a-z\d]+\/)?\d+)/g, '<span class=deadlink>$1</span>'
|
||||
|
||||
threadID = data.thread_num
|
||||
threadID = +data.thread_num
|
||||
o =
|
||||
# id
|
||||
postID: "#{postID}"
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
editTheme = {} # Currently editted theme.
|
||||
editMascot = {} # Which mascot we're editting.
|
||||
userNavigation = {} # ...
|
||||
|
||||
editTheme = {}
|
||||
editMascot = {}
|
||||
userNavigation = {}
|
||||
Conf = {}
|
||||
c = console
|
||||
d = document
|
||||
@ -3074,4 +3075,4 @@ textarea,
|
||||
border: 1px solid #111 !important;
|
||||
background-color: #933;
|
||||
}
|
||||
"""
|
||||
"""
|
||||
|
||||
@ -55,7 +55,7 @@ Header =
|
||||
return unless Main.isThisPageLegit()
|
||||
# Wait for #boardNavMobile instead of #boardNavDesktop,
|
||||
# it might be incomplete otherwise.
|
||||
$.asap (-> $.id('boardNavMobile') or d.readyState in ['interactive', 'complete']), @setBoardList
|
||||
$.asap (-> $.id('boardNavMobile') or d.readyState isnt 'loading'), Header.setBoardList
|
||||
$.prepend d.body, @bar
|
||||
$.add d.body, Header.hover
|
||||
@setBarPosition Conf['Bottom Header']
|
||||
@ -106,7 +106,7 @@ Header =
|
||||
list = $ '#custom-board-list', Header.bar
|
||||
$.rmAll list
|
||||
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) ->
|
||||
if /^[^\w@]/.test t
|
||||
return $.tn t
|
||||
@ -141,7 +141,7 @@ Header =
|
||||
a.textContent
|
||||
|
||||
if m = t.match /-(index|catalog)/
|
||||
a.setAttribute 'data-only', m[1]
|
||||
a.dataset.only = m[1]
|
||||
a.href = "//boards.4chan.org/#{board}/"
|
||||
if m[1] is 'catalog'
|
||||
a.href += 'catalog'
|
||||
|
||||
@ -191,20 +191,18 @@ Main =
|
||||
threads = []
|
||||
posts = []
|
||||
|
||||
for boardChild in board.children
|
||||
continue unless $.hasClass boardChild, 'thread'
|
||||
thread = new Thread boardChild.id[1..], g.BOARD
|
||||
for threadRoot in $$ '.board > .thread', board
|
||||
thread = new Thread +threadRoot.id[1..], g.BOARD
|
||||
threads.push thread
|
||||
for threadChild in boardChild.children
|
||||
continue unless $.hasClass threadChild, 'postContainer'
|
||||
for postRoot in $$ '.thread > .postContainer', threadRoot
|
||||
try
|
||||
posts.push new Post threadChild, thread, g.BOARD
|
||||
posts.push new Post postRoot, thread, g.BOARD
|
||||
catch err
|
||||
# Skip posts that we failed to parse.
|
||||
unless errors
|
||||
errors = []
|
||||
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
|
||||
Main.handleErrors errors if errors
|
||||
|
||||
@ -372,7 +370,7 @@ Main =
|
||||
unless 'thisPageIsLegit' of Main
|
||||
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']
|
||||
d.title not in ['4chan - Temporarily Offline', '4chan - Error', '504 Gateway Time-out']
|
||||
Main.thisPageIsLegit
|
||||
|
||||
Main.init()
|
||||
|
||||
@ -24,7 +24,8 @@ Settings =
|
||||
changelog = '<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md'
|
||||
el = $.el 'span',
|
||||
innerHTML: "<%= meta.name %> has been updated to <a href='#{changelog}' target=_blank>version #{g.VERSION}</a>."
|
||||
new Notification 'info', el, 30
|
||||
if Conf['Show Updated Notifications']
|
||||
new Notification 'info', el, 30
|
||||
else
|
||||
$.on d, '4chanXInitFinished', Settings.open
|
||||
$.set
|
||||
@ -427,7 +428,6 @@ Settings =
|
||||
|
||||
usercss: ->
|
||||
CustomCSS.update()
|
||||
|
||||
keybinds: (section) ->
|
||||
section.innerHTML = """
|
||||
<%= grunt.file.read('src/General/html/Settings/Keybinds.html').replace(/>\s+</g, '><').trim() %>
|
||||
|
||||
@ -303,13 +303,13 @@ UI = do ->
|
||||
|
||||
hoverstart = ({root, el, latestEvent, endEvents, asapTest, cb, close}) ->
|
||||
o = {
|
||||
root: root
|
||||
el: el
|
||||
style: el.style
|
||||
cb: cb
|
||||
root
|
||||
el
|
||||
style: el.style
|
||||
cb
|
||||
close: close
|
||||
endEvents: endEvents
|
||||
latestEvent: latestEvent
|
||||
endEvents
|
||||
latestEvent
|
||||
clientHeight: doc.clientHeight
|
||||
clientWidth: doc.clientWidth
|
||||
}
|
||||
@ -325,6 +325,11 @@ UI = do ->
|
||||
if $.x 'ancestor::div[contains(@class,"inline")][1]', root
|
||||
$.on d, 'keydown', o.hoverend
|
||||
$.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) ->
|
||||
@latestEvent = e
|
||||
@ -355,6 +360,10 @@ UI = do ->
|
||||
$.off @root, @endEvents, @hoverend
|
||||
$.off d, 'keydown', @hoverend
|
||||
$.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
|
||||
|
||||
|
||||
|
||||
1057
src/General/css/style.css
Normal file
1057
src/General/css/style.css
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
<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>
|
||||
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>
|
||||
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.
|
||||
@ -26,4 +26,4 @@
|
||||
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>.
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
@ -49,7 +49,7 @@ $.id = (id) ->
|
||||
d.getElementById id
|
||||
|
||||
$.ready = (fc) ->
|
||||
if d.readyState in ['interactive', 'complete']
|
||||
unless d.readyState is 'loading'
|
||||
$.queueTask fc
|
||||
return
|
||||
cb = ->
|
||||
@ -221,9 +221,7 @@ $.event = (event, detail, root=d) ->
|
||||
|
||||
$.open = (URL) ->
|
||||
<% if (type === 'userscript') { %>
|
||||
# XXX fix GM opening file://// for protocol-less URLs.
|
||||
# https://github.com/greasemonkey/greasemonkey/issues/1719
|
||||
GM_openInTab ($.el 'a', href: URL).href
|
||||
$.open = (URL) -> GM_openInTab URL
|
||||
<% } else { %>
|
||||
window.open URL, '_blank'
|
||||
<% } %>
|
||||
@ -298,29 +296,21 @@ $.minmax = (value, min, max) ->
|
||||
value
|
||||
)
|
||||
|
||||
$.syncing = {}
|
||||
$.item = (key, val) ->
|
||||
item = {}
|
||||
item[key] = val
|
||||
item
|
||||
|
||||
$.sync = do ->
|
||||
$.syncing = {}
|
||||
<% if (type === 'crx') { %>
|
||||
$.sync = do ->
|
||||
chrome.storage.onChanged.addListener (changes) ->
|
||||
for key of changes
|
||||
if cb = $.syncing[key]
|
||||
cb changes[key].newValue
|
||||
return
|
||||
(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 = [
|
||||
# filters
|
||||
'name',
|
||||
@ -372,6 +362,7 @@ $.get = (key, val, cb) ->
|
||||
if syncItems
|
||||
count++
|
||||
chrome.storage.sync.get syncItems, done
|
||||
|
||||
$.set = do ->
|
||||
items = {}
|
||||
localItems = {}
|
||||
@ -397,8 +388,13 @@ $.set = do ->
|
||||
set()
|
||||
|
||||
<% } else { %>
|
||||
|
||||
# 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) ->
|
||||
unless keys instanceof Array
|
||||
keys = [keys]
|
||||
|
||||
@ -59,5 +59,4 @@ class Clone extends Post
|
||||
|
||||
@isDead = true if origin.isDead
|
||||
@isClone = true
|
||||
index = origin.clones.push(@) - 1
|
||||
root.setAttribute 'data-clone', index
|
||||
root.dataset.clone = origin.clones.push(@) - 1
|
||||
|
||||
@ -16,29 +16,34 @@ class Post
|
||||
quotelinks: []
|
||||
backlinks: info.getElementsByClassName 'backlink'
|
||||
|
||||
unless @isReply = $.hasClass post, 'reply'
|
||||
@thread.OP = @
|
||||
@thread.isSticky = !!$ '.stickyIcon', info
|
||||
@thread.isClosed = !!$ '.closedIcon', info
|
||||
|
||||
@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.hand', info
|
||||
if capcode = $ '.capcode.hand', info
|
||||
@nodes.capcode = capcode
|
||||
@info.capcode = capcode.textContent.replace '## ', ''
|
||||
if flag = $ '.countryFlag', info
|
||||
if flag = $ '.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
|
||||
@info.yours = QR.db.get
|
||||
@ -48,43 +53,7 @@ class Post
|
||||
|
||||
@parseComment()
|
||||
@parseQuotes()
|
||||
|
||||
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
|
||||
@parseFile(that)
|
||||
|
||||
@clones = []
|
||||
g.posts[@fullID] = thread.posts[@] = board.posts[@] = @
|
||||
@ -108,18 +77,18 @@ class Post
|
||||
nodes = d.evaluate './/br|.//text()', bq, null, 7, null
|
||||
i = 0
|
||||
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, ''
|
||||
|
||||
parseQuotes: ->
|
||||
quotes = {}
|
||||
for quotelink in $$ '.quotelink', @nodes.comment
|
||||
# Don't add board links. (>>>/b/)
|
||||
hash = quotelink.hash
|
||||
{hash} = quotelink
|
||||
continue unless hash
|
||||
|
||||
# Don't add catalog links. (>>>/b/catalog or >>>/b/search)
|
||||
pathname = quotelink.pathname
|
||||
{pathname} = quotelink
|
||||
continue if /catalog$/.test pathname
|
||||
|
||||
# Don't add rules links. (>>>/a/rules)
|
||||
@ -128,14 +97,49 @@ class Post
|
||||
|
||||
@nodes.quotelinks.push quotelink
|
||||
|
||||
# Don't count capcode replies as quotes. (Admin/Mod/Dev Replies: ...)
|
||||
continue if quotelink.parentNode.parentNode.className is 'capcodeReplies'
|
||||
# Don't count capcode replies as quotes in OPs. (Admin/Mod/Dev Replies: ...)
|
||||
continue if !@isReply and $.hasClass quotelink.parentNode.parentNode, 'capcodeReplies'
|
||||
|
||||
# Basically, only add quotes that link to posts on an imageboard.
|
||||
quotes["#{pathname.split('/')[1]}.#{hash[2..]}"] = true
|
||||
return if @isClone
|
||||
@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) ->
|
||||
now or= new Date()
|
||||
if file
|
||||
@ -190,10 +194,12 @@ class Post
|
||||
quotelink.textContent = quotelink.textContent.replace '\u00A0(Dead)', ''
|
||||
$.rmClass quotelink, 'deadlink'
|
||||
return
|
||||
|
||||
addClone: (context) ->
|
||||
new Clone @, context
|
||||
|
||||
rmClone: (index) ->
|
||||
@clones.splice index, 1
|
||||
for clone in @clones[index..]
|
||||
clone.nodes.root.setAttribute 'data-clone', index++
|
||||
return
|
||||
clone.nodes.root.dataset.clone = index++
|
||||
return
|
||||
|
||||
@ -2,8 +2,7 @@ class Thread
|
||||
callbacks: []
|
||||
toString: -> @ID
|
||||
|
||||
constructor: (ID, @board) ->
|
||||
@ID = +ID
|
||||
constructor: (@ID, @board) ->
|
||||
@fullID = "#{@board}.#{@ID}"
|
||||
@posts = {}
|
||||
|
||||
@ -11,4 +10,4 @@ class Thread
|
||||
|
||||
kill: ->
|
||||
@isDead = true
|
||||
@timeOfDeath = Date.now()
|
||||
@timeOfDeath = Date.now()
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
}],
|
||||
"homepage_url": "<%= meta.page %>",
|
||||
"minimum_chrome_version": "24",
|
||||
"minimum_opera_version": "15",
|
||||
"permissions": [
|
||||
"storage"
|
||||
]
|
||||
|
||||
@ -13,7 +13,7 @@ ImageHover =
|
||||
el = $.el 'img',
|
||||
id: 'ihover'
|
||||
src: post.file.URL
|
||||
el.setAttribute 'data-fullid', post.fullID
|
||||
el.dataset.fullID = post.fullID
|
||||
$.add Header.hover, el
|
||||
UI.hover
|
||||
root: @
|
||||
@ -24,7 +24,7 @@ ImageHover =
|
||||
$.on el, 'error', ImageHover.error
|
||||
error: ->
|
||||
return unless doc.contains @
|
||||
post = g.posts[@dataset.fullid]
|
||||
post = g.posts[@dataset.fullID]
|
||||
|
||||
src = @src.split '/'
|
||||
if src[2] is 'images.4chan.org'
|
||||
@ -48,4 +48,4 @@ ImageHover =
|
||||
post.kill()
|
||||
else if postObj.filedeleted
|
||||
clearTimeout timeoutID
|
||||
post.kill true
|
||||
post.kill true
|
||||
|
||||
@ -4,12 +4,10 @@ Sauce =
|
||||
|
||||
links = []
|
||||
for link in Conf['sauces'].split '\n'
|
||||
continue if link[0] is '#'
|
||||
try
|
||||
links.push @createSauceLink link.trim()
|
||||
links.push @createSauceLink link.trim() if link[0] isnt '#'
|
||||
catch err
|
||||
# Don't add random text plz.
|
||||
continue
|
||||
return unless links.length
|
||||
@links = links
|
||||
@link = $.el 'a', target: '_blank'
|
||||
@ -44,9 +44,8 @@ DeleteLink =
|
||||
return if DeleteLink.cooldown.counting is post
|
||||
|
||||
$.off @, 'click', DeleteLink.delete
|
||||
@textContent = "Deleting #{@textContent}..."
|
||||
|
||||
fileOnly = $.hasClass @, 'delete-file'
|
||||
@textContent = "Deleting #{if fileOnly then 'file' else 'post'}..."
|
||||
|
||||
form =
|
||||
mode: 'usrdel'
|
||||
|
||||
@ -8,29 +8,21 @@ Menu =
|
||||
cb: @node
|
||||
|
||||
node: ->
|
||||
button = Menu.makeButton @
|
||||
if @isClone
|
||||
$.replace $('.menu-button', @nodes.info), button
|
||||
return
|
||||
$.add @nodes.info, [$.tn('\u00A0'), button]
|
||||
button = $ '.menu-button', @nodes.info
|
||||
else
|
||||
button = Menu.makeButton @
|
||||
$.add @nodes.info, [$.tn('\u00A0'), button]
|
||||
$.on button, 'click', Menu.toggle
|
||||
|
||||
makeButton: do ->
|
||||
a = null
|
||||
(post) ->
|
||||
->
|
||||
a or= $.el 'a',
|
||||
className: 'menu-button brackets-wrap'
|
||||
innerHTML: '<span class=drop-marker></span>'
|
||||
href: 'javascript:;'
|
||||
clone = a.cloneNode true
|
||||
clone.setAttribute 'data-postid', post.fullID
|
||||
clone.setAttribute 'data-clone', true if post.isClone
|
||||
$.on clone, 'click', Menu.toggle
|
||||
clone
|
||||
a.cloneNode true
|
||||
|
||||
toggle: (e) ->
|
||||
post =
|
||||
if @dataset.clone
|
||||
Get.postFromNode @
|
||||
else
|
||||
g.posts[@dataset.postid]
|
||||
Menu.menu.toggle e, @, post
|
||||
Menu.menu.toggle e, @, Get.postFromNode @
|
||||
|
||||
@ -93,10 +93,10 @@ Keybinds =
|
||||
window.location = "/#{g.BOARD}/catalog"
|
||||
# Thread Navigation
|
||||
when Conf['Next thread']
|
||||
return if g.VIEW is 'thread'
|
||||
return if g.VIEW isnt 'index'
|
||||
Nav.scroll +1
|
||||
when Conf['Previous thread']
|
||||
return if g.VIEW is 'thread'
|
||||
return if g.VIEW isnt 'index'
|
||||
Nav.scroll -1
|
||||
when Conf['Expand thread']
|
||||
ExpandThread.toggle thread
|
||||
|
||||
@ -58,8 +58,7 @@ Nav =
|
||||
# unless we're not at the beginning of the current thread
|
||||
# (and thus wanting to move to beginning)
|
||||
# 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)
|
||||
i += delta
|
||||
if (delta is -1 and top > -5) or (delta is +1 and top < 5)
|
||||
top = threads[i + delta]?.getBoundingClientRect().top - topMargin
|
||||
|
||||
top = threads[i]?.getBoundingClientRect().top - topMargin
|
||||
window.scrollBy 0, top
|
||||
|
||||
@ -158,8 +158,7 @@ ThreadUpdater =
|
||||
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.
|
||||
###
|
||||
# XXX 304 -> 0 in Opera
|
||||
[text, klass] = if [0, 304].contains req.status
|
||||
[text, klass] = if req.status is 304
|
||||
[null, null]
|
||||
else
|
||||
["#{req.statusText} (#{req.status})", 'warning']
|
||||
|
||||
@ -36,13 +36,11 @@ Unread =
|
||||
# Let the header's onload callback handle it.
|
||||
return if (hash = location.hash.match /\d+/) and hash[0] of Unread.thread.posts
|
||||
if Unread.posts.length
|
||||
# Scroll to before the first unread post.
|
||||
prevID = 0
|
||||
while root = $.x 'preceding-sibling::div[contains(@class,"postContainer")][1]', Unread.posts[0].nodes.root
|
||||
post = Get.postFromRoot root
|
||||
break if prevID is post.ID
|
||||
prevID = post.ID
|
||||
break unless post.isHidden
|
||||
# Scroll to a non-hidden, non-OP post that's before the first unread post.
|
||||
post = Unread.posts[0]
|
||||
while root = $.x 'preceding-sibling::div[contains(@class,"replyContainer")][1]', post.nodes.root
|
||||
break unless (post = Get.postFromRoot root).isHidden
|
||||
return unless root
|
||||
onload = -> root.scrollIntoView false if checkPosition root
|
||||
else
|
||||
# Scroll to the last read post.
|
||||
@ -188,9 +186,7 @@ Unread =
|
||||
else
|
||||
Favicon.default
|
||||
|
||||
<% if (type !== 'crx') { %>
|
||||
<% if (type === 'userscript') { %>
|
||||
# `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
|
||||
<% } %>
|
||||
|
||||
@ -97,8 +97,8 @@ QR =
|
||||
$.rmClass QR.captcha.nodes.input, 'error'
|
||||
if Conf['QR Shortcut']
|
||||
$.toggleClass $('.qr-shortcut'), 'disabled'
|
||||
for i in QR.posts
|
||||
QR.posts[0].rm()
|
||||
for post in QR.posts.splice 0, QR.posts.length, new QR.post true
|
||||
post.delete()
|
||||
QR.cooldown.auto = false
|
||||
QR.status()
|
||||
|
||||
@ -152,7 +152,8 @@ QR =
|
||||
|
||||
status: ->
|
||||
return unless QR.nodes
|
||||
if g.DEAD
|
||||
{thread} = QR.posts[0]
|
||||
if thread isnt 'new' and g.threads["#{g.BOARD}.#{thread}"].isDead
|
||||
value = 404
|
||||
disabled = true
|
||||
QR.cooldown.auto = false
|
||||
@ -373,14 +374,10 @@ QR =
|
||||
e?.preventDefault()
|
||||
return unless QR.postingIsEnabled
|
||||
|
||||
sel = d.getSelection()
|
||||
selectionRoot = $.x 'ancestor::div[contains(@class,"postContainer")][1]', sel.anchorNode
|
||||
post = Get.postFromNode @
|
||||
{OP} = Get.contextFromLink(@).thread
|
||||
|
||||
text = ">>#{post}\n"
|
||||
if (s = sel.toString().trim()) and post.nodes.root is selectionRoot
|
||||
# XXX Opera doesn't retain `\n`s?
|
||||
sel = d.getSelection()
|
||||
post = Get.postFromNode @
|
||||
text = ">>#{post}\n"
|
||||
if (s = sel.toString().trim()) and post is Get.postFromNode sel.anchorNode
|
||||
s = s.replace /\n/g, '\n>'
|
||||
text += ">#{s}\n"
|
||||
|
||||
@ -391,7 +388,7 @@ QR =
|
||||
$.addClass QR.nodes.el, 'dump'
|
||||
QR.cooldown.auto = true
|
||||
{com, thread} = QR.nodes
|
||||
thread.value = OP.ID unless com.value
|
||||
thread.value = Get.contextFromNode(@).thread unless com.value
|
||||
thread.nextElementSibling.firstElementChild.textContent = thread.options[thread.selectedIndex].textContent
|
||||
|
||||
caretPos = com.selectionStart
|
||||
@ -449,7 +446,7 @@ QR =
|
||||
QR.nodes.fileInput.click()
|
||||
|
||||
fileInput: (files) ->
|
||||
if @ instanceof Element #or files instanceof Event # file input
|
||||
if files instanceof Event # file input
|
||||
files = [@files...]
|
||||
QR.nodes.fileInput.value = null # Don't hold the files from being modified on windows
|
||||
{length} = files
|
||||
@ -506,7 +503,7 @@ QR =
|
||||
for elm in $$ '*', el
|
||||
$.on elm, 'blur', QR.focusout
|
||||
$.on elm, 'focus', QR.focusin
|
||||
<% } %>
|
||||
<% } %>
|
||||
$.on el, 'click', @select.bind @
|
||||
$.on @nodes.rm, 'click', (e) => e.stopPropagation(); @rm()
|
||||
$.on @nodes.label, 'click', (e) => e.stopPropagation()
|
||||
@ -555,7 +552,7 @@ QR =
|
||||
@unlock()
|
||||
|
||||
rm: ->
|
||||
$.rm @nodes.el
|
||||
@delete()
|
||||
index = QR.posts.indexOf @
|
||||
if QR.posts.length is 1
|
||||
new QR.post true
|
||||
@ -563,7 +560,9 @@ QR =
|
||||
else if @ is QR.selected
|
||||
(QR.posts[index-1] or QR.posts[index+1]).select()
|
||||
QR.posts.splice index, 1
|
||||
return unless window.URL
|
||||
QR.status()
|
||||
delete: ->
|
||||
$.rm @nodes.el
|
||||
URL.revokeObjectURL @URL
|
||||
|
||||
lock: (lock=true) ->
|
||||
@ -608,15 +607,18 @@ QR =
|
||||
if input.type is 'checkbox'
|
||||
@spoiler = input.checked
|
||||
return
|
||||
{value} = input
|
||||
@[input.dataset.name] = value
|
||||
return if input.nodeName isnt 'TEXTAREA'
|
||||
@nodes.span.textContent = value
|
||||
QR.characterCount()
|
||||
# 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
|
||||
{name} = input.dataset
|
||||
@[name] = input.value
|
||||
switch name
|
||||
when 'thread'
|
||||
QR.status()
|
||||
when 'com'
|
||||
@nodes.span.textContent = @com
|
||||
QR.characterCount()
|
||||
# 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: ->
|
||||
return unless @ is QR.selected
|
||||
@ -630,26 +632,15 @@ QR =
|
||||
@filename = "#{file.name} (#{$.bytesToString file.size})"
|
||||
@nodes.el.title = @filename
|
||||
@nodes.label.hidden = false if QR.spoiler
|
||||
URL.revokeObjectURL @URL if window.URL
|
||||
URL.revokeObjectURL @URL
|
||||
@showFileData()
|
||||
unless /^image/.test file.type
|
||||
@nodes.el.style.backgroundImage = null
|
||||
return
|
||||
@setThumbnail()
|
||||
|
||||
setThumbnail: (fileURL) ->
|
||||
# XXX Opera does not support blob URL
|
||||
setThumbnail: ->
|
||||
# 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.onload = =>
|
||||
@ -661,7 +652,7 @@ QR =
|
||||
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
|
||||
@URL = fileURL
|
||||
@nodes.el.style.backgroundImage = "url(#{@URL})"
|
||||
return
|
||||
if height <= width
|
||||
@ -674,10 +665,6 @@ QR =
|
||||
cv.height = img.height = height
|
||||
cv.width = img.width = width
|
||||
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
|
||||
applyBlob = (blob) =>
|
||||
@URL = URL.createObjectURL blob
|
||||
@ -695,6 +682,7 @@ QR =
|
||||
|
||||
applyBlob new Blob [ui8a], type: 'image/png'
|
||||
|
||||
fileURL = URL.createObjectURL @file
|
||||
img.src = fileURL
|
||||
|
||||
rmFile: ->
|
||||
@ -704,7 +692,6 @@ QR =
|
||||
@nodes.el.style.backgroundImage = null
|
||||
@nodes.label.hidden = true if QR.spoiler
|
||||
@showFileData()
|
||||
return unless window.URL
|
||||
URL.revokeObjectURL @URL
|
||||
|
||||
showFileData: ->
|
||||
@ -729,33 +716,26 @@ QR =
|
||||
@nodes.span.textContent = @com
|
||||
reader.readAsText file
|
||||
|
||||
dragStart: ->
|
||||
$.addClass @, 'drag'
|
||||
|
||||
dragEnd: ->
|
||||
$.rmClass @, 'drag'
|
||||
|
||||
dragEnter: ->
|
||||
$.addClass @, 'over'
|
||||
|
||||
dragLeave: ->
|
||||
$.rmClass @, 'over'
|
||||
dragStart: -> $.addClass @, 'drag'
|
||||
dragEnd: -> $.rmClass @, 'drag'
|
||||
dragEnter: -> $.addClass @, 'over'
|
||||
dragLeave: -> $.rmClass @, 'over'
|
||||
|
||||
dragOver: (e) ->
|
||||
e.preventDefault()
|
||||
e.dataTransfer.dropEffect = 'move'
|
||||
|
||||
drop: ->
|
||||
el = $ '.drag', @parentNode
|
||||
$.rmClass el, 'drag' # Opera doesn't fire dragEnd if we drop it on something else
|
||||
$.rmClass @, 'over'
|
||||
$.rmClass @, 'over'
|
||||
return unless @draggable
|
||||
el = $ '.drag', @parentNode
|
||||
index = (el) -> [el.parentNode.children...].indexOf el
|
||||
oldIndex = index el
|
||||
newIndex = index @
|
||||
(if oldIndex < newIndex then $.after else $.before) @, el
|
||||
post = QR.posts.splice(oldIndex, 1)[0]
|
||||
QR.posts.splice newIndex, 0, post
|
||||
QR.status()
|
||||
|
||||
captcha:
|
||||
init: ->
|
||||
@ -784,20 +764,17 @@ QR =
|
||||
img: imgContainer.firstChild
|
||||
input: input
|
||||
|
||||
if window.MutationObserver
|
||||
observer = new MutationObserver @load.bind @
|
||||
observer.observe @nodes.challenge,
|
||||
childList: true
|
||||
else
|
||||
$.on @nodes.challenge, 'DOMNodeInserted', @load.bind @
|
||||
new MutationObserver(@load.bind @).observe @nodes.challenge,
|
||||
childList: true
|
||||
|
||||
$.on imgContainer, 'click', @reload.bind @
|
||||
$.on input, 'keydown', @keydown.bind @
|
||||
$.on input, 'focus', -> $.addClass QR.nodes.el, 'focus'
|
||||
$.on input, 'blur', -> $.rmClass QR.nodes.el, 'focus'
|
||||
|
||||
$.get 'captchas', [], (item) =>
|
||||
@sync item['captchas']
|
||||
$.get 'captchas', [], ({captchas}) =>
|
||||
@sync captchas
|
||||
|
||||
$.sync 'captchas', @sync
|
||||
# start with an uncached captcha
|
||||
@reload()
|
||||
@ -806,12 +783,13 @@ QR =
|
||||
# XXX Firefox lacks focusin/focusout support.
|
||||
$.on input, 'blur', QR.focusout
|
||||
$.on input, 'focus', QR.focusin
|
||||
<% } %>
|
||||
<% } %>
|
||||
|
||||
$.addClass QR.nodes.el, 'has-captcha'
|
||||
$.after QR.nodes.dumpList.parentElement, [imgContainer, input]
|
||||
|
||||
sync: (@captchas) ->
|
||||
sync: (captchas) ->
|
||||
QR.captcha.captchas = captchas
|
||||
QR.captcha.count()
|
||||
|
||||
getOne: ->
|
||||
@ -931,10 +909,6 @@ QR =
|
||||
# Add empty mimeType to avoid errors with URLs selected in Window's file dialog.
|
||||
QR.mimeTypes.push ''
|
||||
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]'
|
||||
if QR.spoiler
|
||||
@ -969,7 +943,7 @@ QR =
|
||||
for elm in $$ '*', QR.nodes.el
|
||||
$.on elm, 'blur', QR.focusout
|
||||
$.on elm, 'focus', QR.focusin
|
||||
<% } %>
|
||||
<% } %>
|
||||
$.on dialog, 'focusin', QR.focusin
|
||||
$.on dialog, 'focusout', QR.focusout
|
||||
$.on nodes.autohide, 'change', QR.toggleHide
|
||||
@ -1133,11 +1107,6 @@ QR =
|
||||
QR.status()
|
||||
|
||||
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
|
||||
delete QR.req
|
||||
|
||||
@ -1229,15 +1198,15 @@ QR =
|
||||
|
||||
QR.cooldown.set {req, post, isReply}
|
||||
|
||||
if threadID is postID # new thread
|
||||
URL = "/#{g.BOARD}/res/#{threadID}"
|
||||
URL = if threadID is postID # new thread
|
||||
"/#{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
|
||||
URL = "/#{g.BOARD}/res/#{threadID}#p#{postID}"
|
||||
"/#{g.BOARD}/res/#{threadID}#p#{postID}"
|
||||
if URL
|
||||
if Conf['Open Post in New Tab']
|
||||
$.open "/#{g.BOARD}/res/#{threadID}"
|
||||
$.open URL
|
||||
else
|
||||
window.location = "/#{g.BOARD}/res/#{threadID}"
|
||||
window.location = URL
|
||||
|
||||
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
|
||||
e.preventDefault()
|
||||
{boardID, threadID, postID} = Get.postDataFromLink @
|
||||
context = Get.contextFromLink @
|
||||
context = Get.contextFromNode @
|
||||
if $.hasClass @, 'inlined'
|
||||
QuoteInline.rm @, boardID, threadID, postID, context
|
||||
else
|
||||
@ -107,4 +107,4 @@ QuoteInline =
|
||||
{boardID, threadID, postID} = Get.postDataFromLink inlined
|
||||
QuoteInline.rm inlined, boardID, threadID, postID, context
|
||||
$.rmClass inlined, 'inlined'
|
||||
return
|
||||
return
|
||||
|
||||
@ -22,8 +22,9 @@ QuotePreview =
|
||||
qp = $.el 'div',
|
||||
id: 'qp'
|
||||
className: 'dialog'
|
||||
|
||||
$.add Header.hover, qp
|
||||
Get.postClone boardID, threadID, postID, qp, Get.contextFromLink @
|
||||
Get.postClone boardID, threadID, postID, qp, Get.contextFromNode @
|
||||
|
||||
UI.hover
|
||||
root: @
|
||||
|
||||
@ -21,6 +21,9 @@ Quotify =
|
||||
if deadlink.parentNode.className is 'prettyprint'
|
||||
# Don't quotify deadlinks inside code tags,
|
||||
# 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...]
|
||||
return
|
||||
|
||||
@ -48,9 +51,8 @@ Quotify =
|
||||
target: '_blank'
|
||||
textContent: "#{quote}\u00A0(Dead)"
|
||||
|
||||
a.setAttribute 'data-boardid', boardID
|
||||
a.setAttribute 'data-threadid', post.thread.ID
|
||||
a.setAttribute 'data-postid', postID
|
||||
$.extend a.dataset, {boardID, threadID: post.thread.ID, postID}
|
||||
|
||||
else if redirect = Redirect.to 'thread', {boardID, threadID: 0, postID}
|
||||
# Replace the .deadlink span if we can redirect.
|
||||
a = $.el 'a',
|
||||
@ -60,9 +62,8 @@ Quotify =
|
||||
textContent: "#{quote}\u00A0(Dead)"
|
||||
if Redirect.to 'post', {boardID, postID}
|
||||
# Make it function as a normal quote if we can fetch the post.
|
||||
$.addClass a, 'quotelink'
|
||||
a.setAttribute 'data-boardid', boardID
|
||||
a.setAttribute 'data-postid', postID
|
||||
$.addClass a, 'quotelink'
|
||||
$.extend a.dataset, {boardID, postID}
|
||||
|
||||
unless @quotes.contains quoteID
|
||||
@quotes.push quoteID
|
||||
@ -73,4 +74,4 @@ Quotify =
|
||||
|
||||
$.replace deadlink, a
|
||||
if $.hasClass a, 'quotelink'
|
||||
@nodes.quotelinks.push a
|
||||
@nodes.quotelinks.push a
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user