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:
Zixaphir 2013-04-13 23:05:16 -07:00
commit 478a75d03e
11 changed files with 280 additions and 130 deletions

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -402,7 +402,7 @@ Main =
# c.timeEnd 'All initializations'
$.on d, 'AddCallback', Main.addCallback
$.on d, 'AddCallback', Main.addCallback
$.ready Main.initReady
initStyle: ->

View File

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