Keybinds = init: -> return if !Conf['Keybinds'] for hotkey of Config.hotkeys $.sync hotkey, Keybinds.sync init = -> $.off d, '4chanXInitFinished', init $.on d, 'keydown', Keybinds.keydown for node in $$ '[accesskey]' node.removeAttribute 'accesskey' return $.on d, '4chanXInitFinished', init sync: (key, hotkey) -> Conf[hotkey] = key keydown: (e) -> return unless key = Keybinds.keyCode e {target} = e if target.nodeName in ['INPUT', 'TEXTAREA'] return unless /(Esc|Alt|Ctrl|Meta|Shift\+\w{2,})/.test(key) and not /^Alt\+(\d|Up|Down|Left|Right)$/.test(key) unless ( g.VIEW not in ['index', 'thread'] or g.VIEW is 'index' and Conf['JSON Navigation'] and Conf['Index Mode'] is 'catalog' or g.VIEW is 'index' and g.BOARD.ID is 'f' ) threadRoot = Nav.getThread() if op = $ '.op', threadRoot thread = Get.postFromNode(op).thread switch key # QR & Options when Conf['Toggle board list'] return unless Conf['Custom Board Navigation'] Header.toggleBoardList() when Conf['Toggle header'] Header.toggleBarVisibility() when Conf['Open empty QR'] return unless QR.postingIsEnabled Keybinds.qr() when Conf['Open QR'] return unless QR.postingIsEnabled and threadRoot Keybinds.qr threadRoot when Conf['Open settings'] Settings.open() when Conf['Close'] if Settings.dialog Settings.close() else if (notifications = $$ '.notification').length for notification in notifications $('.close', notification).click() else if QR.nodes and not (QR.nodes.el.hidden or window.getComputedStyle(QR.nodes.form).display is 'none') if Conf['Persistent QR'] QR.hide() else QR.close() else if Embedding.lastEmbed Embedding.closeFloat() else return when Conf['Spoiler tags'] return unless target.nodeName is 'TEXTAREA' Keybinds.tags 'spoiler', target when Conf['Code tags'] return unless target.nodeName is 'TEXTAREA' Keybinds.tags 'code', target when Conf['Eqn tags'] return unless target.nodeName is 'TEXTAREA' Keybinds.tags 'eqn', target when Conf['Math tags'] return unless target.nodeName is 'TEXTAREA' Keybinds.tags 'math', target when Conf['SJIS tags'] return unless target.nodeName is 'TEXTAREA' Keybinds.tags 'sjis', target when Conf['Toggle sage'] return unless QR.nodes and !QR.nodes.el.hidden Keybinds.sage() when Conf['Submit QR'] return unless QR.nodes and !QR.nodes.el.hidden QR.submit() if !QR.status() # Index/Thread related when Conf['Update'] switch g.VIEW when 'thread' return unless Conf['Thread Updater'] ThreadUpdater.update() when 'index' return unless Conf['JSON Navigation'] and g.BOARD.ID isnt 'f' Index.update() else return when Conf['Watch'] return unless thread ThreadWatcher.toggle thread # Images when Conf['Expand image'] return unless ImageExpand.enabled and threadRoot Keybinds.img threadRoot when Conf['Expand images'] return unless ImageExpand.enabled and threadRoot Keybinds.img threadRoot, true when Conf['Open Gallery'] return unless Gallery.enabled Gallery.cb.toggle() when Conf['fappeTyme'] return unless Conf['Fappe Tyme'] and g.VIEW in ['index', 'thread'] FappeTyme.toggle 'fappe' when Conf['werkTyme'] return unless Conf['Werk Tyme'] and g.VIEW in ['index', 'thread'] FappeTyme.toggle 'werk' # Board Navigation when Conf['Front page'] if Conf['JSON Navigation'] and g.VIEW is 'index' and g.BOARD.ID isnt 'f' Index.userPageNav 1 else window.location = "/#{g.BOARD}/" when Conf['Open front page'] $.open "/#{g.BOARD}/" when Conf['Next page'] return unless g.VIEW is 'index' and g.BOARD.ID isnt 'f' if Conf['JSON Navigation'] return unless Conf['Index Mode'] in ['paged', 'infinite'] $('.next button', Index.pagelist).click() else if form = $ '.next form' window.location = form.action when Conf['Previous page'] return unless g.VIEW is 'index' and g.BOARD.ID isnt 'f' if Conf['JSON Navigation'] return unless Conf['Index Mode'] in ['paged', 'infinite'] $('.prev button', Index.pagelist).click() else if form = $ '.prev form' window.location = form.action when Conf['Search form'] return unless g.VIEW is 'index' and g.BOARD.ID isnt 'f' searchInput = if Conf['JSON Navigation'] then Index.searchInput else $.id('search-box') Header.scrollToIfNeeded searchInput searchInput.focus() when Conf['Paged mode'] return unless Conf['JSON Navigation'] and g.BOARD.ID isnt 'f' window.location = if g.VIEW is 'index' then '#paged' else "/#{g.BOARD}/#paged" when Conf['Infinite scrolling mode'] return unless Conf['JSON Navigation'] and g.BOARD.ID isnt 'f' window.location = if g.VIEW is 'index' then '#infinite' else "/#{g.BOARD}/#infinite" when Conf['All pages mode'] return unless Conf['JSON Navigation'] and g.BOARD.ID isnt 'f' window.location = if g.VIEW is 'index' then '#all-pages' else "/#{g.BOARD}/#all-pages" when Conf['Open catalog'] return if g.BOARD.ID is 'f' window.location = CatalogLinks.catalog() when Conf['Cycle sort type'] return unless Conf['JSON Navigation'] and g.VIEW is 'index' and g.BOARD.ID isnt 'f' Index.cycleSortType() # Thread Navigation when Conf['Next thread'] return unless g.VIEW is 'index' and threadRoot Nav.scroll +1 when Conf['Previous thread'] return unless g.VIEW is 'index' and threadRoot Nav.scroll -1 when Conf['Expand thread'] return unless g.VIEW is 'index' and threadRoot ExpandThread.toggle thread when Conf['Open thread'] return unless g.VIEW is 'index' and threadRoot Keybinds.open thread when Conf['Open thread tab'] return unless g.VIEW is 'index' and threadRoot Keybinds.open thread, true # Reply Navigation when Conf['Next reply'] return unless threadRoot Keybinds.hl +1, threadRoot when Conf['Previous reply'] return unless threadRoot Keybinds.hl -1, threadRoot when Conf['Deselect reply'] return unless threadRoot Keybinds.hl 0, threadRoot when Conf['Hide'] return unless thread ThreadHiding.toggle thread if ThreadHiding.db when Conf['Previous Post Quoting You'] return unless threadRoot QuoteYou.cb.seek 'preceding' when Conf['Next Post Quoting You'] return unless threadRoot QuoteYou.cb.seek 'following' <% if (tests_enabled) { %> when 't' return unless threadRoot BuildTest.testAll() <% } %> else return e.preventDefault() e.stopPropagation() keyCode: (e) -> key = switch kc = e.keyCode when 8 # return '' when 13 'Enter' when 27 'Esc' when 32 'Space' when 37 'Left' when 38 'Up' when 39 'Right' when 40 'Down' when 188 'Comma' when 190 'Period' else if 48 <= kc <= 57 or 65 <= kc <= 90 # 0-9, A-Z String.fromCharCode(kc).toLowerCase() else if 96 <= kc <= 105 # numpad 0-9 String.fromCharCode(kc - 48).toLowerCase() else null if key if e.altKey then key = 'Alt+' + key if e.ctrlKey then key = 'Ctrl+' + key if e.metaKey then key = 'Meta+' + key if e.shiftKey then key = 'Shift+' + key key qr: (thread) -> QR.open() if thread? QR.quote.call $ 'input', $('.post.highlight', thread) or thread QR.nodes.com.focus() tags: (tag, ta) -> supported = switch tag when 'spoiler' then !!$ '.postForm input[name=spoiler]' when 'code' then g.BOARD.ID is 'g' when 'math', 'eqn' then g.BOARD.ID is 'sci' when 'sjis' then g.BOARD.ID is 'jp' new Notice 'warning', "[#{tag}] tags are not supported on /#{g.BOARD}/.", 20 unless supported value = ta.value selStart = ta.selectionStart selEnd = ta.selectionEnd ta.value = value[...selStart] + "[#{tag}]" + value[selStart...selEnd] + "[/#{tag}]" + value[selEnd..] # Move the caret to the end of the selection. range = "[#{tag}]".length + selEnd ta.setSelectionRange range, range # Fire the 'input' event $.event 'input', null, ta sage: -> isSage = /sage/i.test QR.nodes.email.value QR.nodes.email.value = if isSage "" else "sage" img: (thread, all) -> if all ImageExpand.cb.toggleAll() else post = Get.postFromNode $('.post.highlight', thread) or $ '.op', thread ImageExpand.toggle post open: (thread, tab) -> return if g.VIEW isnt 'index' url = "/#{thread.board}/thread/#{thread}" if tab $.open url else location.href = url hl: (delta, thread) -> postEl = $ '.reply.highlight', thread unless delta $.rmClass postEl, 'highlight' if postEl return if postEl {height} = postEl.getBoundingClientRect() if Header.getTopOf(postEl) >= -height and Header.getBottomOf(postEl) >= -height # We're at least partially visible root = postEl.parentNode axis = if delta is +1 'following' else 'preceding' return unless next = $.x "#{axis}-sibling::div[contains(@class,'replyContainer') and not(@hidden) and not(child::div[@class='stub'])][1]/child::div[contains(@class,'reply')]", root Header.scrollToIfNeeded next, delta is +1 @focus next $.rmClass postEl, 'highlight' return $.rmClass postEl, 'highlight' replies = $$ '.reply', thread replies.reverse() if delta is -1 for reply in replies if delta is +1 and Header.getTopOf(reply) > 0 or delta is -1 and Header.getBottomOf(reply) > 0 @focus reply return focus: (post) -> $.addClass post, 'highlight'