Merge branch 'next'
This commit is contained in:
commit
61954985bf
@ -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('<span>MD5 filtered.</span> [<a href="javascript:;">show</a>] [<a href="javascript:;">undo</a>]') %>
|
||||
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
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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}/ - ") + (
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -19,6 +19,16 @@
|
||||
<button id="update-archives">Update now</button> Last updated: <time id="lastarchivecheck"></time> <label><input type="checkbox" name="archiveAutoUpdate"> Auto-update</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>External Catalog</legend>
|
||||
<div class="warning" data-feature="External Catalog"><code>External Catalog</code> is disabled. This will be used only as a fallback.</div>
|
||||
<div>
|
||||
URLs of external catalog sites, where <code>%board</code> is to be replaced by the board name.<br>
|
||||
Each URL should be followed by <code>;boards:</code> and optionally <code>;exclude:</code> and a list of supported/excluded boards in the format explained in the Filter guide.
|
||||
</div>
|
||||
<textarea hidden name="externalCatalogURLs" class="field" spellcheck="false"></textarea>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Override 4chan Image Host</legend>
|
||||
<div>Change 4chan image links to this domain. Leave blank for no change.</div>
|
||||
@ -173,3 +183,9 @@
|
||||
</div>
|
||||
<textarea hidden name="jsWhitelist" class="field" spellcheck="false"></textarea>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Known Banners</legend>
|
||||
<div>List of known banners, used for click-to-change feature.</div>
|
||||
<textarea hidden name="knownBanners" class="field" spellcheck="false"></textarea>
|
||||
</fieldset>
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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(',') %>'
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -123,11 +123,30 @@ Main =
|
||||
<%= html(meta.name + ' has been updated to <a href="' + meta.changelog + '" target="_blank">version ${g.VERSION}</a>.') %>
|
||||
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()
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user