Main = init: -> if location.hostname is 'www.google.com' return $.ready -> Captcha.noscript.initFrame() g.threads = new SimpleDict() g.posts = new SimpleDict() pathname = location.pathname.split '/' g.BOARD = new Board pathname[1] return if g.BOARD.ID in ['z', 'fk'] g.VIEW = switch pathname[2] when 'res', 'thread' 'thread' when 'catalog' 'catalog' when 'archive' 'archive' else 'index' if g.VIEW is 'thread' g.THREADID = +pathname[3] # flatten Config into Conf # and get saved or default values flatten = (parent, obj) -> if obj instanceof Array Conf[parent] = obj[0] else if typeof obj is 'object' for key, val of obj flatten key, val else # string or number Conf[parent] = obj return flatten null, Config for db in DataBoard.keys Conf[db] = boards: {} Conf['selectedArchives'] = {} $.get Conf, (items) -> $.extend Conf, items $.asap (-> doc = d.documentElement), Main.initFeatures # set up CSS when
is completely loaded $.asap (-> doc = d.documentElement), -> $.onExists doc, 'body', false, Main.initStyle initFeatures: -> switch location.hostname when 'a.4cdn.org' return when 'sys.4chan.org' return when 'i.4cdn.org' $.asap (-> d.readyState isnt 'loading'), -> if Conf['404 Redirect'] and d.title in ['4chan - Temporarily Offline', '4chan - 404 Not Found'] Redirect.init() pathname = location.pathname.split '/' URL = Redirect.to 'file', boardID: g.BOARD.ID filename: pathname[pathname.length - 1] Redirect.navigate URL else if video = $ 'video' if Conf['Volume in New Tab'] Volume.setup video if Conf['Loop in New Tab'] video.loop = true video.controls = false video.play() ImageCommon.addControls video return if Conf['Normalize URL'] and g.VIEW is 'thread' pathname = location.pathname.split '/' if pathname[2] isnt 'thread' or pathname.length > 4 pathname[2] = 'thread' history.replaceState null, '', pathname[0...4].join('/') + location.hash # c.time 'All initializations' for [name, feature] in Main.features # c.time "#{name} initialization" try feature.init() catch err Main.handleErrors message: "\"#{name}\" initialization crashed." error: err # finally # c.timeEnd "#{name} initialization" # c.timeEnd 'All initializations' $.ready Main.initReady initStyle: -> return if !Main.isThisPageLegit() or $.hasClass doc, 'fourchan-x' # disable the mobile layout $('link[href*=mobile]', d.head)?.disabled = true $.addClass doc, 'fourchan-x', 'seaweedchan' $.addClass doc, if g.VIEW is 'thread' then 'thread-view' else g.VIEW $.addClass doc, if chrome? then 'blink' else 'gecko' $.addStyle Main.css, 'fourchanx-css' Main.setClass() setClass: -> if g.VIEW is 'catalog' $.addClass doc, $.id('base-css').href.match(/catalog_(\w+)/)[1].replace('_new', '').replace /_+/g, '-' return style = 'yotsuba-b' mainStyleSheet = $ 'link[title=switch]', d.head styleSheets = $$ 'link[rel="alternate stylesheet"]', d.head setStyle = -> $.rmClass doc, style for styleSheet in styleSheets if styleSheet.href is mainStyleSheet.href style = styleSheet.title.toLowerCase().replace('new', '').trim().replace /\s+/g, '-' break $.addClass doc, style setStyle() return unless mainStyleSheet new MutationObserver(setStyle).observe mainStyleSheet, attributes: true attributeFilter: ['href'] initReady: -> if d.title in ['4chan - Temporarily Offline', '4chan - 404 Not Found'] if g.VIEW is 'thread' ThreadWatcher.set404 g.BOARD.ID, g.THREADID, -> if Conf['404 Redirect'] href = Redirect.to 'thread', boardID: g.BOARD.ID threadID: g.THREADID postID: +location.hash.match /\d+/ # post number or 0 Redirect.navigate href, "/#{g.BOARD}/" return # 4chan Pass Link if styleSelector = $.id 'styleSelector' passLink = $.el 'a', textContent: '4chan Pass' href: 'javascript:;' $.on passLink, 'click', -> window.open '//sys.4chan.org/auth', 'This will steal your data.' 'left=0,top=0,width=500,height=255,toolbar=0,resizable=0' $.before styleSelector.previousSibling, [$.tn '['; passLink, $.tn ']\u00A0\u00A0'] # Parse HTML or skip it and start building from JSON. unless Conf['JSON Navigation'] and g.VIEW is 'index' Main.initThread() else $.event '4chanXInitFinished' $.get 'previousversion', null, ({previousversion}) -> return if previousversion is g.VERSION if previousversion el = $.el 'span', <%= html('${g.NAME} has been updated to version ${g.VERSION}.') %> new Notice 'info', el, 15 else Settings.open() $.set 'previousversion', g.VERSION if Conf['Show Support Message'] <% if (type === 'userscript') { %> GMver = GM_info.version.split '.' for v, i in "<%= meta.min.greasemonkey %>".split '.' continue if v is GMver[i] (v < GMver[i]) or new Notice 'warning', "Your version of Greasemonkey is outdated (v#{GM_info.version} instead of v<%= meta.min.greasemonkey %> minimum) and <%= meta.name %> may not operate correctly.", 30 break <% } %> try localStorage.getItem '4chan-settings' catch err new Notice 'warning', 'Cookies need to be enabled on 4chan for <%= meta.name %> to operate properly.', 30 initThread: -> if board = $ '.board' threads = [] posts = [] for threadRoot in $$ '.board > .thread', board thread = new Thread +threadRoot.id[1..], g.BOARD threads.push thread for postRoot in $$ '.thread > .postContainer', threadRoot try 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.#{postRoot.id.match(/\d+/)} failed. Post will be skipped." error: err Main.handleErrors errors if errors if g.VIEW is 'thread' scriptData = Get.scriptData() threads[0].postLimit = /\bbumplimit *= *1\b/.test scriptData threads[0].fileLimit = /\bimagelimit *= *1\b/.test scriptData threads[0].ipCount = if m = scriptData.match /\bunique_ips *= *(\d+)\b/ then +m[1] Main.callbackNodes Thread, threads Main.callbackNodesDB Post, posts, -> QuoteThreading.insert post for post in posts $.event '4chanXInitFinished' else $.event '4chanXInitFinished' callbackNodes: (klass, nodes) -> i = 0 cb = klass.callbacks while node = nodes[i++] cb.execute node return callbackNodesDB: (klass, nodes, cb) -> i = 0 cbs = klass.callbacks fn = -> return false unless node = nodes[i] cbs.execute node ++i % 25 softTask = -> while fn() continue unless nodes[i] cb() if cb return setTimeout softTask, 0 softTask() handleErrors: (errors) -> unless errors instanceof Array error = errors else if errors.length is 1 error = errors[0] if error new Notice 'error', Main.parseError(error), 15 return div = $.el 'div', <%= html('${errors.length} errors occurred. [show]') %> $.on div.lastElementChild, 'click', -> [@textContent, logs.hidden] = if @textContent is 'show' ['hide', false] else ['show', true] logs = $.el 'div', hidden: true for error in errors $.add logs, Main.parseError error new Notice 'error', [div, logs], 30 parseError: (data) -> c.error data.message, data.error.stack message = $.el 'div', textContent: data.message error = $.el 'div', textContent: "#{data.error.name or 'Error'}: #{data.error.message or 'see console for details'}" [message, error] isThisPageLegit: -> # 404 error page or similar. 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', '504 Gateway Time-out'] Main.thisPageIsLegit ready: (cb) -> $.ready -> cb() if Main.isThisPageLegit() css: """ <%= grunt.file.read('src/General/css/font-awesome.css').replace(/\s+/g, ' ').replace(/\\/g, '\\\\').trim() %> <%= grunt.file.read('src/General/css/style.css').replace(/\s+/g, ' ').trim() %> <%= grunt.file.read('src/General/css/yotsuba.css').replace(/\s+/g, ' ').trim() %> <%= grunt.file.read('src/General/css/yotsuba-b.css').replace(/\s+/g, ' ').trim() %> <%= grunt.file.read('src/General/css/futaba.css').replace(/\s+/g, ' ').trim() %> <%= grunt.file.read('src/General/css/burichan.css').replace(/\s+/g, ' ').trim() %> <%= grunt.file.read('src/General/css/tomorrow.css').replace(/\s+/g, ' ').trim() %> <%= grunt.file.read('src/General/css/photon.css').replace(/\s+/g, ' ').trim() %> """ features: [ ['Polyfill', Polyfill] ['Redirect', Redirect] ['Header', Header] ['Catalog Links', CatalogLinks] ['Settings', Settings] ['Index Generator', Index] ['Disable Autoplay', AntiAutoplay] ['Announcement Hiding', PSAHiding] ['Fourchan thingies', Fourchan] ['Color User IDs', IDColor] ['Highlight by User ID', IDHighlight] ['Custom CSS', CustomCSS] ['Linkify', Linkify] ['Reveal Spoilers', RemoveSpoilers] ['Resurrect Quotes', Quotify] ['Filter', Filter] ['Thread Hiding Buttons', ThreadHiding] ['Reply Hiding Buttons', PostHiding] ['Recursive', Recursive] ['Strike-through Quotes', QuoteStrikeThrough] ['Quick Reply', QR] ['Menu', Menu] ['Index Generator (Menu)', Index.menu] ['Report Link', ReportLink] ['Thread Hiding (Menu)', ThreadHiding.menu] ['Reply Hiding (Menu)', PostHiding.menu] ['Delete Link', DeleteLink] ['Filter (Menu)', Filter.menu] ['Download Link', DownloadLink] ['Archive Link', ArchiveLink] ['Quote Inlining', QuoteInline] ['Quote Previewing', QuotePreview] ['Quote Backlinks', QuoteBacklink] ['Mark Quotes of You', QuoteYou] ['Mark OP Quotes', QuoteOP] ['Mark Cross-thread Quotes', QuoteCT] ['Anonymize', Anonymize] ['Time Formatting', Time] ['Relative Post Dates', RelativeDates] ['File Info Formatting', FileInfo] ['Fappe Tyme', FappeTyme] ['Gallery', Gallery] ['Gallery (menu)', Gallery.menu] ['Sauce', Sauce] ['Image Expansion', ImageExpand] ['Image Expansion (Menu)', ImageExpand.menu] ['Reveal Spoiler Thumbnails', RevealSpoilers] ['Image Loading', ImageLoader] ['Image Hover', ImageHover] ['Volume Control', Volume] ['WEBM Metadata', Metadata] ['Comment Expansion', ExpandComment] ['Thread Expansion', ExpandThread] ['Thread Excerpt', ThreadExcerpt] ['Favicon', Favicon] ['Unread', Unread] ['Quote Threading', QuoteThreading] ['Thread Stats', ThreadStats] ['Thread Updater', ThreadUpdater] ['Thread Watcher', ThreadWatcher] ['Thread Watcher (Menu)', ThreadWatcher.menu] ['Mark New IPs', MarkNewIPs] ['Index Navigation', Nav] ['Keybinds', Keybinds] ['Banner', Banner] <% if (tests_enabled) { %> ['Build Test', BuildTest] <% } %> ] Main.init()