Merge branch 'v3' of git://github.com/MayhemYDG/4chan-x into v3
Conflicts: css/style.css src/config.coffee src/qr.coffee
This commit is contained in:
commit
478a75d03e
@ -1,6 +1,6 @@
|
||||
// ==UserScript==
|
||||
// @name 4chan X
|
||||
// @version 3.0.4
|
||||
// @version 3.0.6
|
||||
// @namespace 4chan-X
|
||||
// @description Cross-browser extension for productive lurking on 4chan.
|
||||
// @copyright 2009-2011 James Campos <james.r.campos@gmail.com>
|
||||
|
||||
209
4chan-X.user.js
209
4chan-X.user.js
File diff suppressed because one or more lines are too long
14
CHANGELOG.md
14
CHANGELOG.md
@ -1,3 +1,15 @@
|
||||
### 3.0.6 - *2013-04-14*
|
||||
|
||||
- Fix regression concerning thread selection when quoting on the index.
|
||||
|
||||
### 3.0.5 - *2013-04-14*
|
||||
|
||||
- `Scroll to Last Read Post` is now optional, enabled by default.
|
||||
- The QR won't auto-hide when auto-hide is enabled and one of its input is focused. Doesn't work on Firefox.
|
||||
- Added the `Remember QR Size` setting back in, disabled by default. Only on Firefox.
|
||||
- Fix QR remembering the file spoiler state when it shouldn't.
|
||||
- Fix QR cooldown in Opera.
|
||||
|
||||
### 3.0.4 - *2013-04-11*
|
||||
|
||||
- More minor fixes.
|
||||
@ -9,7 +21,7 @@
|
||||
### 3.0.2 - *2013-04-09*
|
||||
|
||||
- Added a setting in the Header's menu to move it at the bottom of the screen.
|
||||
- Added Cooldown setting back in.
|
||||
- Added the `Cooldown` setting back in.
|
||||
- Fixed the Header going above posts when following quotelinks for example.
|
||||
- Fixed a bug where dead quotelinks would disappear.
|
||||
|
||||
|
||||
@ -542,6 +542,15 @@ a[href="javascript:;"] {
|
||||
}
|
||||
|
||||
/* QR */
|
||||
:root.hide-original-post-form #postForm,
|
||||
:root.hide-original-post-form .postingMode,
|
||||
:root.hide-original-post-form #togglePostForm,
|
||||
#qr.autohide:not(.has-focus):not(:hover) > form {
|
||||
display: none;
|
||||
}
|
||||
#qr select, #dump-button, .remove, .captcha-img {
|
||||
cursor: pointer;
|
||||
}
|
||||
#qr {
|
||||
z-index: 20;
|
||||
position: fixed;
|
||||
@ -553,9 +562,6 @@ a[href="javascript:;"] {
|
||||
#qrtab {
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
.autohide:not(:hover):not(.focus) > form {
|
||||
display: none !important;
|
||||
}
|
||||
#qrtab {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
@ -138,8 +138,11 @@ $.extend $,
|
||||
el.classList.toggle className
|
||||
hasClass: (el, className) ->
|
||||
el.classList.contains className
|
||||
rm: (el) ->
|
||||
el.parentNode.removeChild el
|
||||
rm: do ->
|
||||
if 'remove' of Element.prototype
|
||||
(el) -> el.remove()
|
||||
else
|
||||
(el) -> el.parentNode?.removeChild el
|
||||
tn: (s) ->
|
||||
d.createTextNode s
|
||||
frag: ->
|
||||
|
||||
@ -4,7 +4,8 @@ UI = do ->
|
||||
className: 'dialog'
|
||||
innerHTML: html
|
||||
id: id
|
||||
el.style.cssText = localStorage.getItem("#{g.NAMESPACE}#{id}.position") or position
|
||||
$.get "#{id}.position", position, (item) ->
|
||||
el.style.cssText = item["#{id}.position"]
|
||||
move = $ '.move', el
|
||||
$.on move, 'touchstart mousedown', dragstart
|
||||
for child in move.children
|
||||
@ -276,7 +277,7 @@ UI = do ->
|
||||
else # mouseup
|
||||
$.off d, 'mousemove', @move
|
||||
$.off d, 'mouseup', @up
|
||||
localStorage.setItem "#{g.NAMESPACE}#{@id}.position", @style.cssText
|
||||
$.set "#{@id}.position", @style.cssText
|
||||
|
||||
hoverstart = ({root, el, latestEvent, endEvents, asapTest, cb}) ->
|
||||
o = {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "4chan-X",
|
||||
"version": "3.0.4",
|
||||
"version": "3.0.6",
|
||||
"description": "Cross-browser extension for productive lurking on 4chan.",
|
||||
"meta": {
|
||||
"name": "4chan X",
|
||||
|
||||
@ -221,6 +221,12 @@ Config =
|
||||
false
|
||||
'Remember the subject field, instead of resetting after posting.'
|
||||
]
|
||||
<% if (type === 'userscript') { %>
|
||||
'Remember QR Size': [
|
||||
false
|
||||
'Remember the size of the Quick reply.'
|
||||
]
|
||||
<% } %>
|
||||
'Remember Spoiler': [
|
||||
false
|
||||
'Remember the spoiler state, instead of resetting after posting.'
|
||||
|
||||
@ -95,6 +95,7 @@ Header =
|
||||
|
||||
hashScroll: ->
|
||||
return unless post = $.id @location.hash[1..]
|
||||
return if (Get.postFromRoot post).isHidden
|
||||
Header.scrollToPost post
|
||||
|
||||
scrollToPost: (post) ->
|
||||
@ -145,7 +146,7 @@ class Notification
|
||||
setTimeout @close, @timeout * $.SECOND if @timeout
|
||||
|
||||
close = ->
|
||||
$.rm @el if @el.parentNode
|
||||
$.rm @el
|
||||
|
||||
CatalogLinks =
|
||||
init: ->
|
||||
@ -1536,7 +1537,7 @@ ReportLink =
|
||||
|
||||
DeleteLink =
|
||||
init: ->
|
||||
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Delete Link'] or !Conf['Quick Reply']
|
||||
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Delete Link']
|
||||
|
||||
div = $.el 'div',
|
||||
className: 'delete-link'
|
||||
@ -1588,20 +1589,22 @@ DeleteLink =
|
||||
else
|
||||
$.id('delPassword').value
|
||||
|
||||
fileOnly = $.hasClass @, 'delete-file'
|
||||
|
||||
form =
|
||||
mode: 'usrdel'
|
||||
onlyimgdel: $.hasClass @, 'delete-file'
|
||||
onlyimgdel: fileOnly
|
||||
pwd: pwd
|
||||
form[post.ID] = 'delete'
|
||||
|
||||
link = @
|
||||
$.ajax $.id('delform').action.replace("/#{g.BOARD}/", "/#{post.board}/"),
|
||||
onload: -> DeleteLink.load link, post, @response
|
||||
onload: -> DeleteLink.load link, post, fileOnly, @response
|
||||
onerror: -> DeleteLink.error link
|
||||
,
|
||||
cred: true
|
||||
form: $.formData form
|
||||
load: (link, post, html) ->
|
||||
load: (link, post, fileOnly, html) ->
|
||||
tmpDoc = d.implementation.createHTMLDocument ''
|
||||
tmpDoc.documentElement.innerHTML = html
|
||||
if tmpDoc.title is '4chan - Banned' # Ban/warn check
|
||||
@ -1612,7 +1615,7 @@ DeleteLink =
|
||||
else
|
||||
if tmpDoc.title is 'Updating index...'
|
||||
# We're 100% sure.
|
||||
(post.origin or post).kill()
|
||||
(post.origin or post).kill fileOnly
|
||||
s = 'Deleted'
|
||||
link.textContent = s
|
||||
error: (link) ->
|
||||
@ -1924,7 +1927,13 @@ Keybinds =
|
||||
|
||||
Nav =
|
||||
init: ->
|
||||
return if g.VIEW is 'index' and !Conf['Index Navigation'] or g.VIEW is 'thread' and !Conf['Reply Navigation']
|
||||
switch g.VIEW
|
||||
when 'index'
|
||||
return unless Conf['Index Navigation']
|
||||
when 'thread'
|
||||
return unless Conf['Reply Navigation']
|
||||
else # catalog
|
||||
return
|
||||
|
||||
span = $.el 'span',
|
||||
id: 'navlinks'
|
||||
@ -3693,18 +3702,22 @@ Unread =
|
||||
threadID: @ID
|
||||
defaultValue: 0
|
||||
Unread.addPosts posts
|
||||
if (hash = location.hash.match /\d+/) and post = @posts[hash[0]]
|
||||
Header.scrollToPost post.nodes.root
|
||||
else if Unread.posts.length
|
||||
# Scroll to before the first unread post.
|
||||
$.x('preceding-sibling::div[contains(@class,"postContainer")][1]', Unread.posts[0].nodes.root).scrollIntoView false
|
||||
else if posts.length
|
||||
# Scroll to the last read post.
|
||||
Header.scrollToPost posts[posts.length - 1].nodes.root
|
||||
$.on d, 'ThreadUpdate', Unread.onUpdate
|
||||
$.on d, 'scroll visibilitychange', Unread.read
|
||||
$.on d, 'visibilitychange', Unread.setLine if Conf['Unread Line']
|
||||
|
||||
return unless Conf['Scroll to Last Read Post']
|
||||
# Let the header's onload callback handle it.
|
||||
return if (hash = location.hash.match /\d+/) and hash[0] of @posts
|
||||
if Unread.posts.length
|
||||
# Scroll to before the first unread post.
|
||||
while root = $.x 'preceding-sibling::div[contains(@class,"postContainer")][1]', Unread.posts[0].nodes.root
|
||||
break unless (Get.postFromRoot root).isHidden
|
||||
root.scrollIntoView false
|
||||
else if posts.length
|
||||
# Scroll to the last read post.
|
||||
Header.scrollToPost posts[posts.length - 1].nodes.root
|
||||
|
||||
sync: ->
|
||||
lastReadPost = Unread.db.get
|
||||
boardID: Unread.thread.board.ID
|
||||
@ -3790,7 +3803,7 @@ Unread =
|
||||
{root} = post.nodes
|
||||
if root isnt $ '.thread > .replyContainer', root.parentNode # not the first reply
|
||||
$.before root, Unread.hr
|
||||
else if Unread.hr.parentNode
|
||||
else
|
||||
$.rm Unread.hr
|
||||
|
||||
update: <% if (type === 'crx') { %>(dontrepeat) <% } %>->
|
||||
|
||||
@ -402,7 +402,7 @@ Main =
|
||||
|
||||
# c.timeEnd 'All initializations'
|
||||
|
||||
$.on d, 'AddCallback', Main.addCallback
|
||||
$.on d, 'AddCallback', Main.addCallback
|
||||
$.ready Main.initReady
|
||||
|
||||
initStyle: ->
|
||||
|
||||
106
src/qr.coffee
106
src/qr.coffee
@ -11,9 +11,9 @@ QR =
|
||||
href: 'javascript:;'
|
||||
$.on sc, 'click', ->
|
||||
if !QR.nodes or QR.nodes.el.hidden
|
||||
$.event 'CloseMenu'
|
||||
QR.open()
|
||||
QR.nodes.com.focus()
|
||||
QR.resetThreadSelector()
|
||||
else
|
||||
QR.close()
|
||||
$.toggleClass @, 'disabled'
|
||||
@ -21,8 +21,7 @@ QR =
|
||||
Header.addShortcut sc
|
||||
|
||||
if Conf['Hide Original Post Form']
|
||||
$.asap (-> doc), ->
|
||||
$.addClass doc, 'hide-original-post-form'
|
||||
$.asap (-> doc), -> $.addClass doc, 'hide-original-post-form'
|
||||
|
||||
$.on d, '4chanXInitFinished', @initReady
|
||||
|
||||
@ -79,6 +78,10 @@ QR =
|
||||
QR.status()
|
||||
if !Conf['Remember Spoiler'] and QR.nodes.spoiler.checked
|
||||
QR.nodes.spoiler.click()
|
||||
focusin: ->
|
||||
$.addClass QR.nodes.el, 'has-focus'
|
||||
focusout: ->
|
||||
$.rmClass QR.nodes.el, 'has-focus'
|
||||
hide: ->
|
||||
d.activeElement.blur()
|
||||
$.addClass QR.nodes.el, 'autohide'
|
||||
@ -207,7 +210,7 @@ QR =
|
||||
|
||||
now = Date.now()
|
||||
post = QR.posts[0]
|
||||
isReply = QR.nodes.thread.value isnt 'new'
|
||||
isReply = post.thread isnt 'new'
|
||||
isSage = /sage/i.test post.email
|
||||
hasFile = !!post.file
|
||||
seconds = null
|
||||
@ -266,19 +269,19 @@ QR =
|
||||
text += ">#{s}\n"
|
||||
|
||||
QR.open()
|
||||
ta = QR.nodes.com
|
||||
QR.nodes.thread.value = OP.ID unless ta.value
|
||||
{com, thread} = QR.nodes
|
||||
thread.value = OP.ID unless com.value
|
||||
|
||||
caretPos = ta.selectionStart
|
||||
caretPos = com.selectionStart
|
||||
# Replace selection for text.
|
||||
ta.value = ta.value[...caretPos] + text + ta.value[ta.selectionEnd..]
|
||||
com.value = com.value[...caretPos] + text + com.value[com.selectionEnd..]
|
||||
# Move the caret to the end of the new quote.
|
||||
range = caretPos + text.length
|
||||
ta.setSelectionRange range, range
|
||||
ta.focus()
|
||||
com.setSelectionRange range, range
|
||||
com.focus()
|
||||
|
||||
# Fire the 'input' event
|
||||
$.event 'input', null, ta
|
||||
QR.selected.save com
|
||||
QR.selected.save thread
|
||||
|
||||
characterCount: ->
|
||||
counter = QR.nodes.charCount
|
||||
@ -351,11 +354,6 @@ QR =
|
||||
post = new QR.post()
|
||||
post.setFile file
|
||||
$.addClass QR.nodes.el, 'dump'
|
||||
resetThreadSelector: ->
|
||||
if g.VIEW is 'thread'
|
||||
QR.nodes.thread.value = g.THREADID
|
||||
else
|
||||
QR.nodes.thread.value = 'new'
|
||||
|
||||
posts: []
|
||||
post: class
|
||||
@ -373,8 +371,12 @@ QR =
|
||||
spoiler: $ 'input', el
|
||||
span: el.lastChild
|
||||
|
||||
@nodes.spoiler.checked = @spoiler
|
||||
|
||||
<% if (type === 'userscript') { %>
|
||||
# XXX Firefox lacks focusin/focusout support.
|
||||
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()
|
||||
@ -386,9 +388,14 @@ QR =
|
||||
for event in ['dragStart', 'dragEnter', 'dragLeave', 'dragOver', 'dragEnd', 'drop']
|
||||
$.on el, event.toLowerCase(), @[event]
|
||||
|
||||
@thread = if g.VIEW is 'thread'
|
||||
g.THREADID
|
||||
else
|
||||
'new'
|
||||
|
||||
prev = QR.posts[QR.posts.length - 1]
|
||||
QR.posts.push @
|
||||
@spoiler = if prev and Conf['Remember Spoiler']
|
||||
@nodes.spoiler.checked = @spoiler = if prev and Conf['Remember Spoiler']
|
||||
prev.spoiler
|
||||
else
|
||||
false
|
||||
@ -420,7 +427,7 @@ QR =
|
||||
lock: (lock=true) ->
|
||||
@isLocked = lock
|
||||
return unless @ is QR.selected
|
||||
for name in ['name', 'email', 'sub', 'com', 'spoiler']
|
||||
for name in ['thread', 'name', 'email', 'sub', 'com', 'spoiler']
|
||||
QR.nodes[name].disabled = lock
|
||||
@nodes.rm.style.visibility =
|
||||
QR.nodes.fileRM.style.visibility = if lock then 'hidden' else ''
|
||||
@ -443,11 +450,14 @@ QR =
|
||||
@load()
|
||||
load: ->
|
||||
# Load this post's values.
|
||||
for name in ['name', 'email', 'sub', 'com']
|
||||
for name in ['thread', 'name', 'email', 'sub', 'com']
|
||||
QR.nodes[name].value = @[name] or null
|
||||
@showFileData()
|
||||
QR.characterCount()
|
||||
save: (input) ->
|
||||
if input.type is 'checkbox'
|
||||
@spoiler = input.checked
|
||||
return
|
||||
{value} = input
|
||||
@[input.dataset.name] = value
|
||||
return if input.nodeName isnt 'TEXTAREA'
|
||||
@ -461,7 +471,7 @@ QR =
|
||||
return unless @ is QR.selected
|
||||
# Do this in case people use extensions
|
||||
# that do not trigger the `input` event.
|
||||
for name in ['name', 'email', 'sub', 'com']
|
||||
for name in ['thread', 'name', 'email', 'sub', 'com', 'spoiler']
|
||||
@save QR.nodes[name]
|
||||
return
|
||||
setFile: (@file) ->
|
||||
@ -542,7 +552,7 @@ QR =
|
||||
@showFileData()
|
||||
return unless window.URL
|
||||
URL.revokeObjectURL @URL
|
||||
showFileData: (hide) ->
|
||||
showFileData: ->
|
||||
if @file
|
||||
QR.nodes.filename.textContent = @filename
|
||||
QR.nodes.filename.title = @filename
|
||||
@ -628,6 +638,12 @@ QR =
|
||||
# start with an uncached captcha
|
||||
@reload()
|
||||
|
||||
<% if (type === 'userscript') { %>
|
||||
# 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.com.parentNode, [imgContainer, input]
|
||||
sync: (@captchas) ->
|
||||
@ -702,7 +718,7 @@ QR =
|
||||
<div class=move>
|
||||
<input type=checkbox id=autohide title=Auto-hide>
|
||||
<a href=javascript:; class=close title=Close>×</a>
|
||||
<select title='Create a new thread / Reply'>
|
||||
<select data-name=thread title='Create a new thread / Reply'>
|
||||
<option value=new>New thread</option>
|
||||
</select>
|
||||
</div>
|
||||
@ -797,10 +813,17 @@ QR =
|
||||
$.add nodes.thread, $.el 'option',
|
||||
value: thread
|
||||
textContent: "Thread No.#{thread}"
|
||||
QR.resetThreadSelector()
|
||||
|
||||
$.on nodes.filename.parentNode, 'click keyup', QR.openFileInput
|
||||
|
||||
<% if (type === 'userscript') { %>
|
||||
# XXX Firefox lacks focusin/focusout support.
|
||||
for elm in $$ '*', QR.nodes.el
|
||||
$.on elm, 'blur', QR.focusout
|
||||
$.on elm, 'focus', QR.focusin
|
||||
<% } %>
|
||||
$.on QR.nodes.el, 'focusin', QR.focusin
|
||||
$.on QR.nodes.el, 'focusout', QR.focusout
|
||||
$.on nodes.autohide, 'change', QR.toggleHide
|
||||
$.on nodes.close, 'click', QR.close
|
||||
$.on nodes.dumpButton, 'click', -> nodes.el.classList.toggle 'dump'
|
||||
@ -809,13 +832,21 @@ QR =
|
||||
$.on nodes.fileRM, 'click', -> QR.selected.rmFile()
|
||||
$.on nodes.spoiler, 'change', -> QR.selected.nodes.spoiler.click()
|
||||
$.on nodes.fileInput, 'change', QR.fileInput
|
||||
|
||||
new QR.post true
|
||||
# save selected post's data
|
||||
for name in ['name', 'email', 'sub', 'com']
|
||||
$.on nodes[name], 'input', -> QR.selected.save @
|
||||
$.on nodes[name], 'focus', -> $.addClass nodes.el, 'focus'
|
||||
$.on nodes[name], 'blur', -> $.rmClass nodes.el, 'focus'
|
||||
$.on nodes[name], 'input', -> QR.selected.save @
|
||||
$.on nodes.thread, 'change', -> QR.selected.save @
|
||||
|
||||
<% if (type === 'userscript') { %>
|
||||
if Conf['Remember QR Size']
|
||||
$.get 'QR Size', '', (item) ->
|
||||
nodes.com.style.cssText = item['QR Size']
|
||||
$.on nodes.com, 'mouseup', (e) ->
|
||||
return if e.button isnt 0
|
||||
$.set 'QR Size', @style.cssText
|
||||
<% } %>
|
||||
|
||||
new QR.post true
|
||||
|
||||
QR.status()
|
||||
QR.cooldown.init()
|
||||
@ -842,7 +873,7 @@ QR =
|
||||
post.forceSave()
|
||||
if g.BOARD.ID is 'f'
|
||||
filetag = QR.nodes.flashTag.value
|
||||
threadID = QR.nodes.thread.value
|
||||
threadID = post.thread
|
||||
thread = g.BOARD.threads[threadID]
|
||||
|
||||
# prevent errors
|
||||
@ -930,6 +961,11 @@ 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
|
||||
|
||||
@ -971,6 +1007,12 @@ QR =
|
||||
# Too many frequent mistyped captchas will auto-ban you!
|
||||
# On connection error, the post most likely didn't go through.
|
||||
QR.cooldown.set delay: 2
|
||||
else if err.textContent and m = err.textContent.match /wait\s(\d+)\ssecond/i
|
||||
QR.cooldown.auto = if QR.captcha.isEnabled
|
||||
!!QR.captcha.captchas.length
|
||||
else
|
||||
true
|
||||
QR.cooldown.set delay: m[1]
|
||||
else # stop auto-posting
|
||||
QR.cooldown.auto = false
|
||||
QR.status()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user