diff --git a/src/Filtering/Filter.coffee b/src/Filtering/Filter.coffee index f5790e148..ba09d260f 100644 --- a/src/Filtering/Filter.coffee +++ b/src/Filtering/Filter.coffee @@ -246,18 +246,64 @@ Filter = re $.set type, save, cb + removeFilters: (type, res, cb) -> + $.get type, Conf[type], (item) -> + save = item[type] + res = res.map(Filter.escape).join('|') + save = save.replace RegExp("(?:$\n|^)(?:#{res})$", 'mg'), '' + $.set type, save, cb + + showFilters: (type) -> + # Open the settings and display & focus the relevant filter textarea. + Settings.open 'Filter' + section = $ '.section-container' + select = $ 'select[name=filter]', section + select.value = type + Settings.selectFilter.call select + $.onExists section, 'textarea', (ta) -> + tl = ta.textLength + ta.setSelectionRange tl, tl + ta.focus() + quickFilterMD5: -> post = Get.postFromNode @ - return unless post.file - Filter.addFilter 'MD5', "/#{post.file.MD5}/" + files = post.files.filter((f) -> f.MD5) + return unless files.length + filter = files.map((f) -> "/#{f.MD5}/").join('\n') + Filter.addFilter 'MD5', filter origin = post.origin or post if origin.isReply PostHiding.hide origin else if g.VIEW is 'index' ThreadHiding.hide origin.thread - # If post is still visible, give an indication that the MD5 was filtered. - if post.nodes.post.getBoundingClientRect().height - new Notice 'info', 'MD5 filtered.', 2 + {notice} = Filter.quickFilterMD5 + if notice + notice.filters.push filter + notice.posts.push origin + $('span', notice.el).textContent = "#{notice.filters.length} MD5s filtered." + else + msg = $.el 'div', + <%= html('MD5 filtered. [show] [undo]') %> + notice = Filter.quickFilterMD5.notice = new Notice 'info', msg, undefined, -> + delete Filter.quickFilterMD5.notice + notice.filters = [filter] + notice.posts = [origin] + links = $$ 'a', msg + $.on links[0], 'click', Filter.quickFilterCB.show.bind(notice) + $.on links[1], 'click', Filter.quickFilterCB.undo.bind(notice) + + quickFilterCB: + show: -> + Filter.showFilters 'MD5' + @close() + undo: -> + Filter.removeFilters 'MD5', @filters + for post in @posts + if post.isReply + PostHiding.show post + else if g.VIEW is 'index' + ThreadHiding.show post.thread + @close() escape: (value) -> value.replace /// @@ -346,13 +392,4 @@ Filter = ).join('\n') Filter.addFilter type, res, -> - # Open the settings and display & focus the relevant filter textarea. - Settings.open 'Filter' - section = $ '.section-container' - select = $ 'select[name=filter]', section - select.value = type - Settings.selectFilter.call select - $.onExists section, 'textarea', (ta) -> - tl = ta.textLength - ta.setSelectionRange tl, tl - ta.focus() + Filter.showFilters type diff --git a/src/Filtering/ThreadHiding.coffee b/src/Filtering/ThreadHiding.coffee index bf3329246..4e250227a 100644 --- a/src/Filtering/ThreadHiding.coffee +++ b/src/Filtering/ThreadHiding.coffee @@ -145,7 +145,7 @@ ThreadHiding = a makeStub: (thread, root) -> - numReplies = $$(g.SITE.selectors.postContainer + g.SITE.selectors.relative.replyPost, root).length + numReplies = $$(g.SITE.selectors.replyOriginal, root).length numReplies += +summary.textContent.match /\d+/ if summary = $ g.SITE.selectors.summary, root a = ThreadHiding.makeButton thread, 'show' diff --git a/src/General/Get.coffee b/src/General/Get.coffee index b2438e5e7..4dae77b0f 100644 --- a/src/General/Get.coffee +++ b/src/General/Get.coffee @@ -1,4 +1,6 @@ Get = + url: (type, IDs, args...) -> + g.sites[IDs.siteID]?.urls[type] IDs, args... threadExcerpt: (thread) -> {OP} = thread excerpt = ("/#{decodeURIComponent thread.board.ID}/ - ") + ( diff --git a/src/General/Header.coffee b/src/General/Header.coffee index 941440aa9..f2f003cab 100644 --- a/src/General/Header.coffee +++ b/src/General/Header.coffee @@ -234,7 +234,10 @@ Header = href: "/#{g.BOARD.ID}/" textContent: text or g.BOARD.ID className: 'current' - if /-catalog/.test(t) + if /-index/.test(t) + a.dataset.only = 'index' + else if /-catalog/.test(t) + a.dataset.only = 'catalog' a.href += 'catalog.html' else if /-(archive|expired)/.test(t) a = a.firstChild # Its text node. @@ -263,9 +266,10 @@ Header = text or boardID if m = t.match /-(index|catalog)/ - unless boardID is 'f' and m[1] is 'catalog' + urlIC = CatalogLinks[m[1]] {siteID: '4chan.org', boardID} + if urlIC a.dataset.only = m[1] - a.href = CatalogLinks[m[1]] boardID + a.href = urlIC $.addClass a, 'catalog' if m[1] is 'catalog' else return a.firstChild # Its text node. diff --git a/src/General/Index.coffee b/src/General/Index.coffee index 7164acd18..6cbd4fe54 100644 --- a/src/General/Index.coffee +++ b/src/General/Index.coffee @@ -2,14 +2,17 @@ Index = showHiddenThreads: false changed: {} + enabledOn: ({siteID, boardID}) -> + Conf['JSON Index'] and g.sites[siteID].software is 'yotsuba' and boardID isnt 'f' + init: -> - return unless g.VIEW is 'index' and g.BOARD.ID isnt 'f' + return unless g.VIEW is 'index' # For IndexRefresh events $.one d, '4chanXInitFinished', @cb.initFinished $.on d, 'PostsInserted', @cb.postsInserted - return unless Conf['JSON Index'] + return unless @enabledOn g.BOARD @enabled = true @@ -197,7 +200,7 @@ Index = menu: init: -> - return if g.VIEW isnt 'index' or !Conf['JSON Index'] or !Conf['Menu'] or !Conf['Thread Hiding Link'] or g.BOARD.ID is 'f' + return unless g.VIEW is 'index' and Conf['Menu'] and Conf['Thread Hiding Link'] and Index.enabledOn(g.BOARD) Menu.menu.addEntry el: $.el 'a', diff --git a/src/General/Settings.coffee b/src/General/Settings.coffee index 133164d82..cccf7968b 100644 --- a/src/General/Settings.coffee +++ b/src/General/Settings.coffee @@ -561,10 +561,13 @@ Settings = $.id('lastarchivecheck').textContent = 'never' items = {} - for name in ['archiveLists', 'archiveAutoUpdate', 'fourchanImageHost', 'captchaLanguage', 'captchaServiceDomain', 'boardnav', 'time', 'timeLocale', 'backlink', 'pastedname', 'fileInfo', 'QR.personas', 'favicon', 'usercss', 'customCooldown', 'jsWhitelist'] + for name, input of inputs when name not in ['captchaServiceKey', 'Interval', 'Custom CSS'] items[name] = Conf[name] - input = inputs[name] - event = if name in ['archiveLists', 'archiveAutoUpdate', 'QR.personas', 'favicon', 'usercss'] then 'change' else 'input' + event = if ( + input.nodeName is 'SELECT' or + input.type in ['checkbox', 'radio'] or + (input.nodeName is 'TEXTAREA' and name not of Settings) + ) then 'change' else 'input' $.on input, event, $.cb[if input.type is 'checkbox' then 'checked' else 'value'] $.on input, event, Settings[name] if name of Settings diff --git a/src/General/Settings/Advanced.html b/src/General/Settings/Advanced.html index 36333e97e..d0066b3a0 100644 --- a/src/General/Settings/Advanced.html +++ b/src/General/Settings/Advanced.html @@ -19,6 +19,16 @@ Last updated: +
+ + + diff --git a/src/Miscellaneous/Banner.coffee b/src/Miscellaneous/Banner.coffee index 271deaa0d..87705d9bd 100644 --- a/src/Miscellaneous/Banner.coffee +++ b/src/Miscellaneous/Banner.coffee @@ -1,6 +1,4 @@ Banner = - banners: `<%= JSON.stringify(readJSON('banners.json')) %>` - init: -> if Conf['Custom Board Titles'] @db = new DataBoard 'customTitles', null, true @@ -44,7 +42,7 @@ Banner = cb: toggle: -> unless Banner.choices?.length - Banner.choices = Banner.banners.slice() + Banner.choices = Conf['knownBanners'].split(',').slice() i = Math.floor(Banner.choices.length * Math.random()) banner = Banner.choices.splice i, 1 $('img', @parentNode).src = "//s.4cdn.org/image/title/#{banner}" diff --git a/src/Miscellaneous/CatalogLinks.coffee b/src/Miscellaneous/CatalogLinks.coffee index 91c91a8c3..27af6d5a6 100644 --- a/src/Miscellaneous/CatalogLinks.coffee +++ b/src/Miscellaneous/CatalogLinks.coffee @@ -13,10 +13,11 @@ CatalogLinks = link.href = CatalogLinks.index() when "/#{g.BOARD}/catalog" link.href = CatalogLinks.catalog() - if g.VIEW is 'catalog' and Conf['JSON Index'] and Conf['Use <%= meta.name %> Catalog'] + if g.VIEW is 'catalog' and (catalogURL = CatalogLinks.catalog()) isnt g.SITE.urls.catalog?(g.BOARD) catalogLink = link.parentNode.cloneNode true - catalogLink.firstElementChild.textContent = '<%= meta.name %> Catalog' - catalogLink.firstElementChild.href = CatalogLinks.catalog() + link2 = catalogLink.firstElementChild + link2.href = catalogURL + link2.textContent = if link2.hostname is location.hostname then '<%= meta.name %> Catalog' else 'External Catalog' $.after link.parentNode, [$.tn(' '), catalogLink] return @@ -57,33 +58,63 @@ CatalogLinks = setLinks: (list) -> return unless (CatalogLinks.enabled ? Conf['Catalog Links']) and list + # do not transform links unless they differ from the expected value at most by this tail + tail = /(?:index)?(?:\.\w+)?$/ + for a in $$('a:not([data-only])', list) - continue if ( - a.hostname not in ['boards.4chan.org', 'boards.4channel.org', 'catalog.neet.tv'] or - !(board = a.pathname.split('/')[1]) or - board in ['f', 'status', '4chan'] or - a.pathname.split('/')[2] is 'archive' or - $.hasClass a, 'external' - ) + {siteID, boardID} = a.dataset + unless siteID and boardID + {siteID, boardID, VIEW} = Site.parseURL a + continue unless ( + siteID and boardID and + VIEW in ['index', 'catalog'] and + (a.dataset.indexOptions or a.href.replace(tail, '') is Get.url(VIEW, {siteID, boardID}).replace(tail, '')) + ) + $.extend a.dataset, {siteID, boardID} - # Href is easier than pathname because then we don't have - # conditions where External Catalog has been disabled between switches. - a.href = if Conf['Header catalog links'] then CatalogLinks.catalog(board) else "//#{BoardConfig.domain(board)}/#{board}/" - - if a.dataset.indexOptions and a.hostname in ['boards.4chan.org', 'boards.4channel.org'] and a.pathname.split('/')[2] is '' - a.href += (if a.hash then '/' else '#') + a.dataset.indexOptions + board = {siteID, boardID} + url = if Conf['Header catalog links'] then CatalogLinks.catalog(board) else Get.url('index', board) + if url + a.href = url + if a.dataset.indexOptions and url.split('#')[0] is Get.url('index', board) + a.href += (if a.hash then '/' else '#') + a.dataset.indexOptions return - catalog: (board=g.BOARD.ID) -> - if Conf['External Catalog'] and board in ['3', 'a', 'adv', 'an', 'asp', 'biz', 'c', 'cgl', 'ck', 'cm', 'co', 'diy', 'f', 'fa', 'fit', 'g', 'gd', 'his', 'i', 'int', 'jp', 'k', 'lgbt', 'lit', 'm', 'mlp', 'mu', 'n', 'news', 'o', 'out', 'p', 'po', 'pol', 's4s', 'sci', 'sp', 'tg', 'toy', 'trv', 'tv', 'v', 'vg', 'vip', 'vp', 'vr', 'w', 'wg', 'wsg', 'wsr', 'x'] - "//catalog.neet.tv/#{board}/" - else if Conf['JSON Index'] and Conf['Use <%= meta.name %> Catalog'] - if location.hostname in ['boards.4chan.org', 'boards.4channel.org'] and g.BOARD.ID is board and g.VIEW is 'index' then '#catalog' else "//#{BoardConfig.domain(board)}/#{board}/#catalog" - else - "//#{BoardConfig.domain(board)}/#{board}/catalog" + externalParse: -> + CatalogLinks.externalList = {} + for line in Conf['externalCatalogURLs'].split '\n' + continue if line[0] is '#' + url = line.split(';')[0] + boards = Filter.parseBoards(line.match(/;boards:([^;]+)/)?[1] or '*') + excludes = Filter.parseBoards(line.match(/;exclude:([^;]+)/)?[1]) or {} + for board of boards + unless excludes[board] or excludes[board.split('/')[0] + '/*'] + CatalogLinks.externalList[board] = url + return - index: (board=g.BOARD.ID) -> - if Conf['JSON Index'] and board isnt 'f' - if location.hostname in ['boards.4chan.org', 'boards.4channel.org'] and g.BOARD.ID is board and g.VIEW is 'index' then '#index' else "//#{BoardConfig.domain(board)}/#{board}/#index" + external: ({siteID, boardID}) -> + CatalogLinks.externalParse() unless CatalogLinks.externalList + external = (CatalogLinks.externalList["#{siteID}/#{boardID}"] or CatalogLinks.externalList["#{siteID}/*"]) + if external then external.replace(/%board/g, boardID) else undefined + + jsonIndex: (board, hash) -> + if g.SITE.ID is board.siteID and g.BOARD.ID is board.boardID and g.VIEW is 'index' + hash else - "//#{BoardConfig.domain(board)}/#{board}/" + Get.url('index', board) + hash + + catalog: (board=g.BOARD) -> + if Conf['External Catalog'] and (external = CatalogLinks.external board) + external + else if Index.enabledOn(board) and Conf['Use <%= meta.name %> Catalog'] + CatalogLinks.jsonIndex board, '#catalog' + else if (nativeCatalog = Get.url 'catalog', board) + nativeCatalog + else + CatalogLinks.external board + + index: (board=g.BOARD) -> + if Index.enabledOn(board) + CatalogLinks.jsonIndex board, '#index' + else + Get.url 'index', board diff --git a/src/Miscellaneous/ExpandThread.coffee b/src/Miscellaneous/ExpandThread.coffee index 3ac687784..6accc9719 100644 --- a/src/Miscellaneous/ExpandThread.coffee +++ b/src/Miscellaneous/ExpandThread.coffee @@ -58,6 +58,7 @@ ExpandThread = return if @ isnt status.req # aborted delete status.req ExpandThread.parse @, thread, a + status.numReplies = $$(g.SITE.selectors.replyOriginal, thread.nodes.root).length contract: (thread, a, threadRoot) -> status = ExpandThread.statuses[thread] @@ -69,15 +70,7 @@ ExpandThread = return replies = $$ '.thread > .replyContainer', threadRoot - if !Conf['JSON Index'] or Conf['Show Replies'] - num = if thread.isSticky - 1 - else switch g.BOARD.ID - # XXX boards config - when 'b', 'vg', 'bant' then 3 - when 't' then 1 - else 5 - replies = replies[...-num] + replies = replies[...(-status.numReplies)] if status.numReplies postsCount = 0 filesCount = 0 for reply in replies diff --git a/src/Miscellaneous/Keybinds.coffee b/src/Miscellaneous/Keybinds.coffee index a10896f59..f89cc9d94 100644 --- a/src/Miscellaneous/Keybinds.coffee +++ b/src/Miscellaneous/Keybinds.coffee @@ -21,14 +21,9 @@ Keybinds = {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 Index'] and Conf['Index Mode'] is 'catalog' or - g.VIEW is 'index' and g.BOARD.ID is 'f' - ) + if g.VIEW in ['index', 'thread'] threadRoot = Nav.getThread() - if op = $ '.op', threadRoot - thread = Get.postFromNode(op).thread + thread = Get.threadFromRoot threadRoot switch key # QR & Options when Conf['Toggle board list'] @@ -93,10 +88,10 @@ Keybinds = when Conf['Update'] switch g.VIEW when 'thread' - return unless Conf['Thread Updater'] + return unless ThreadUpdater.enabled ThreadUpdater.update() when 'index' - return unless Conf['JSON Index'] and g.BOARD.ID isnt 'f' + return unless Index.enabled Index.update() else return @@ -118,10 +113,11 @@ Keybinds = # Images when Conf['Expand image'] return unless ImageExpand.enabled and threadRoot - Keybinds.img threadRoot + post = Get.postFromNode Keybinds.post threadRoot + ImageExpand.toggle post if post.file when Conf['Expand images'] - return unless ImageExpand.enabled and threadRoot - Keybinds.img threadRoot, true + return unless ImageExpand.enabled + ImageExpand.cb.toggleAll() when Conf['Open Gallery'] return unless Gallery.enabled Gallery.cb.toggle() @@ -133,47 +129,51 @@ Keybinds = FappeTyme.toggle 'werk' # Board Navigation when Conf['Front page'] - if Conf['JSON Index'] and g.VIEW is 'index' and g.BOARD.ID isnt 'f' + if Index.enabled Index.userPageNav 1 else location.href = "/#{g.BOARD}/" when Conf['Open front page'] $.open "#{location.origin}/#{g.BOARD}/" when Conf['Next page'] - return unless g.VIEW is 'index' and g.BOARD.ID isnt 'f' - if Conf['JSON Index'] + return unless g.VIEW is 'index' and !g.SITE.isOnePage?(g.BOARD) + if Index.enabled return unless Conf['Index Mode'] in ['paged', 'infinite'] $('.next button', Index.pagelist).click() else - if form = $ '.next form' - location.href = form.action + $(g.SITE.selectors.nav.next)?.click() when Conf['Previous page'] - return unless g.VIEW is 'index' and g.BOARD.ID isnt 'f' - if Conf['JSON Index'] + return unless g.VIEW is 'index' and !g.SITE.isOnePage?(g.BOARD) + if Index.enabled return unless Conf['Index Mode'] in ['paged', 'infinite'] $('.prev button', Index.pagelist).click() else - if form = $ '.prev form' - location.href = form.action + $(g.SITE.selectors.nav.prev)?.click() when Conf['Search form'] - return unless g.VIEW is 'index' and g.BOARD.ID isnt 'f' - searchInput = if Conf['JSON Index'] then Index.searchInput else $.id('search-box') + return unless g.VIEW is 'index' + searchInput = if Index.enabled + Index.searchInput + else if g.SITE.selectors.searchBox + $ g.SITE.selectors.searchBox + else + undefined + return unless searchInput Header.scrollToIfNeeded searchInput searchInput.focus() when Conf['Paged mode'] - return unless Conf['JSON Index'] and g.BOARD.ID isnt 'f' + return unless Index.enabledOn(g.BOARD) location.href = if g.VIEW is 'index' then '#paged' else "/#{g.BOARD}/#paged" when Conf['Infinite scrolling mode'] - return unless Conf['JSON Index'] and g.BOARD.ID isnt 'f' + return unless Index.enabledOn(g.BOARD) location.href = if g.VIEW is 'index' then '#infinite' else "/#{g.BOARD}/#infinite" when Conf['All pages mode'] - return unless Conf['JSON Index'] and g.BOARD.ID isnt 'f' + return unless Index.enabledOn(g.BOARD) location.href = if g.VIEW is 'index' then '#all-pages' else "/#{g.BOARD}/#all-pages" when Conf['Open catalog'] - return if g.BOARD.ID is 'f' - location.href = CatalogLinks.catalog() + return unless (catalog = CatalogLinks.catalog()) + location.href = catalog when Conf['Cycle sort type'] - return unless Conf['JSON Index'] and g.VIEW is 'index' and g.BOARD.ID isnt 'f' + return unless Index.enabled Index.cycleSortType() # Thread Navigation when Conf['Next thread'] @@ -269,7 +269,11 @@ Keybinds = key post: (thread) -> - $('.post.highlight', thread) or $('.op', thread) + s = g.SITE.selectors + ( + $("#{s.postContainer}#{s.highlightable.reply}.#{g.SITE.classes.highlight}", thread) or + $("#{if g.SITE.isOPContainerThread then s.thread else s.postContainer}#{s.highlightable.op}", thread) + ) qr: (thread) -> QR.open() @@ -309,49 +313,44 @@ Keybinds = "" else "sage" - img: (thread, all) -> - if all - ImageExpand.cb.toggleAll() - else - post = Get.postFromNode Keybinds.post thread - ImageExpand.toggle post if post.file - open: (thread, tab) -> return if g.VIEW isnt 'index' - url = "/#{thread.board}/thread/#{thread}" + url = Get.url 'thread', thread if tab - $.open location.origin + url + $.open url else location.href = url hl: (delta, thread) -> - postEl = $ '.reply.highlight', thread + replySelector = "#{g.SITE.selectors.postContainer}#{g.SITE.selectors.highlightable.reply}" + {highlight} = g.SITE.classes + + postEl = $ "#{replySelector}.#{highlight}", thread unless delta - $.rmClass postEl, 'highlight' if postEl + $.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 + {root} = Get.postFromNode(postEl).nodes axis = if delta is +1 'following' else 'preceding' - return if not (next = $.x "#{axis}-sibling::div[contains(@class,'replyContainer') and not(@hidden) and not(child::div[@class='stub'])][1]/child::div[contains(@class,'reply')]", root) + return unless (next = $.x "#{axis}-sibling::#{g.SITE.xpath.replyContainer}[not(@hidden) and not(child::div[@class='stub'])][1]", root) + next = $ replySelector, next unless next.matches(replySelector) Header.scrollToIfNeeded next, delta is +1 - @focus next - $.rmClass postEl, 'highlight' + $.addClass next, highlight + $.rmClass postEl, highlight return - $.rmClass postEl, 'highlight' + $.rmClass postEl, highlight - replies = $$ '.reply', thread + replies = $$ replySelector, 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 + $.addClass reply, highlight return - - focus: (post) -> - $.addClass post, 'highlight' + return diff --git a/src/Miscellaneous/Nav.coffee b/src/Miscellaneous/Nav.coffee index 6b9de63c5..8cfd93294 100644 --- a/src/Miscellaneous/Nav.coffee +++ b/src/Miscellaneous/Nav.coffee @@ -39,22 +39,24 @@ Nav = Nav.scroll +1 getThread: -> - return $ '.board' if $.hasClass doc, 'catalog-mode' - for threadRoot in $$ '.thread' + return g.threads["#{g.BOARD}.#{g.THREADID}"].nodes.root if g.VIEW is 'thread' + return if $.hasClass doc, 'catalog-mode' + for threadRoot in $$ g.SITE.selectors.thread thread = Get.threadFromRoot threadRoot continue if thread.isHidden and !thread.stub if Header.getTopOf(threadRoot) >= -threadRoot.getBoundingClientRect().height # not scrolled past return threadRoot - return $ '.board' + return scroll: (delta) -> d.activeElement?.blur() thread = Nav.getThread() + return unless thread axis = if delta is +1 'following' else 'preceding' - if next = $.x "#{axis}-sibling::div[contains(@class,'thread') and not(@hidden)][1]", thread + if next = $.x "#{axis}-sibling::#{g.SITE.xpath.thread}[not(@hidden)][1]", thread # Unless we're not at the beginning of the current thread, # and thus wanting to move to beginning, # or we're above the first thread and don't want to skip it. diff --git a/src/Monitoring/ThreadUpdater.coffee b/src/Monitoring/ThreadUpdater.coffee index d4cbf5abc..de4a9f460 100644 --- a/src/Monitoring/ThreadUpdater.coffee +++ b/src/Monitoring/ThreadUpdater.coffee @@ -1,6 +1,7 @@ ThreadUpdater = init: -> return if g.VIEW isnt 'thread' or !Conf['Thread Updater'] + @enabled = true # Chromium won't play audio created in an inactive tab until the tab has been focused, so set it up now. # XXX Sometimes the loading stalls in Firefox, esp. when opening in private browsing window followed by normal window. diff --git a/src/Posting/QR.coffee b/src/Posting/QR.coffee index 73d933791..bc7a655db 100644 --- a/src/Posting/QR.coffee +++ b/src/Posting/QR.coffee @@ -628,7 +628,7 @@ QR = $.rm nodes.flag delete nodes.flag - if g.BOARD.ID is 'pol' + if g.BOARD.config.troll_flags flag = QR.flags() flag.dataset.name = 'flag' flag.dataset.default = '0' diff --git a/src/Quotelinks/QuoteYou.coffee b/src/Quotelinks/QuoteYou.coffee index b3d49600b..d49c5457b 100644 --- a/src/Quotelinks/QuoteYou.coffee +++ b/src/Quotelinks/QuoteYou.coffee @@ -92,7 +92,8 @@ QuoteYou = cb: seek: (type) -> - $.rmClass highlight, 'highlight' if highlight = $ '.highlight' + {highlight} = g.SITE.classes + $.rmClass highlighted, highlight if (highlighted = $ ".#{highlight}") unless QuoteYou.lastRead and doc.contains(QuoteYou.lastRead) and $.hasClass(QuoteYou.lastRead, 'quotesYou') if not (post = QuoteYou.lastRead = $ '.quotesYou') @@ -111,12 +112,16 @@ QuoteYou = QuoteYou.cb.scroll posts[if type is 'following' then 0 else posts.length - 1] scroll: (root) -> - post = $ '.post', root - if !post.getBoundingClientRect().height + post = Get.postFromRoot root + if !post.nodes.post.getBoundingClientRect().height return false else QuoteYou.lastRead = root - location.href = "##{post.id}" - Header.scrollTo post - $.addClass post, 'highlight' + location.href = Get.url('post', post) + Header.scrollTo post.nodes.post + if post.isReply + sel = "#{g.SITE.selectors.postContainer}#{g.SITE.selectors.highlightable.reply}" + node = post.nodes.root + node = $ sel, node unless node.matches(sel) + $.addClass node, g.SITE.classes.highlight return true diff --git a/src/classes/Callbacks.coffee b/src/classes/Callbacks.coffee index 31aee94f5..89aa2f552 100644 --- a/src/classes/Callbacks.coffee +++ b/src/classes/Callbacks.coffee @@ -11,7 +11,7 @@ class Callbacks @keys.push name unless @[name] @[name] = cb - execute: (node, keys=@keys, force) -> + execute: (node, keys=@keys, force=false) -> return if node.callbacksExecuted and !force node.callbacksExecuted = true for name in keys diff --git a/src/classes/Post.Clone.coffee b/src/classes/Post.Clone.coffee index 8f87315ea..efa3089b7 100644 --- a/src/classes/Post.Clone.coffee +++ b/src/classes/Post.Clone.coffee @@ -1,7 +1,12 @@ Post.Clone = class extends Post isClone: true - constructor: (@origin, @context, contractThumb) -> + constructor: -> + that = Object.create(Post.Clone.prototype) + that.construct arguments... + return that + + construct: (@origin, @context, contractThumb) -> for key in ['ID', 'postID', 'threadID', 'boardID', 'siteID', 'fullID', 'board', 'thread', 'info', 'quotes', 'isReply'] # Copy or point to the origin's key value. @[key] = @origin[key] diff --git a/src/config/Config.coffee b/src/config/Config.coffee index 57be795fe..cc0331269 100644 --- a/src/config/Config.coffee +++ b/src/config/Config.coffee @@ -825,6 +825,10 @@ Config = lastarchivecheck: 0 archiveAutoUpdate: true + externalCatalogURLs: """ + //catalog.neet.tv/%board/;boards:4chan.org:3,a,adv,an,asp,biz,c,cgl,ck,cm,co,diy,f,fa,fit,g,gd,his,i,int,jp,k,lgbt,lit,m,mlp,mu,n,news,o,out,p,po,pol,s4s,sci,sp,tg,toy,trv,tv,v,vg,vip,vp,vr,w,wg,wsg,wsr,x + """ + boardnav: """ [ toggle-all ] a-replace @@ -1177,3 +1181,5 @@ Config = fourchanImageHost: 'i.4cdn.org' hiddenPSAList: [{}] + + knownBanners: '<%= readJSON('banners.json').join(',') %>' diff --git a/src/Miscellaneous/Banner/banners.json b/src/config/banners.json similarity index 100% rename from src/Miscellaneous/Banner/banners.json rename to src/config/banners.json diff --git a/src/css/photon.css b/src/css/photon.css index bda9dfafb..4cb64bc66 100644 --- a/src/css/photon.css +++ b/src/css/photon.css @@ -9,9 +9,15 @@ } /* 4chan style fixes */ -:root.photon.sw-yotsuba #arc-list tr:nth-of-type(odd) span.quote { +:root.photon #arc-list tr:nth-of-type(odd) span.quote { color: #C0E17A; } +:root.photon.highlight-you .quotesYou$site$highlightable$reply { + border-left: 3px solid rgba(221, 0, 0, .8) !important; +} +:root.photon.highlight-own .yourPost$site$highlightable$reply { + border-left: 3px dashed rgba(221, 0, 0, .8) !important; +} /* Header */ :root.photon #header-bar.dialog { diff --git a/src/css/spooky.css b/src/css/spooky.css index 89ad1b3f7..7211d6a52 100644 --- a/src/css/spooky.css +++ b/src/css/spooky.css @@ -9,9 +9,15 @@ } /* 4chan style fixes */ -:root.spooky.sw-yotsuba #arc-list span.quote { +:root.spooky #arc-list span.quote { color: #634C2C; } +:root.spooky.highlight-you .quotesYou$site$highlightable$reply { + border-left: 3px solid rgba(145, 182, 214, .8) !important; +} +:root.spooky.highlight-own .yourPost$site$highlightable$reply { + border-left: 3px dashed rgba(145, 182, 214, .8) !important; +} /* Header */ :root.spooky #header-bar.dialog { @@ -67,16 +73,16 @@ :root.spooky .qphl { outline: 2px solid rgba(145, 182, 214, .8); } -:root.spooky.highlight-you .quotesYou$site$relative$opHighlight, -:root.spooky.highlight-you .quotesYou$site$relative$replyPost { +:root.spooky.highlight-you .quotesYou$site$highlightable$op, +:root.spooky.highlight-you .quotesYou$site$highlightable$reply { border-left: 3px solid rgba(145, 182, 214, .8); } -:root.spooky.highlight-own .yourPost$site$relative$opHighlight, -:root.spooky.highlight-own .yourPost$site$relative$replyPost { +:root.spooky.highlight-own .yourPost$site$highlightable$op, +:root.spooky.highlight-own .yourPost$site$highlightable$reply { border-left: 3px dashed rgba(145, 182, 214, .8); } -:root.spooky .filter-highlight$site$relative$opHighlight, -:root.spooky .filter-highlight$site$relative$replyPost { +:root.spooky .filter-highlight$site$highlightable$op, +:root.spooky .filter-highlight$site$highlightable$reply { box-shadow: inset 5px 0 rgba(145, 182, 214, .5); } :root.spooky.highlight-own .yourPost > $site$sideArrows, diff --git a/src/css/style.css b/src/css/style.css index 9afb1d42c..e999a37b3 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -658,7 +658,9 @@ div[data-checked="false"] > .suboption-list { .section-advanced textarea { height: 150px; } -.section-advanced textarea[name="archiveLists"] { +.section-advanced textarea[name="archiveLists"], +.section-advanced textarea[name="externalCatalogURLs"], +.section-advanced textarea[name="knownBanners"] { height: 75px; } .section-advanced .archive-cell { @@ -1383,8 +1385,8 @@ input[name="Default Volume"] { margin: 0px; } /* Fappe and Werk Tyme */ -:root.fappeTyme $site$relative$replyOriginal.noFile, -:root.fappeTyme $site$relative$replyOriginal.noFile + br { +:root.fappeTyme $site$replyOriginal.noFile, +:root.fappeTyme $site$replyOriginal.noFile + br { display: none; } :root.werkTyme $site$thumbLink, @@ -1438,16 +1440,16 @@ input[name="Default Volume"] { .qphl { outline: 2px solid rgba(216, 94, 49, .8); } -:root.highlight-you .quotesYou$site$relative$opHighlight, -:root.highlight-you .quotesYou$site$relative$replyPost { +:root.highlight-you .quotesYou$site$highlightable$op, +:root.highlight-you .quotesYou$site$highlightable$reply { border-left: 3px solid rgba(221, 0, 0, .8); } -:root.highlight-own .yourPost$site$relative$opHighlight, -:root.highlight-own .yourPost$site$relative$replyPost { +:root.highlight-own .yourPost$site$highlightable$op, +:root.highlight-own .yourPost$site$highlightable$reply { border-left: 3px dashed rgba(221, 0, 0, .8); } -.filter-highlight$site$relative$opHighlight, -.filter-highlight$site$relative$replyPost { +.filter-highlight$site$highlightable$op, +.filter-highlight$site$highlightable$reply { box-shadow: inset 5px 0 rgba(221, 0, 0, .5); } :root.highlight-own .yourPost > $site$sideArrows, @@ -1455,9 +1457,9 @@ input[name="Default Volume"] { .filter-highlight > $site$sideArrows { color: rgba(221, 0, 0, .8); } -:root.highlight-own .yourPost$site$relative$opHighlight::after, -:root.highlight-you .quotesYou$site$relative$opHighlight::after, -.filter-highlight$site$relative$opHighlight::after { +:root.highlight-own .yourPost$site$highlightable$op::after, +:root.highlight-you .quotesYou$site$highlightable$op::after, +.filter-highlight$site$highlightable$op::after { content: ""; display: block; clear: both; @@ -1466,7 +1468,7 @@ input[name="Default Volume"] { :root.werkTyme .catalog-thread.filter-highlight:not(:hover), :root.werkTyme:not(.catalog-hover-expand) .catalog-thread.filter-highlight, :root.werkTyme.catalog-hover-expand .catalog-thread.filter-highlight > .catalog-container:hover > .catalog-post, -:root.catalog $site$catalog$thread.filter-highlight$site$relative$catalogHighlight { +:root.catalog $site$catalog$thread.filter-highlight$site$highlightable$catalog { box-shadow: 0 0 3px 3px rgba(255, 0, 0, .5); } :root:not(.werkTyme) .catalog-thread.watched .catalog-thumb, diff --git a/src/css/tomorrow.css b/src/css/tomorrow.css index d1b2ad5af..e3ce82ddd 100644 --- a/src/css/tomorrow.css +++ b/src/css/tomorrow.css @@ -5,9 +5,15 @@ } /* 4chan style fixes */ -:root.tomorrow.sw-yotsuba #arc-list span.quote { +:root.tomorrow #arc-list span.quote { color: #B5BD68; } +:root.tomorrow.highlight-you .quotesYou$site$highlightable$reply { + border-left: 3px solid rgba(145, 182, 214, .8) !important; +} +:root.tomorrow.highlight-own .yourPost$site$highlightable$reply { + border-left: 3px dashed rgba(145, 182, 214, .8) !important; +} /* Header */ :root.tomorrow #header-bar.dialog { @@ -63,16 +69,16 @@ :root.tomorrow .qphl { outline: 2px solid rgba(145, 182, 214, .8); } -:root.tomorrow.highlight-you .quotesYou$site$relative$opHighlight, -:root.tomorrow.highlight-you .quotesYou$site$relative$replyPost { +:root.tomorrow.highlight-you .quotesYou$site$highlightable$op, +:root.tomorrow.highlight-you .quotesYou$site$highlightable$reply { border-left: 3px solid rgba(145, 182, 214, .8); } -:root.tomorrow.highlight-own .yourPost$site$relative$opHighlight, -:root.tomorrow.highlight-own .yourPost$site$relative$replyPost { +:root.tomorrow.highlight-own .yourPost$site$highlightable$op, +:root.tomorrow.highlight-own .yourPost$site$highlightable$reply { border-left: 3px dashed rgba(145, 182, 214, .8); } -:root.tomorrow .filter-highlight$site$relative$opHighlight, -:root.tomorrow .filter-highlight$site$relative$replyPost { +:root.tomorrow .filter-highlight$site$highlightable$op, +:root.tomorrow .filter-highlight$site$highlightable$reply { box-shadow: inset 5px 0 rgba(145, 182, 214, .5); } :root.tomorrow.highlight-own .yourPost > $site$sideArrows, diff --git a/src/css/yotsuba-b.css b/src/css/yotsuba-b.css index abfafa5f7..b04f9246c 100644 --- a/src/css/yotsuba-b.css +++ b/src/css/yotsuba-b.css @@ -8,6 +8,14 @@ border-color: #98E; } +/* 4chan style fixes */ +:root.yotsuba-b.highlight-you .quotesYou$site$highlightable$reply { + border-left: 3px solid rgba(221, 0, 0, .8) !important; +} +:root.yotsuba-b.highlight-own .yourPost$site$highlightable$reply { + border-left: 3px dashed rgba(221, 0, 0, .8) !important; +} + /* Header */ :root.yotsuba-b #header-bar.dialog { background-color: rgba(214,218,240,0.98); diff --git a/src/css/yotsuba.css b/src/css/yotsuba.css index 7ed23a580..ea8b8d799 100644 --- a/src/css/yotsuba.css +++ b/src/css/yotsuba.css @@ -8,6 +8,14 @@ border-color: #EA8; } +/* 4chan style fixes */ +:root.yotsuba.highlight-you .quotesYou$site$highlightable$reply { + border-left: 3px solid rgba(221, 0, 0, .8) !important; +} +:root.yotsuba.highlight-own .yourPost$site$highlightable$reply { + border-left: 3px dashed rgba(221, 0, 0, .8) !important; +} + /* Header */ :root.yotsuba #header-bar.dialog { background-color: rgba(240,224,214,0.98); diff --git a/src/main/Main.coffee b/src/main/Main.coffee index 812513430..7cdb7129e 100644 --- a/src/main/Main.coffee +++ b/src/main/Main.coffee @@ -123,11 +123,30 @@ Main = <%= html(meta.name + ' has been updated to version ${g.VERSION}.') %> new Notice 'info', el, 15 - initFeatures: -> - {hostname, search} = location - pathname = location.pathname.split /\/+/ - g.BOARD = new Board pathname[1] unless hostname in ['www.4chan.org', 'www.4channel.org'] + parseURL: (site=g.SITE, url=location) -> + r = {} + return r if !site + r.siteID = site.ID + + return r if site.isBoardlessPage?(url) + pathname = url.pathname.split /\/+/ + r.boardID = pathname[1] + + if site.isFileURL(url) + r.VIEW = 'file' + else if site.isAuxiliaryPage?(url) + # pass + else if pathname[2] in ['thread', 'res'] + r.VIEW = 'thread' + r.threadID = r.THREADID = +pathname[3].replace(/\.\w+$/, '') + else if /^(?:catalog|archive)(?:\.\w+)?$/.test(pathname[2]) + r.VIEW = pathname[2].replace(/\.\w+$/, '') + else if /^(?:index|\d*)(?:\.\w+)?$/.test(pathname[2]) + r.VIEW = 'index' + r + + initFeatures: -> $.global -> document.documentElement.classList.add 'js-enabled' window.FCX = {} @@ -136,29 +155,17 @@ Main = # XXX https://bugs.chromium.org/p/chromium/issues/detail?id=920638 $.ajaxPageInit?() - switch hostname - when 'www.4chan.org', 'www.4channel.org' - $.onExists doc, 'body', -> $.addStyle CSS.www - Captcha.replace.init() - return - when 'sys.4chan.org', 'sys.4channel.org' - if pathname[2] is 'imgboard.php' - if /\bmode=report\b/.test search - Report.init() - else if (match = search.match /\bres=(\d+)/) - $.ready -> - if Conf['404 Redirect'] and $.id('errmsg')?.textContent is 'Error: Specified thread does not exist.' - Redirect.navigate 'thread', { - boardID: g.BOARD.ID - postID: +match[1] - } - else if pathname[2] is 'post' - PostSuccessful.init() - return + $.extend g, Main.parseURL() + g.BOARD = new Board g.boardID if g.boardID - if g.SITE.isFileURL() + if !g.VIEW + g.SITE.initAuxiliary?() + return + + if g.VIEW is 'file' $.asap (-> d.readyState isnt 'loading'), -> if g.SITE.software is 'yotsuba' and Conf['404 Redirect'] and g.SITE.is404?() + pathname = location.pathname.split /\/+/ Redirect.navigate 'file', { boardID: g.BOARD.ID filename: pathname[pathname.length - 1] @@ -173,18 +180,6 @@ Main = ImageCommon.addControls video return - return if g.SITE.isAuxiliaryPage?() - - if pathname[2] in ['thread', 'res'] - g.VIEW = 'thread' - g.THREADID = +pathname[3].replace(/\.\w+$/, '') - else if /^(?:catalog|archive)(?:\.\w+)?$/.test(pathname[2]) - g.VIEW = pathname[2].replace(/\.\w+$/, '') - else if /^(?:index|\d*)(?:\.\w+)?$/.test(pathname[2]) - g.VIEW = 'index' - else - return - g.threads = new SimpleDict() g.posts = new SimpleDict() diff --git a/src/site/SW.tinyboard.coffee b/src/site/SW.tinyboard.coffee index cc78ec912..b30327cc9 100644 --- a/src/site/SW.tinyboard.coffee +++ b/src/site/SW.tinyboard.coffee @@ -4,12 +4,10 @@ SW.tinyboard = threadModTimeIgnoresSage: true disabledFeatures: [ - 'Index Generator' 'Resurrect Quotes' 'Quick Reply Personas' 'Quick Reply' 'Cooldown' - 'Index Generator (Menu)' 'Report Link' 'Delete Link' 'Edit Link' @@ -47,6 +45,9 @@ SW.tinyboard = urls: thread: ({siteID, boardID, threadID}) -> "#{Conf['siteProperties'][siteID]?.root or "http://#{siteID}/"}#{boardID}/res/#{threadID}.html" + post: ({postID}) -> "##{postID}" + index: ({siteID, boardID}) -> "#{Conf['siteProperties'][siteID]?.root or "http://#{siteID}/"}#{boardID}/" + catalog: ({siteID, boardID}) -> "#{Conf['siteProperties'][siteID]?.root or "http://#{siteID}/"}#{boardID}/catalog.html" threadJSON: ({siteID, boardID, threadID}) -> root = Conf['siteProperties'][siteID]?.root if root then "#{root}#{boardID}/res/#{threadID}.json" else '' @@ -68,6 +69,7 @@ SW.tinyboard = summary: '.omitted' postContainer: 'div[id^="reply_"]:not(.hidden)' # postContainer is thread for OP opBottom: '.op' + replyOriginal: 'div[id^="reply_"]:not(.hidden)' infoRoot: '.intro' info: subject: '.subject' @@ -90,11 +92,10 @@ SW.tinyboard = thumb: 'a > .post-image' thumbLink: '.file > a' multifile: '.files > .file' - relative: - opHighlight: ' > .op' - replyPost: '.reply' - replyOriginal: 'div[id^="reply_"]:not(.hidden)' - catalogHighlight: ' > .thread' + highlightable: + op: ' > .op' + reply: '.reply' + catalog: ' > .thread' comment: '.body' spoiler: '.spoiler' quotelink: 'a[onclick^="highlightReply("]' @@ -106,10 +107,17 @@ SW.tinyboard = boardListBottom: '.boardlist.bottom' styleSheet: '#stylesheet' psa: '.blotter' + nav: + prev: '.pages > form > [value=Previous]' + next: '.pages > form > [value=Next]' + + classes: + highlight: 'highlighted' xpath: - thread: 'div[starts-with(@id,"thread_")]' - postContainer: 'div[starts-with(@id,"reply_") or starts-with(@id,"thread_")]' + thread: 'div[starts-with(@id,"thread_")]' + postContainer: 'div[starts-with(@id,"reply_") or starts-with(@id,"thread_")]' + replyContainer: 'div[starts-with(@id,"reply_")]' regexp: quotelink: @@ -154,8 +162,8 @@ SW.tinyboard = bgColoredEl: -> $.el 'div', className: 'post reply' - isFileURL: -> - /\/src\/[^\/]+/.test(location.pathname) + isFileURL: (url) -> + /\/src\/[^\/]+/.test(url.pathname) parseNodes: (post, nodes) -> # Add vichan's span.poster_id around the ID if not already present. diff --git a/src/site/SW.yotsuba.coffee b/src/site/SW.yotsuba.coffee index 38a74994c..44521b85a 100644 --- a/src/site/SW.yotsuba.coffee +++ b/src/site/SW.yotsuba.coffee @@ -4,6 +4,9 @@ SW.yotsuba = urls: thread: ({boardID, threadID}) -> "#{location.protocol}//#{BoardConfig.domain(boardID)}/#{boardID}/thread/#{threadID}" + post: ({postID}) -> "#p#{postID}" + index: ({boardID}) -> "#{location.protocol}//#{BoardConfig.domain(boardID)}/#{boardID}/" + catalog: ({boardID}) -> if boardID is 'f' then undefined else "#{location.protocol}//#{BoardConfig.domain(boardID)}/#{boardID}/catalog" threadJSON: ({boardID, threadID}) -> "#{location.protocol}//a.4cdn.org/#{boardID}/thread/#{threadID}.json" threadsListJSON: ({boardID}) -> "#{location.protocol}//a.4cdn.org/#{boardID}/threads.json" archiveListJSON: ({boardID}) -> if BoardConfig.isArchived(boardID) then "#{location.protocol}//a.4cdn.org/#{boardID}/archive.json" else '' @@ -14,8 +17,9 @@ SW.yotsuba = thumb: ({boardID}, filename) -> "#{location.protocol}//#{ImageHost.thumbHost()}/#{boardID}/#{filename}" - isPrunedByAge: ({boardID}) -> boardID is 'f' + isPrunedByAge: ({boardID}) -> boardID is 'f' areMD5sDeferred: ({boardID}) -> boardID is 'f' + isOnePage: ({boardID}) -> boardID is 'f' noAudio: ({boardID}) -> BoardConfig.noAudio(boardID) selectors: @@ -24,6 +28,7 @@ SW.yotsuba = threadDivider: '.board > hr' summary: '.summary' postContainer: '.postContainer' + replyOriginal: '.replyContainer:not([data-clone])' sideArrows: 'div.sideArrows' post: '.post' infoRoot: '.postInfo' @@ -50,11 +55,10 @@ SW.yotsuba = link: '.fileText > a' thumb: 'a.fileThumb > [data-md5]' thumbLink: 'a.fileThumb' - relative: - opHighlight: '.opContainer' - replyPost: ' > .reply' - replyOriginal: '.replyContainer:not([data-clone])' - catalogHighlight: '' + highlightable: + op: '.opContainer' + reply: ' > .reply' + catalog: '' comment: '.postMessage' spoiler: 's' quotelink: ':not(pre) > .quotelink' # XXX https://github.com/4chan/4chan-JS/issues/77: 4chan currently creates quote links inside [code] tags; ignore them @@ -67,10 +71,18 @@ SW.yotsuba = styleSheet: 'link[title=switch]' psa: '#globalMessage' psaTop: '#globalToggle' + searchBox: '#search-box' + nav: + prev: '.prev > form > [type=submit]' + next: '.next > form > [type=submit]' + + classes: + highlight: 'highlight' xpath: - thread: 'div[contains(concat(" ",@class," ")," thread ")]' - postContainer: 'div[contains(@class,"postContainer")]' + thread: 'div[contains(concat(" ",@class," ")," thread ")]' + postContainer: 'div[contains(@class,"postContainer")]' + replyContainer: 'div[contains(@class,"replyContainer")]' regexp: quotelink: @@ -105,11 +117,36 @@ SW.yotsuba = isIncomplete: -> return g.VIEW in ['index', 'thread'] and not $('.board + *') - isAuxiliaryPage: -> - location.hostname not in ['boards.4chan.org', 'boards.4channel.org'] + isBoardlessPage: (url) -> + url.hostname in ['www.4chan.org', 'www.4channel.org'] - isFileURL: -> - ImageHost.test(location.hostname) + isAuxiliaryPage: (url) -> + url.hostname not in ['boards.4chan.org', 'boards.4channel.org'] + + isFileURL: (url) -> + ImageHost.test(url.hostname) + + initAuxiliary: -> + switch location.hostname + when 'www.4chan.org', 'www.4channel.org' + $.onExists doc, 'body', -> $.addStyle CSS.www + Captcha.replace.init() + return + when 'sys.4chan.org', 'sys.4channel.org' + pathname = location.pathname.split /\/+/ + if pathname[2] is 'imgboard.php' + if /\bmode=report\b/.test location.search + Report.init() + else if (match = location.search.match /\bres=(\d+)/) + $.ready -> + if Conf['404 Redirect'] and $.id('errmsg')?.textContent is 'Error: Specified thread does not exist.' + Redirect.navigate 'thread', { + boardID: g.BOARD.ID + postID: +match[1] + } + else if pathname[2] is 'post' + PostSuccessful.init() + return scriptData: -> for script in $$ 'script:not([src])', d.head diff --git a/src/site/Site.coffee b/src/site/Site.coffee index 33ceaff0e..317f8bed9 100644 --- a/src/site/Site.coffee +++ b/src/site/Site.coffee @@ -6,14 +6,10 @@ Site = init: (cb) -> $.extend Conf['siteProperties'], Site.defaultProperties - {hostname} = location - while hostname and hostname not of Conf['siteProperties'] - hostname = hostname.replace(/^[^.]*\.?/, '') - if hostname - hostname = canonical if (canonical = Conf['siteProperties'][hostname].canonical) - if Conf['siteProperties'][hostname].software of SW - @set hostname - cb() + hostname = Site.resolve() + if hostname and Conf['siteProperties'][hostname].software of SW + @set hostname + cb() $.onExists doc, 'body', => for software of SW when (changes = SW[software].detect?()) changes.software = software @@ -31,6 +27,18 @@ Site = return return + resolve: (url=location) -> + {hostname} = url + while hostname and hostname not of Conf['siteProperties'] + hostname = hostname.replace(/^[^.]*\.?/, '') + if hostname + hostname = canonical if (canonical = Conf['siteProperties'][hostname].canonical) + hostname + + parseURL: (url) -> + siteID = Site.resolve url + Main.parseURL g.sites[siteID], url + set: (hostname) -> for ID, properties of Conf['siteProperties'] continue if properties.canonical diff --git a/tools/banners.py b/tools/banners.py index 192a88479..cfd3bef44 100755 --- a/tools/banners.py +++ b/tools/banners.py @@ -15,5 +15,5 @@ for ext in ['jpg', 'png', 'gif']: print(banner, status) if status == 200: banners.append(banner) -with open('src/Miscellaneous/Banner/banners.json', 'w') as f: +with open('src/config/banners.json', 'w') as f: f.write(json.dumps(banners))