QR = init: -> return if !Conf['Quick Reply'] @db = new DataBoard 'yourPosts' if Conf['QR Shortcut'] sc = $.el 'a', className: "qr-shortcut #{unless Conf['Persistent QR'] then 'disabled' else ''}" textContent: 'QR' title: 'Quick Reply' href: 'javascript:;' $.on sc, 'click', -> if Conf['Persistent QR'] or !QR.nodes or QR.nodes.el.hidden $.event 'CloseMenu' QR.open() QR.nodes.com.focus() $.rmClass @, 'disabled' else QR.close() $.addClass @, 'disabled' Header.addShortcut sc if Conf['Hide Original Post Form'] $.asap (-> doc), -> $.addClass doc, 'hide-original-post-form' $.ready @initReady if Conf['Persistent QR'] unless g.BOARD.ID is 'f' and g.VIEW is 'index' $.on d, '4chanXInitFinished', @persist else $.ready @persist Post::callbacks.push name: 'Quick Reply' cb: @node initReady: -> QR.postingIsEnabled = !!$.id 'postForm' return unless QR.postingIsEnabled link = $.el 'h1', innerHTML: "#{if g.VIEW is 'thread' then 'Reply to Thread' else 'Start a Thread'}" className: "qr-link-container" $.on link.firstChild, 'click', -> $.event 'CloseMenu' QR.open() QR.nodes.com.focus() if Conf['QR Shortcut'] $.rmClass $('.qr-shortcut'), 'disabled' $.before $.id('postForm'), link $.on d, 'QRGetSelectedPost', ({detail: cb}) -> cb QR.selected $.on d, 'QRAddPreSubmitHook', ({detail: cb}) -> QR.preSubmitHooks.push cb <% if (type === 'crx') { %> $.on d, 'paste', QR.paste <% } %> $.on d, 'dragover', QR.dragOver $.on d, 'drop', QR.dropFile $.on d, 'dragstart dragend', QR.drag $.on d, 'ThreadUpdate', -> if g.DEAD QR.abort() else QR.status() node: -> $.on $('a[title="Quote this post"]', @nodes.info), 'click', QR.quote persist: -> return unless QR.postingIsEnabled QR.open() QR.hide() if Conf['Auto Hide QR'] open: -> if QR.nodes QR.nodes.el.hidden = false QR.unhide() return try QR.dialog() catch err delete QR.nodes Main.handleErrors message: 'Quick Reply dialog creation crashed.' error: err close: -> if QR.req QR.abort() return QR.nodes.el.hidden = true QR.cleanNotifications() d.activeElement.blur() $.rmClass QR.nodes.el, 'dump' unless Conf['Captcha Warning Notifications'] $.rmClass QR.captcha.nodes.input, 'error' if Conf['QR Shortcut'] $.toggleClass $('.qr-shortcut'), 'disabled' for i in QR.posts QR.posts[0].rm() QR.cooldown.auto = false QR.status() focusin: -> $.addClass QR.nodes.el, 'has-focus' focusout: -> $.rmClass QR.nodes.el, 'has-focus' hide: -> d.activeElement.blur() $.addClass QR.nodes.el, 'autohide' QR.nodes.autohide.checked = true unhide: -> $.rmClass QR.nodes.el, 'autohide' QR.nodes.autohide.checked = false toggleHide: -> if @checked QR.hide() else QR.unhide() error: (err) -> QR.open() if typeof err is 'string' el = $.tn err else el = err el.removeAttribute 'style' if QR.captcha.isEnabled and /captcha|verification/i.test el.textContent # Focus the captcha input on captcha error. QR.captcha.nodes.input.focus() if Conf['Captcha Warning Notifications'] QR.notifications.push new Notification 'warning', el else $.addClass QR.captcha.nodes.input, 'error' $.on QR.captcha.nodes.input, 'keydown', -> $.rmClass QR.captcha.nodes.input, 'error' else QR.notifications.push new Notification 'warning', el alert el.textContent if d.hidden notifications: [] cleanNotifications: -> for notification in QR.notifications notification.close() QR.notifications = [] status: -> return unless QR.nodes if g.DEAD value = 404 disabled = true QR.cooldown.auto = false value = if QR.req QR.req.progress else QR.cooldown.seconds or value {status} = QR.nodes status.value = unless value 'Submit' else if QR.cooldown.auto "Auto #{value}" else value status.disabled = disabled or false persona: pwd: '' always: {} init: -> QR.persona.getPassword() $.get 'QR.personas', Conf['QR.personas'], ({'QR.personas': personas}) -> types = name: [] email: [] sub: [] for item in personas.split '\n' QR.persona.parseItem item.trim(), types for type, arr of types QR.persona.loadPersonas type, arr return parseItem: (item, types) -> return if item[0] is '#' return unless match = item.match /(name|email|subject|password):"(.*)"/i [match, type, val] = match # Don't mix up item settings with val. item = item.replace match, '' boards = item.match(/boards:([^;]+)/i)?[1].toLowerCase() or 'global' if boards isnt 'global' and not ((boards.split ',').contains g.BOARD.ID) return if type is 'password' QR.persona.pwd = val return type = 'sub' if type is 'subject' if /always/i.test item QR.persona.always[type] = val unless types[type].contains val types[type].push val loadPersonas: (type, arr) -> list = $ "#list-#{type}", QR.nodes.el for val in arr # XXX Firefox displays empty """ $.add nodes.form, nodes.flashTag # Make a list of threads. for thread of g.BOARD.threads $.add nodes.thread, $.el 'option', value: thread textContent: "Thread No.#{thread}" $.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 dialog, 'focusin', QR.focusin $.on dialog, 'focusout', QR.focusout $.on nodes.autohide, 'change', QR.toggleHide $.on nodes.close, 'click', QR.close $.on nodes.dumpButton, 'click', -> nodes.el.classList.toggle 'dump' $.on nodes.addPost, 'click', -> new QR.post true $.on nodes.form, 'submit', QR.submit $.on nodes.fileRM, 'click', -> QR.selected.rmFile() $.on nodes.fileExtras, 'click', (e) -> e.stopPropagation() $.on nodes.spoiler, 'change', -> QR.selected.nodes.spoiler.click() $.on nodes.fileInput, 'change', QR.fileInput # save selected post's data for name in ['name', 'email', 'sub', 'com'] $.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 <% } %> QR.persona.init() new QR.post true QR.status() QR.cooldown.init() QR.captcha.init() $.add d.body, dialog # Create a custom event when the QR dialog is first initialized. # Use it to extend the QR's functionalities, or for XTRM RICE. $.event 'QRDialogCreation', null, dialog preSubmitHooks: [] submit: (e) -> e?.preventDefault() if QR.req QR.abort() return if QR.cooldown.seconds QR.cooldown.auto = !QR.cooldown.auto QR.status() return post = QR.posts[0] post.forceSave() if g.BOARD.ID is 'f' filetag = QR.nodes.flashTag.value threadID = post.thread thread = g.BOARD.threads[threadID] # prevent errors if threadID is 'new' threadID = null if ['vg', 'q'].contains(g.BOARD.ID) and !post.sub err = 'New threads require a subject.' else unless post.file or textOnly = !!$ 'input[name=textonly]', $.id 'postForm' err = 'No file selected.' else if g.BOARD.threads[threadID].isClosed err = 'You can\'t reply to this thread anymore.' else unless post.com or post.file err = 'No file selected.' else if post.file and thread.fileLimit err = 'Max limit of image replies has been reached.' else for hook in QR.preSubmitHooks if err = hook post, thread break if QR.captcha.isEnabled and !err {challenge, response} = QR.captcha.getOne() err = 'No valid captcha.' unless response QR.cleanNotifications() if err # stop auto-posting QR.cooldown.auto = false QR.status() QR.error err return # Enable auto-posting if we have stuff to post, disable it otherwise. QR.cooldown.auto = QR.posts.length > 1 if Conf['Auto Hide QR'] and !QR.cooldown.auto QR.hide() if !QR.cooldown.auto and $.x 'ancestor::div[@id="qr"]', d.activeElement # Unfocus the focused element if it is one within the QR and we're not auto-posting. d.activeElement.blur() post.lock() postData = resto: threadID name: post.name email: post.email sub: post.sub com: post.com upfile: post.file filetag: filetag spoiler: post.spoiler textonly: textOnly mode: 'regist' pwd: QR.persona.pwd recaptcha_challenge_field: challenge recaptcha_response_field: response callbacks = onload: QR.response onerror: -> # Connection error, or # www.4chan.org/banned delete QR.req post.unlock() QR.cooldown.auto = false QR.status() QR.error $.el 'span', innerHTML: """ 4chan X encountered an error while posting. [Banned?] [More info] """ opts = cred: true form: $.formData postData upCallbacks: onload: -> # Upload done, waiting for server response. QR.req.isUploadFinished = true QR.req.uploadEndTime = Date.now() QR.req.progress = '...' QR.status() onprogress: (e) -> # Uploading... QR.req.progress = "#{Math.round e.loaded / e.total * 100}%" QR.status() QR.req = $.ajax $.id('postForm').parentNode.action, callbacks, opts # Starting to upload might take some time. # Provide some feedback that we're starting to submit. QR.req.uploadStartTime = Date.now() QR.req.progress = '...' 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 post = QR.posts[0] post.unlock() tmpDoc = d.implementation.createHTMLDocument '' tmpDoc.documentElement.innerHTML = req.response if ban = $ '.banType', tmpDoc # banned/warning board = $('.board', tmpDoc).innerHTML err = $.el 'span', innerHTML: if ban.textContent.toLowerCase() is 'banned' "You are banned on #{board}! ;_;
" + "Click here to see the reason." else "You were issued a warning on #{board} as #{$('.nameBlock', tmpDoc).innerHTML}.
" + "Reason: #{$('.reason', tmpDoc).innerHTML}" else if err = tmpDoc.getElementById 'errmsg' # error! $('a', err)?.target = '_blank' # duplicate image link else if tmpDoc.title isnt 'Post successful!' err = 'Connection error with sys.4chan.org.' else if req.status isnt 200 err = "Error #{req.statusText} (#{req.status})" if err if /captcha|verification/i.test(err.textContent) or err is 'Connection error with sys.4chan.org.' # Remove the obnoxious 4chan Pass ad. if /mistyped/i.test err.textContent err = 'You seem to have mistyped the CAPTCHA.' # Enable auto-post if we have some cached captchas. QR.cooldown.auto = if QR.captcha.isEnabled !!QR.captcha.captchas.length else if err is 'Connection error with sys.4chan.org.' true else # Something must've gone terribly wrong if you get captcha errors without captchas. # Don't auto-post indefinitely in that case. false # 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() QR.error err return QR.cleanNotifications() h1 = $ 'h1', tmpDoc if Conf['Posting Success Notifications'] QR.notifications.push new Notification 'success', h1.textContent, 5 QR.persona.set post [_, threadID, postID] = h1.nextSibling.textContent.match /thread:(\d+),no:(\d+)/ postID = +postID threadID = +threadID or postID isReply = threadID isnt postID QR.db.set boardID: g.BOARD.ID threadID: threadID postID: postID val: true ThreadUpdater.postID = postID # Post/upload confirmed as successful. $.event 'QRPostSuccessful', { board: g.BOARD threadID postID } # Enable auto-posting if we have stuff to post, disable it otherwise. QR.cooldown.auto = QR.posts.length > 1 and isReply unless Conf['Persistent QR'] or QR.cooldown.auto QR.close() else post.rm() QR.cooldown.set {req, post, isReply} if threadID is postID # new thread URL = "/#{g.BOARD}/res/#{threadID}" else if g.VIEW is 'index' and !QR.cooldown.auto and Conf['Open Post in New Tab'] # replying from the index URL = "/#{g.BOARD}/res/#{threadID}#p#{postID}" if URL if Conf['Open Post in New Tab'] $.open "/#{g.BOARD}/res/#{threadID}" else window.location = "/#{g.BOARD}/res/#{threadID}" QR.status() abort: -> if QR.req and !QR.req.isUploadFinished QR.req.abort() delete QR.req QR.posts[0].unlock() QR.notifications.push new Notification 'info', 'QR upload aborted.', 5 QR.status()