Merge branch 'multisite'
This commit is contained in:
commit
ebb9cc11e6
2
Makefile
2
Makefile
@ -32,7 +32,7 @@ $(eval $(shell node tools/pkgvars.js))
|
||||
version = $(shell node -p "JSON.parse(require('fs').readFileSync('version.json')).version")
|
||||
|
||||
source_directories := \
|
||||
globals config css platform classes \
|
||||
globals config css platform classes site \
|
||||
Archive Filtering General Images Linkification \
|
||||
Menu Miscellaneous Monitoring Posting Quotelinks \
|
||||
main
|
||||
|
||||
@ -24,9 +24,14 @@ PostHiding =
|
||||
Recursive.add PostHiding.hide, @, data.makeStub, true
|
||||
|
||||
return unless Conf['Reply Hiding Buttons']
|
||||
sideArrows = $('.sideArrows', @nodes.root)
|
||||
$.replace sideArrows.firstChild, PostHiding.makeButton @, 'hide'
|
||||
sideArrows.removeAttribute 'class'
|
||||
|
||||
button = PostHiding.makeButton @, 'hide'
|
||||
if (sa = Site.selectors.sideArrows)
|
||||
sideArrows = $ sa, @nodes.root
|
||||
$.replace sideArrows.firstChild, button
|
||||
sideArrows.removeAttribute 'class'
|
||||
else
|
||||
$.prepend @nodes.root, button
|
||||
|
||||
menu:
|
||||
init: ->
|
||||
|
||||
@ -43,5 +43,6 @@ BoardConfig =
|
||||
board for board, data of (@boards or Conf['boardConfig'].boards) when !!data.ws_board is sfw
|
||||
|
||||
noAudio: (boardID) ->
|
||||
return false unless Site.software is 'yotsuba'
|
||||
boards = @boards or Conf['boardConfig'].boards
|
||||
boards and !boards[boardID].webm_audio
|
||||
|
||||
@ -10,25 +10,24 @@ Get =
|
||||
excerpt
|
||||
threadFromRoot: (root) ->
|
||||
return null unless root?
|
||||
g.threads["#{g.BOARD}.#{root.id[1..]}"]
|
||||
g.threads["#{g.BOARD}.#{root.id.match(/\d*$/)[0]}"]
|
||||
threadFromNode: (node) ->
|
||||
Get.threadFromRoot $.x 'ancestor-or-self::div[contains(concat(" ",@class," ")," thread ")]', node
|
||||
Get.threadFromRoot $.x "ancestor-or-self::#{Site.xpath.thread}", node
|
||||
postFromRoot: (root) ->
|
||||
return null unless root?
|
||||
post = g.posts[root.dataset.fullID]
|
||||
index = root.dataset.clone
|
||||
if index then post.clones[index] else post
|
||||
postFromNode: (root) ->
|
||||
Get.postFromRoot $.x 'ancestor-or-self::div[contains(@class,"postContainer")][1]', root
|
||||
Get.postFromRoot $.x "ancestor-or-self::#{Site.xpath.postContainer}[1]", root
|
||||
postDataFromLink: (link) ->
|
||||
if link.hostname is 'boards.4chan.org'
|
||||
path = link.pathname.split /\/+/
|
||||
boardID = path[1]
|
||||
threadID = path[3]
|
||||
postID = if link.hash then link.hash[2..] else path[3]
|
||||
else # resurrected quote
|
||||
if link.dataset.postID # resurrected quote
|
||||
{boardID, threadID, postID} = link.dataset
|
||||
threadID or= 0
|
||||
else
|
||||
match = link.href.match Site.regexp.quotelink
|
||||
[boardID, threadID, postID] = match[1..]
|
||||
postID or= threadID
|
||||
return {
|
||||
boardID: boardID
|
||||
threadID: +threadID
|
||||
@ -64,8 +63,3 @@ Get =
|
||||
quotelinks.filter (quotelink) ->
|
||||
{boardID, postID} = Get.postDataFromLink quotelink
|
||||
boardID is post.board.ID and postID is post.ID
|
||||
|
||||
scriptData: ->
|
||||
for script in $$ 'script:not([src])', d.head
|
||||
return script.textContent if /\bcooldowns *=/.test script.textContent
|
||||
''
|
||||
|
||||
@ -84,15 +84,15 @@ Header =
|
||||
$.on window, 'load popstate', Header.hashScroll
|
||||
$.on d, 'CreateNotification', @createNotification
|
||||
|
||||
@setBoardList()
|
||||
|
||||
$.onExists doc, 'body', =>
|
||||
return unless Main.isThisPageLegit()
|
||||
$.prepend d.body, @bar
|
||||
$.add d.body, Header.hover
|
||||
@setBarPosition Conf['Bottom Header']
|
||||
|
||||
# Wait for #boardNavMobile instead of #boardNavDesktop,
|
||||
# it might be incomplete otherwise.
|
||||
$.onExists doc, '#boardNavMobile', Header.setBoardList
|
||||
$.onExists doc, "#{Site.selectors.boardList} + *", Header.generateFullBoardList
|
||||
|
||||
Main.ready ->
|
||||
if not (footer = $.id 'boardNavDesktopFoot')
|
||||
@ -154,9 +154,20 @@ Header =
|
||||
btn = $('.hide-board-list-button', boardList)
|
||||
$.on btn, 'click', Header.toggleBoardList
|
||||
|
||||
$.add Header.bar, [Header.boardList, Header.shortcuts, Header.noticesRoot, Header.toggle]
|
||||
|
||||
Header.setCustomNav Conf['Custom Board Navigation']
|
||||
Header.generateBoardList Conf['boardnav']
|
||||
|
||||
$.sync 'Custom Board Navigation', Header.setCustomNav
|
||||
$.sync 'boardnav', Header.generateBoardList
|
||||
|
||||
generateFullBoardList: ->
|
||||
nodes = []
|
||||
spacer = -> $.el 'span', className: 'spacer'
|
||||
for node in $('#boardNavDesktop > .boardList').childNodes
|
||||
items = $.X './/a|.//text()[not(ancestor::a)]', $(Site.selectors.boardList)
|
||||
i = 0
|
||||
while node = items.snapshotItem i++
|
||||
switch node.nodeName
|
||||
when '#text'
|
||||
for chr in node.nodeValue
|
||||
@ -169,18 +180,10 @@ Header =
|
||||
a = node.cloneNode true
|
||||
a.className = 'current' if a.pathname.split('/')[1] is g.BOARD.ID
|
||||
nodes.push a
|
||||
fullBoardList = $ '.boardList', boardList
|
||||
fullBoardList = $ '.boardList', Header.boardList
|
||||
$.add fullBoardList, nodes
|
||||
CatalogLinks.setLinks fullBoardList
|
||||
|
||||
$.add Header.bar, [Header.boardList, Header.shortcuts, Header.noticesRoot, Header.toggle]
|
||||
|
||||
Header.setCustomNav Conf['Custom Board Navigation']
|
||||
Header.generateBoardList Conf['boardnav']
|
||||
|
||||
$.sync 'Custom Board Navigation', Header.setCustomNav
|
||||
$.sync 'boardnav', Header.generateBoardList
|
||||
|
||||
generateBoardList: (boardnav) ->
|
||||
list = $ '#custom-board-list', Header.boardList
|
||||
$.rmAll list
|
||||
@ -224,7 +227,17 @@ Header =
|
||||
return a
|
||||
|
||||
boardID = t.split('-')[0]
|
||||
boardID = g.BOARD.ID if boardID is 'current'
|
||||
if boardID is 'current'
|
||||
if location.hostname is 'boards.4chan.org'
|
||||
boardID = g.BOARD.ID
|
||||
else
|
||||
a = $.el 'a',
|
||||
href: "/#{g.BOARD.ID}/"
|
||||
textContent: text or g.BOARD.ID
|
||||
className: 'current'
|
||||
if /-(catalog|archive|expired)/.test(t)
|
||||
a = a.firstChild # Its text node.
|
||||
return a
|
||||
|
||||
a = do ->
|
||||
if boardID is '@'
|
||||
@ -237,13 +250,13 @@ Header =
|
||||
return a.cloneNode true
|
||||
|
||||
a = $.el 'a',
|
||||
href: "/#{boardID}/"
|
||||
href: "//boards.4chan.org/#{boardID}/"
|
||||
textContent: boardID
|
||||
a.href += g.VIEW if g.VIEW in ['catalog', 'archive']
|
||||
a.className = 'current' if boardID is g.BOARD.ID
|
||||
a.className = 'current' if a.hostname is location.hostname and boardID is g.BOARD.ID
|
||||
a
|
||||
|
||||
a.textContent = if /-title/.test(t) or /-replace/.test(t) and boardID is g.BOARD.ID
|
||||
a.textContent = if /-title/.test(t) or /-replace/.test(t) and a.hostname is location.hostname and boardID is g.BOARD.ID
|
||||
a.title or a.textContent
|
||||
else if /-full/.test t
|
||||
("/#{boardID}/") + (if a.title then " - #{a.title}" else '')
|
||||
@ -271,7 +284,7 @@ Header =
|
||||
|
||||
if /-expired/.test t
|
||||
if boardID not in ['b', 'f', 'trash', 'bant']
|
||||
a.href = "/#{boardID}/archive"
|
||||
a.href = "//boards.4chan.org/#{boardID}/archive"
|
||||
else
|
||||
return a.firstChild # Its text node.
|
||||
|
||||
|
||||
@ -169,7 +169,7 @@ Settings =
|
||||
# Unsupported options
|
||||
if $.engine isnt 'gecko'
|
||||
$('div[data-name="Remember QR Size"]', section).hidden = true
|
||||
if $.perProtocolSettings
|
||||
if $.perProtocolSettings or location.protocol isnt 'https:'
|
||||
$('div[data-name="Redirect to HTTPS"]', section).hidden = true
|
||||
|
||||
$.get items, (items) ->
|
||||
|
||||
@ -82,8 +82,8 @@ Gallery =
|
||||
|
||||
$.on window, 'resize', Gallery.cb.setHeight
|
||||
|
||||
for file in $$ '.post .file'
|
||||
post = Get.postFromNode file
|
||||
for postThumb in $$ Site.selectors.file.thumb
|
||||
post = Get.postFromNode postThumb
|
||||
continue unless post.file?.thumb
|
||||
Gallery.generateThumb post
|
||||
# If no image to open is given, pick image we have scrolled to.
|
||||
|
||||
@ -23,7 +23,7 @@ ImageHover =
|
||||
return unless doc.contains @
|
||||
{file} = post
|
||||
{isVideo} = file
|
||||
return if file.isExpanding or file.isExpanded
|
||||
return if file.isExpanding or file.isExpanded or Site.isThumbExpanded?(file)
|
||||
error = ImageHover.error post
|
||||
if ImageCommon.cache?.dataset.fullID is post.fullID
|
||||
el = ImageCommon.popCache()
|
||||
|
||||
@ -78,12 +78,12 @@ CatalogLinks =
|
||||
if Conf['External Catalog'] and board in ['a', 'c', 'g', 'biz', 'k', 'm', 'o', 'p', 'v', 'vg', 'vr', 'w', 'wg', 'cm', '3', 'adv', 'an', 'asp', 'cgl', 'ck', 'co', 'diy', 'fa', 'fit', 'gd', 'int', 'jp', 'lit', 'mlp', 'mu', 'n', 'out', 'po', 'sci', 'sp', 'tg', 'toy', 'trv', 'tv', 'vp', 'wsg', 'x', 'f', 'pol', 's4s', 'lgbt']
|
||||
"//catalog.neet.tv/#{board}/"
|
||||
else if Conf['JSON Index'] and Conf['Use <%= meta.name %> Catalog']
|
||||
if g.BOARD.ID is board and g.VIEW is 'index' then '#catalog' else "/#{board}/#catalog"
|
||||
if location.hostname is 'boards.4chan.org' and g.BOARD.ID is board and g.VIEW is 'index' then '#catalog' else "//boards.4chan.org/#{board}/#catalog"
|
||||
else
|
||||
"/#{board}/catalog"
|
||||
"//boards.4chan.org/#{board}/catalog"
|
||||
|
||||
index: (board=g.BOARD.ID) ->
|
||||
if Conf['JSON Index'] and board isnt 'f'
|
||||
if g.BOARD.ID is board and g.VIEW is 'index' then '#index' else "/#{board}/#index"
|
||||
if location.hostname is 'boards.4chan.org' and g.BOARD.ID is board and g.VIEW is 'index' then '#index' else "//boards.4chan.org/#{board}/#index"
|
||||
else
|
||||
"/#{board}/"
|
||||
"//boards.4chan.org/#{board}/"
|
||||
|
||||
@ -326,7 +326,7 @@ QR =
|
||||
$.replace node, $.tn '\n'
|
||||
for node in $$ 'br', frag
|
||||
$.replace node, $.tn '\n>' unless node is frag.lastChild
|
||||
Post::insertTags frag
|
||||
Site.insertTags?(frag)
|
||||
for node in $$ '.linkify[data-original]', frag
|
||||
$.replace node, $.tn node.dataset.original
|
||||
for node in $$ '.embedder', frag
|
||||
|
||||
@ -2,7 +2,7 @@ class DataBoard
|
||||
@keys = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads', 'customTitles']
|
||||
|
||||
constructor: (@key, sync, dontClean) ->
|
||||
@data = Conf[@key]
|
||||
@initData Conf[@key]
|
||||
$.sync @key, @onSync
|
||||
@clean() unless dontClean
|
||||
return unless sync
|
||||
@ -13,28 +13,34 @@ class DataBoard
|
||||
@sync = sync
|
||||
$.on d, '4chanXInitFinished', init
|
||||
|
||||
initData: (@allData) ->
|
||||
if Site.hostname is '4chan.org' and @allData.boards
|
||||
@data = @allData
|
||||
else
|
||||
@data = (@allData[Site.hostname] or= boards: {})
|
||||
|
||||
changes: []
|
||||
|
||||
save: (change, cb) ->
|
||||
snapshot1 = JSON.stringify @data
|
||||
snapshot1 = JSON.stringify @allData
|
||||
change()
|
||||
{changes} = @
|
||||
changes.push change
|
||||
$.get @key, {boards: {}}, (items) =>
|
||||
@data = items[@key]
|
||||
snapshot2 = JSON.stringify @data
|
||||
@initData items[@key]
|
||||
snapshot2 = JSON.stringify @allData
|
||||
c() for c in changes
|
||||
$.set @key, @data, =>
|
||||
$.set @key, @allData, =>
|
||||
@changes = []
|
||||
@sync?() if snapshot1 isnt snapshot2
|
||||
cb?()
|
||||
|
||||
forceSync: (cb) ->
|
||||
snapshot1 = JSON.stringify @data
|
||||
snapshot1 = JSON.stringify @allData
|
||||
{changes} = @
|
||||
$.get @key, {boards: {}}, (items) =>
|
||||
@data = items[@key]
|
||||
snapshot2 = JSON.stringify @data
|
||||
@initData items[@key]
|
||||
snapshot2 = JSON.stringify @allData
|
||||
c() for c in changes
|
||||
@sync?() if snapshot1 isnt snapshot2
|
||||
cb?()
|
||||
@ -103,6 +109,9 @@ class DataBoard
|
||||
val or defaultValue
|
||||
|
||||
clean: ->
|
||||
# XXX not yet multisite ready
|
||||
return unless Site.software is 'yotsuba'
|
||||
|
||||
for boardID, val of @data.boards
|
||||
@deleteIfEmpty {boardID}
|
||||
|
||||
@ -133,8 +142,8 @@ class DataBoard
|
||||
threads[ID] = board[ID] if ID of board
|
||||
@data.boards[boardID] = threads
|
||||
@deleteIfEmpty {boardID}
|
||||
$.set @key, @data
|
||||
$.set @key, @allData
|
||||
|
||||
onSync: (data) =>
|
||||
@data = data or boards: {}
|
||||
@initData data
|
||||
@sync?()
|
||||
|
||||
@ -11,10 +11,10 @@ Post.Clone = class extends Post
|
||||
@cloneWithoutVideo nodes.root
|
||||
else
|
||||
nodes.root.cloneNode true
|
||||
Post.Clone.prefix or= 0
|
||||
Post.Clone.suffix or= 0
|
||||
for node in [root, $$('[id]', root)...]
|
||||
node.id = Post.Clone.prefix + node.id
|
||||
Post.Clone.prefix++
|
||||
node.id += "_#{Post.Clone.suffix}"
|
||||
Post.Clone.suffix++
|
||||
|
||||
# Remove inlined posts inside of this post.
|
||||
for inline in $$ '.inline', root
|
||||
@ -44,12 +44,10 @@ Post.Clone = class extends Post
|
||||
@file = {}
|
||||
for key, val of @origin.file
|
||||
@file[key] = val
|
||||
{fileRoot} = @nodes
|
||||
@file.text = fileRoot.firstElementChild
|
||||
@file.link = $ '.fileText > a, .fileText-original', fileRoot
|
||||
@file.thumb = $ 'a.fileThumb > [data-md5]', fileRoot
|
||||
for key, selector of Site.selectors.file
|
||||
@file[key] = $ selector, @nodes.root
|
||||
@file.thumbLink = @file.thumb?.parentNode
|
||||
@file.fullImage = $ '.full-image', fileRoot
|
||||
@file.fullImage = $ '.full-image', @file.thumbLink if @file.thumbLink
|
||||
@file.videoControls = $ '.video-controls', @file.text
|
||||
|
||||
@file.thumb.muted = true if @file.videoThumb
|
||||
|
||||
@ -6,7 +6,7 @@ class Post
|
||||
@normalizedOriginal = Build.Test.normalize root
|
||||
<% } %>
|
||||
|
||||
@ID = +root.id[2..]
|
||||
@ID = +root.id.match(/\d*$/)[0]
|
||||
@threadID = @thread.ID
|
||||
@boardID = @board.ID
|
||||
@fullID = "#{@board}.#{@ID}"
|
||||
@ -16,15 +16,13 @@ class Post
|
||||
|
||||
@nodes = @parseNodes root
|
||||
|
||||
if not (@isReply = $.hasClass @nodes.post, 'reply')
|
||||
if not (@isReply = @ID isnt @threadID)
|
||||
@thread.OP = @
|
||||
if @boardID is 'f'
|
||||
for type in ['Sticky', 'Closed'] when (icon = $ "img[alt=#{type}]", @nodes.info)
|
||||
$.addClass icon, "#{type.toLowerCase()}Icon", 'retina'
|
||||
@thread.isArchived = !!$ '.archivedIcon', @nodes.info
|
||||
@thread.isSticky = !!$ '.stickyIcon', @nodes.info
|
||||
@thread.isClosed = @thread.isArchived or !!$ '.closedIcon', @nodes.info
|
||||
@thread.kill() if @thread.isArchived
|
||||
for key in ['isSticky', 'isClosed', 'isArchived']
|
||||
@thread[key] = if (selector = Site.selectors.icons[key]) then !!$(selector, @nodes.info) else false
|
||||
if @thread.isArchived
|
||||
@thread.isClosed = true
|
||||
@thread.kill()
|
||||
|
||||
@info =
|
||||
subject: @nodes.subject?.textContent or undefined
|
||||
@ -36,7 +34,7 @@ class Post
|
||||
flagCode: @nodes.flag?.className.match(/flag-(\w+)/)?[1].toUpperCase()
|
||||
flagCodeTroll: @nodes.flag?.src?.match(/(\w+)\.gif$/)?[1].toUpperCase()
|
||||
flag: @nodes.flag?.title
|
||||
date: if @nodes.date then new Date(@nodes.date.dataset.utc * 1000)
|
||||
date: if @nodes.date then new Date(@nodes.date.getAttribute('datetime')?.trim() or (@nodes.date.dataset.utc * 1000))
|
||||
|
||||
if Conf['Anonymize']
|
||||
@info.nameBlock = 'Anonymous'
|
||||
@ -66,30 +64,21 @@ class Post
|
||||
g.posts.push @fullID, @
|
||||
|
||||
parseNodes: (root) ->
|
||||
post = $ '.post', root
|
||||
info = $ '.postInfo', post
|
||||
s = Site.selectors
|
||||
post = $(s.post, root) or root
|
||||
info = $ s.infoRoot, post
|
||||
nodes =
|
||||
root: root
|
||||
post: post
|
||||
info: info
|
||||
subject: $ '.subject', info
|
||||
name: $ '.name', info
|
||||
email: $ '.useremail', info
|
||||
tripcode: $ '.postertrip', info
|
||||
uniqueIDRoot: $ '.posteruid', info
|
||||
uniqueID: $ '.posteruid > .hand', info
|
||||
capcode: $ '.capcode.hand', info
|
||||
pass: $ '.n-pu', info
|
||||
flag: $ '.flag, .countryFlag', info
|
||||
date: $ '.dateTime', info
|
||||
nameBlock: $ '.nameBlock', info
|
||||
quote: $ '.postNum > a:nth-of-type(2)', info
|
||||
reply: $ '.replylink', info
|
||||
fileRoot: $ '.file', post
|
||||
comment: $ '.postMessage', post
|
||||
quotelinks: []
|
||||
root: root
|
||||
post: post
|
||||
info: info
|
||||
comment: $ s.comment, post
|
||||
quotelinks: []
|
||||
archivelinks: []
|
||||
embedlinks: []
|
||||
for key, selector of s.info
|
||||
nodes[key] = $ selector, info
|
||||
Site.parseNodes?(@, nodes)
|
||||
nodes.uniqueIDRoot or= nodes.uniqueID
|
||||
|
||||
# XXX Edge invalidates HTMLCollections when an ancestor node is inserted into another node.
|
||||
# https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7560353/
|
||||
@ -113,7 +102,7 @@ class Post
|
||||
# 'Comment too long'...
|
||||
# EXIF data. (/p/)
|
||||
@nodes.commentClean = bq = @nodes.comment.cloneNode true
|
||||
@cleanComment bq
|
||||
Site.cleanComment?(bq)
|
||||
@info.comment = @nodesToText bq
|
||||
|
||||
commentDisplay: ->
|
||||
@ -126,13 +115,13 @@ class Post
|
||||
# Trailing spaces.
|
||||
bq = @nodes.commentClean.cloneNode true
|
||||
@cleanSpoilers bq unless Conf['Remove Spoilers'] or Conf['Reveal Spoilers']
|
||||
@cleanCommentDisplay bq
|
||||
Site.cleanCommentDisplay?(bq)
|
||||
@nodesToText(bq).trim().replace(/\s+$/gm, '')
|
||||
|
||||
commentOrig: ->
|
||||
# Get the comment's text for reposting purposes.
|
||||
bq = @nodes.commentClean.cloneNode true
|
||||
@insertTags bq
|
||||
Site.insertTags?(bq)
|
||||
@nodesToText bq
|
||||
|
||||
nodesToText: (bq) ->
|
||||
@ -143,36 +132,15 @@ class Post
|
||||
text += node.data or '\n'
|
||||
text
|
||||
|
||||
cleanComment: (bq) ->
|
||||
if (abbr = $ '.abbr', bq) # 'Comment too long' or 'EXIF data available'
|
||||
for node in $$ '.abbr + br, .exif', bq
|
||||
$.rm node
|
||||
for i in [0...2]
|
||||
$.rm br if (br = abbr.previousSibling) and br.nodeName is 'BR'
|
||||
$.rm abbr
|
||||
|
||||
cleanSpoilers: (bq) ->
|
||||
spoilers = $$ 's', bq
|
||||
spoilers = $$ Site.selectors.spoiler, bq
|
||||
for node in spoilers
|
||||
$.replace node, $.tn '[spoiler]'
|
||||
return
|
||||
|
||||
cleanCommentDisplay: (bq) ->
|
||||
$.rm b if (b = $ 'b', bq) and /^Rolled /.test(b.textContent)
|
||||
$.rm $('.fortune', bq)
|
||||
|
||||
insertTags: (bq) ->
|
||||
for node in $$ 's, .removed-spoiler', bq
|
||||
$.replace node, [$.tn('[spoiler]'), node.childNodes..., $.tn '[/spoiler]']
|
||||
for node in $$ '.prettyprint', bq
|
||||
$.replace node, [$.tn('[code]'), node.childNodes..., $.tn '[/code]']
|
||||
return
|
||||
|
||||
parseQuotes: ->
|
||||
@quotes = []
|
||||
# XXX https://github.com/4chan/4chan-JS/issues/77
|
||||
# 4chan currently creates quote links inside [code] tags; ignore them
|
||||
for quotelink in $$ ':not(pre) > .quotelink', @nodes.comment
|
||||
for quotelink in $$ Site.selectors.quotelink, @nodes.comment
|
||||
@parseQuote quotelink
|
||||
return
|
||||
|
||||
@ -183,13 +151,7 @@ class Post
|
||||
# - catalog links. (>>>/b/catalog or >>>/b/search)
|
||||
# - rules links. (>>>/a/rules)
|
||||
# - text-board quotelinks. (>>>/img/1234)
|
||||
match = quotelink.href.match ///
|
||||
^https?://boards\.4chan\.org/+
|
||||
([^/]+) # boardID
|
||||
/+(?:res|thread)/+\d+(?:[/?][^#]*)?#p
|
||||
(\d+) # postID
|
||||
$
|
||||
///
|
||||
match = quotelink.href.match Site.regexp.quotelink
|
||||
return unless match or (@isClone and quotelink.dataset.postID) # normal or resurrected quote
|
||||
|
||||
@nodes.quotelinks.push quotelink
|
||||
@ -197,39 +159,28 @@ class Post
|
||||
return if @isClone
|
||||
|
||||
# ES6 Set when?
|
||||
fullID = "#{match[1]}.#{match[2]}"
|
||||
fullID = "#{match[1]}.#{match[3]}"
|
||||
@quotes.push fullID unless fullID in @quotes
|
||||
|
||||
parseFile: ->
|
||||
{fileRoot} = @nodes
|
||||
return unless fileRoot
|
||||
return if not (link = $ '.fileText > a, .fileText-original > a', fileRoot)
|
||||
return if not (info = link.nextSibling?.textContent.match /\(([\d.]+ [KMG]?B).*\)/)
|
||||
fileText = fileRoot.firstElementChild
|
||||
@file =
|
||||
text: fileText
|
||||
link: link
|
||||
url: link.href
|
||||
name: fileText.title or link.title or link.textContent
|
||||
size: info[1]
|
||||
isImage: /(jpg|png|gif)$/i.test link.href
|
||||
isVideo: /webm$/i.test link.href
|
||||
dimensions: info[0].match(/\d+x\d+/)?[0]
|
||||
tag: info[0].match(/,[^,]*, ([a-z]+)\)/i)?[1]
|
||||
MD5: fileText.dataset.md5
|
||||
size = +@file.size.match(/[\d.]+/)[0]
|
||||
unit = ['B', 'KB', 'MB', 'GB'].indexOf @file.size.match(/\w+$/)[0]
|
||||
file = {}
|
||||
for key, selector of Site.selectors.file
|
||||
file[key] = $ selector, @nodes.root
|
||||
file.thumbLink = file.thumb?.parentNode
|
||||
|
||||
return if not (file.text and file.link)
|
||||
return if not Site.parseFile @, file
|
||||
|
||||
$.extend file,
|
||||
url: file.link.href
|
||||
isImage: /(jpg|png|gif)$/i.test file.link.href
|
||||
isVideo: /(webm|mp4)$/i.test file.link.href
|
||||
size = +file.size.match(/[\d.]+/)[0]
|
||||
unit = ['B', 'KB', 'MB', 'GB'].indexOf file.size.match(/\w+$/)[0]
|
||||
size *= 1024 while unit-- > 0
|
||||
@file.sizeInBytes = size
|
||||
if (thumb = $ 'a.fileThumb > [data-md5]', fileRoot)
|
||||
$.extend @file,
|
||||
thumb: thumb
|
||||
thumbLink: thumb.parentNode
|
||||
thumbURL: thumb.src
|
||||
MD5: thumb.dataset.md5
|
||||
isSpoiler: $.hasClass thumb.parentNode, 'imgspoiler'
|
||||
if @file.isSpoiler
|
||||
@file.thumbURL = if (m = link.href.match /\d+(?=\.\w+$)/) then "#{location.protocol}//#{ImageHost.thumbHost()}/#{@board}/#{m[0]}s.jpg"
|
||||
file.sizeInBytes = size
|
||||
|
||||
@file = file
|
||||
|
||||
@deadMark =
|
||||
# \u00A0 is nbsp
|
||||
|
||||
@ -1119,3 +1119,7 @@ Config =
|
||||
'updater.position': 'bottom: 0px; left: 0px;'
|
||||
'thread-watcher.position': 'top: 50px; left: 0px;'
|
||||
'qr.position': 'top: 50px; right: 0px;'
|
||||
|
||||
siteSoftware: """
|
||||
4chan.org yotsuba
|
||||
"""
|
||||
|
||||
@ -1272,7 +1272,7 @@ span.hide-announcement {
|
||||
.expanded-image > .post > .file > .fileThumb > img[data-md5] {
|
||||
display: none;
|
||||
}
|
||||
.full-image {
|
||||
.full-image[data-full-i-d] {
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ Main =
|
||||
flatten null, Config
|
||||
|
||||
for db in DataBoard.keys
|
||||
Conf[db] = boards: {}
|
||||
Conf[db] = {}
|
||||
Conf['boardConfig'] = boards: {}
|
||||
Conf['archives'] = Redirect.archives
|
||||
Conf['selectedArchives'] = {}
|
||||
@ -74,15 +74,16 @@ Main =
|
||||
Conf['Toggleable Thread Watcher'] = true
|
||||
|
||||
# Enforce JS whitelist
|
||||
($.getSync or $.get) {'jsWhitelist': Conf['jsWhitelist']}, ({jsWhitelist}) ->
|
||||
$.addCSP "script-src #{jsWhitelist.replace(/^#.*$/mg, '').replace(/[\s;]+/g, ' ').trim()}"
|
||||
if /\.4chan\.org$/.test(location.hostname)
|
||||
($.getSync or $.get) {'jsWhitelist': Conf['jsWhitelist']}, ({jsWhitelist}) ->
|
||||
$.addCSP "script-src #{jsWhitelist.replace(/^#.*$/mg, '').replace(/[\s;]+/g, ' ').trim()}"
|
||||
|
||||
# Get saved values as items
|
||||
items = {}
|
||||
items[key] = undefined for key of Conf
|
||||
items['previousversion'] = undefined
|
||||
($.getSync or $.get) items, (items) ->
|
||||
if !$.perProtocolSettings and (items['Redirect to HTTPS'] ? Conf['Redirect to HTTPS']) and location.protocol isnt 'https:'
|
||||
if !$.perProtocolSettings and /\.4chan\.org$/.test(location.hostname) and (items['Redirect to HTTPS'] ? Conf['Redirect to HTTPS']) and location.protocol isnt 'https:'
|
||||
location.replace('https:' + location.host + location.pathname + location.search + location.hash)
|
||||
return
|
||||
$.asap docSet, ->
|
||||
@ -105,7 +106,7 @@ Main =
|
||||
for key, val of Conf
|
||||
Conf[key] = items[key] ? val
|
||||
|
||||
Main.initFeatures()
|
||||
Site.init Main.initFeatures
|
||||
|
||||
upgrade: (items) ->
|
||||
{previousversion} = items
|
||||
@ -122,11 +123,10 @@ Main =
|
||||
pathname = location.pathname.split /\/+/
|
||||
g.BOARD = new Board pathname[1] unless hostname is 'www.4chan.org'
|
||||
|
||||
if hostname in ['boards.4chan.org', 'sys.4chan.org', 'www.4chan.org']
|
||||
$.global ->
|
||||
document.documentElement.classList.add 'js-enabled'
|
||||
window.FCX = {}
|
||||
Main.jsEnabled = $.hasClass doc, 'js-enabled'
|
||||
$.global ->
|
||||
document.documentElement.classList.add 'js-enabled'
|
||||
window.FCX = {}
|
||||
Main.jsEnabled = $.hasClass doc, 'js-enabled'
|
||||
|
||||
switch hostname
|
||||
when 'www.4chan.org'
|
||||
@ -151,7 +151,7 @@ Main =
|
||||
if ImageHost.test hostname
|
||||
return unless pathname[2] and not /[sm]\.jpg$/.test(pathname[2])
|
||||
$.asap (-> d.readyState isnt 'loading'), ->
|
||||
if Conf['404 Redirect'] and d.title in ['4chan - Temporarily Offline', '4chan - 404 Not Found']
|
||||
if Conf['404 Redirect'] and Site.is404?()
|
||||
Redirect.navigate 'file', {
|
||||
boardID: g.BOARD.ID
|
||||
filename: pathname[pathname.length - 1]
|
||||
@ -166,14 +166,14 @@ Main =
|
||||
ImageCommon.addControls video
|
||||
return
|
||||
|
||||
return unless hostname is 'boards.4chan.org'
|
||||
return if Site.isAuxiliaryPage?()
|
||||
|
||||
if pathname[2] in ['thread', 'res']
|
||||
g.VIEW = 'thread'
|
||||
g.THREADID = +pathname[3]
|
||||
else if pathname[2] in ['catalog', 'archive']
|
||||
g.VIEW = pathname[2]
|
||||
else if pathname[2].match /^\d*$/
|
||||
else if /^(?:catalog|archive)(?:\.html)?$/.test(pathname[2])
|
||||
g.VIEW = pathname[2].replace('.html', '')
|
||||
else if /^(?:index|\d*)(?:\.html)?$/.test(pathname[2])
|
||||
g.VIEW = 'index'
|
||||
else
|
||||
return
|
||||
@ -186,6 +186,7 @@ Main =
|
||||
|
||||
# c.time 'All initializations'
|
||||
for [name, feature] in Main.features
|
||||
continue if Site.disabledFeatures and name in Site.disabledFeatures
|
||||
# c.time "#{name} initialization"
|
||||
try
|
||||
feature.init()
|
||||
@ -245,9 +246,9 @@ Main =
|
||||
$.rm Main.bgColorStyle
|
||||
else
|
||||
# Determine proper background color for dialogs if 4chan is using a special stylesheet.
|
||||
div = $.el 'div',
|
||||
className: 'reply'
|
||||
div.style.cssText = 'position: absolute; visibility: hidden;'
|
||||
div = Site.bgColoredEl()
|
||||
div.style.position = 'absolute';
|
||||
div.style.visibility = 'hidden';
|
||||
$.add d.body, div
|
||||
bgColor = window.getComputedStyle(div).backgroundColor
|
||||
$.rm div
|
||||
@ -265,42 +266,44 @@ Main =
|
||||
}
|
||||
|
||||
initReady: ->
|
||||
# XXX Sometimes threads don't 404 but are left over as stubs containing one garbage reply post.
|
||||
if g.VIEW is 'thread' and (d.title in ['4chan - Temporarily Offline', '4chan - 404 Not Found'] or ($('.board') and not $('.opContainer')))
|
||||
ThreadWatcher.set404 g.BOARD.ID, g.THREADID, ->
|
||||
if Conf['404 Redirect']
|
||||
Redirect.navigate 'thread',
|
||||
boardID: g.BOARD.ID
|
||||
threadID: g.THREADID
|
||||
postID: +location.hash.match /\d+/ # post number or 0
|
||||
, "/#{g.BOARD}/"
|
||||
if Site.is404?()
|
||||
if g.VIEW is 'thread'
|
||||
ThreadWatcher.set404 g.BOARD.ID, g.THREADID, ->
|
||||
if Conf['404 Redirect']
|
||||
Redirect.navigate 'thread',
|
||||
boardID: g.BOARD.ID
|
||||
threadID: g.THREADID
|
||||
postID: +location.hash.match /\d+/ # post number or 0
|
||||
, "/#{g.BOARD}/"
|
||||
|
||||
return
|
||||
|
||||
return if d.title in ['4chan - Temporarily Offline', '4chan - 404 Not Found']
|
||||
|
||||
if g.VIEW in ['index', 'thread'] and not $('.board + *')
|
||||
if Site.isIncomplete?()
|
||||
msg = $.el 'div',
|
||||
<%= html('The page didn't load completely.<br>Some features may not work unless you <a href="javascript:;">reload</a>.') %>
|
||||
$.on $('a', msg), 'click', -> location.reload()
|
||||
new Notice 'warning', msg
|
||||
|
||||
# Parse HTML or skip it and start building from JSON.
|
||||
unless Conf['JSON Index'] and g.VIEW is 'index'
|
||||
unless Index.enabled
|
||||
Main.initThread()
|
||||
else
|
||||
Main.expectInitFinished = true
|
||||
$.event '4chanXInitFinished'
|
||||
|
||||
initThread: ->
|
||||
if (board = $ '.board')
|
||||
s = Site.selectors
|
||||
if (board = $ s.board)
|
||||
threads = []
|
||||
posts = []
|
||||
|
||||
for threadRoot in $$ '.board > .thread', board
|
||||
thread = new Thread +threadRoot.id[1..], g.BOARD
|
||||
for threadRoot in $$(s.thread, board)
|
||||
thread = new Thread +threadRoot.id.match(/\d*$/)[0], g.BOARD
|
||||
thread.nodes.root = threadRoot
|
||||
threads.push thread
|
||||
for postRoot in $$('.thread > .postContainer', threadRoot) when $('.postMessage', postRoot)
|
||||
postRoots = $$ s.postContainer, threadRoot
|
||||
postRoots.unshift threadRoot if Site.isOPContainerThread
|
||||
for postRoot in postRoots when $(s.comment, postRoot)
|
||||
try
|
||||
posts.push new Post postRoot, thread, g.BOARD
|
||||
catch err
|
||||
@ -313,17 +316,7 @@ Main =
|
||||
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]
|
||||
|
||||
if g.BOARD.ID is 'f' and g.VIEW is 'thread'
|
||||
$.ajax "#{location.protocol}//a.4cdn.org/f/thread/#{g.THREADID}.json",
|
||||
timeout: $.MINUTE
|
||||
onloadend: ->
|
||||
if @response and posts[0].file
|
||||
posts[0].file.text.dataset.md5 = posts[0].file.MD5 = @response.posts[0].md5
|
||||
Site.parseThreadMetadata?(threads[0])
|
||||
|
||||
Main.callbackNodes 'Thread', threads
|
||||
Main.callbackNodesDB 'Post', posts, ->
|
||||
@ -424,11 +417,12 @@ Main =
|
||||
<%= html('<span class="report-error"> [<a href="${url}" target="_blank">report</a>]</span>') %>
|
||||
|
||||
isThisPageLegit: ->
|
||||
# 404 error page or similar.
|
||||
# not 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 = if Site.isThisPageLegit
|
||||
Site.isThisPageLegit()
|
||||
else
|
||||
!/^[45]\d\d\b/.test(document.title)
|
||||
Main.thisPageIsLegit
|
||||
|
||||
ready: (cb) ->
|
||||
|
||||
@ -384,6 +384,10 @@ $.oneItemSugar = (fn) ->
|
||||
|
||||
$.syncing = {}
|
||||
|
||||
$.securityCheck = (data) ->
|
||||
if location.protocol isnt 'https:'
|
||||
delete data['Redirect to HTTPS']
|
||||
|
||||
<% if (type === 'crx') { %>
|
||||
# https://developer.chrome.com/extensions/storage.html
|
||||
$.oldValue =
|
||||
@ -485,6 +489,7 @@ do ->
|
||||
|
||||
$.set = $.oneItemSugar (data, cb) ->
|
||||
return unless $.crxWorking()
|
||||
$.securityCheck data
|
||||
$.extend items.local, data
|
||||
setArea 'local', cb
|
||||
|
||||
@ -536,6 +541,7 @@ if GM?.deleteValue? and window.BroadcastChannel and not GM_addValueChangeListene
|
||||
cb items
|
||||
|
||||
$.set = $.oneItemSugar (items, cb) ->
|
||||
$.securityCheck items
|
||||
Promise.all(GM.setValue(g.NAMESPACE + key, JSON.stringify(val)) for key, val of items).then ->
|
||||
$.syncChannel.postMessage items
|
||||
cb?()
|
||||
@ -655,6 +661,7 @@ else
|
||||
cb items
|
||||
|
||||
$.set = $.oneItemSugar (items, cb) ->
|
||||
$.securityCheck items
|
||||
$.queueTask ->
|
||||
for key, value of items
|
||||
$.setValue(g.NAMESPACE + key, JSON.stringify value)
|
||||
|
||||
1
src/site/SW.js
Normal file
1
src/site/SW.js
Normal file
@ -0,0 +1 @@
|
||||
SW = {};
|
||||
123
src/site/SW.tinyboard.coffee
Normal file
123
src/site/SW.tinyboard.coffee
Normal file
@ -0,0 +1,123 @@
|
||||
SW.tinyboard =
|
||||
isOPContainerThread: true
|
||||
|
||||
disabledFeatures: [
|
||||
'Board Configuration'
|
||||
'Normalize URL'
|
||||
'Captcha Configuration'
|
||||
'Image Host Rewriting'
|
||||
'Index Generator'
|
||||
'Announcement Hiding'
|
||||
'Fourchan thingies'
|
||||
'Custom CSS'
|
||||
'Resurrect Quotes'
|
||||
'Quick Reply Personas'
|
||||
'Quick Reply'
|
||||
'Cooldown'
|
||||
'Pass Link'
|
||||
'Index Generator (Menu)'
|
||||
'Edit Link'
|
||||
'Archive Link'
|
||||
'Quote Inlining'
|
||||
'Quote Previewing'
|
||||
'Quote Backlinks'
|
||||
'File Info Formatting'
|
||||
'Image Expansion'
|
||||
'Image Expansion (Menu)'
|
||||
'Comment Expansion'
|
||||
'Thread Expansion'
|
||||
'Thread Stats'
|
||||
'Thread Updater'
|
||||
'Mark New IPs'
|
||||
'Banner'
|
||||
'Flash Features'
|
||||
'Reply Pruning'
|
||||
<% if (readJSON('/.tests_enabled')) { %>
|
||||
'Build Test'
|
||||
<% } %>
|
||||
]
|
||||
|
||||
detect: ->
|
||||
for script in $$ 'script:not([src])', d.head
|
||||
return true if /\bvar configRoot=".*?"/.test(script.textContent)
|
||||
false
|
||||
|
||||
selectors:
|
||||
board: 'form[name="postcontrols"]'
|
||||
thread: 'div[id^="thread_"]'
|
||||
postContainer: '.reply' # postContainer is thread for OP
|
||||
infoRoot: '.intro'
|
||||
info:
|
||||
subject: '.subject'
|
||||
name: '.name'
|
||||
email: '.email'
|
||||
tripcode: '.trip'
|
||||
uniqueID: '.poster_id'
|
||||
capcode: '.capcode'
|
||||
flag: '.flag'
|
||||
date: 'time'
|
||||
nameBlock: 'label'
|
||||
quote: 'a[href*="#q"]'
|
||||
reply: 'a[href*="/res/"]:not([href*="#"])'
|
||||
icons:
|
||||
isSticky: '.fa-thumb-tack'
|
||||
isClosed: '.fa-lock'
|
||||
file:
|
||||
text: '.fileinfo'
|
||||
link: '.fileinfo > a'
|
||||
thumb: 'a > .post-image'
|
||||
comment: '.body'
|
||||
spoiler: '.spoiler'
|
||||
quotelink: 'a[onclick^="highlightReply("]'
|
||||
boardList: '.boardlist'
|
||||
|
||||
xpath:
|
||||
thread: 'div[starts-with(@id,"thread_")]'
|
||||
postContainer: 'div[starts-with(@id,"reply_") or starts-with(@id,"thread_")]'
|
||||
|
||||
regexp:
|
||||
quotelink:
|
||||
///
|
||||
/
|
||||
([^/]+) # boardID
|
||||
/res/
|
||||
(\d+) # threadID
|
||||
\.html#
|
||||
(\d+) # postID
|
||||
$
|
||||
///
|
||||
|
||||
bgColoredEl: ->
|
||||
$.el 'div', className: 'post reply'
|
||||
|
||||
parseNodes: (post, nodes) ->
|
||||
# Add vichan's span.poster_id around the ID if not already present.
|
||||
return if nodes.uniqueID
|
||||
nodes.info.normalize()
|
||||
{nextSibling} = nodes.nameBlock
|
||||
if nextSibling.nodeType is 3 and (m = nextSibling.textContent.match /(\s*ID:\s*)(\S+)/)
|
||||
nextSibling = nextSibling.splitText m[1].length
|
||||
nextSibling.splitText m[2].length
|
||||
nodes.uniqueID = uniqueID = $.el 'span', {className: 'poster_id'}
|
||||
$.replace nextSibling, uniqueID
|
||||
$.add uniqueID, nextSibling
|
||||
|
||||
parseFile: (post, file) ->
|
||||
{text, link, thumb} = file
|
||||
return false if $.x("ancestor::#{Site.xpath.postContainer}[1]", text) isnt post.nodes.root # file belongs to a reply
|
||||
return false if not (infoNode = link.nextElementSibling)
|
||||
return false if not (info = infoNode.textContent.match /\((Spoiler Image, )?([\d.]+ [KMG]?B).*\)/)
|
||||
nameNode = $ '.postfilename', text
|
||||
$.extend file,
|
||||
name: if nameNode then (nameNode.title or nameNode.textContent) else link.pathname.match(/[^/]*$/)[0]
|
||||
size: info[2]
|
||||
dimensions: info[0].match(/\d+x\d+/)?[0]
|
||||
if thumb
|
||||
$.extend file,
|
||||
thumbURL: if '/static/' in thumb.src then link.href else thumb.src
|
||||
isSpoiler: !!info[1]
|
||||
true
|
||||
|
||||
isThumbExpanded: (file) ->
|
||||
# Detect old Tinyboard image expansion that changes src attribute on thumbnail.
|
||||
$.hasClass file.thumb.parentNode, 'expanded'
|
||||
135
src/site/SW.yotsuba.coffee
Normal file
135
src/site/SW.yotsuba.coffee
Normal file
@ -0,0 +1,135 @@
|
||||
SW.yotsuba =
|
||||
isOPContainerThread: false
|
||||
|
||||
selectors:
|
||||
board: '.board'
|
||||
thread: '.thread'
|
||||
postContainer: '.postContainer'
|
||||
sideArrows: '.sideArrows'
|
||||
post: '.post'
|
||||
infoRoot: '.postInfo'
|
||||
info:
|
||||
subject: '.subject'
|
||||
name: '.name'
|
||||
email: '.useremail'
|
||||
tripcode: '.postertrip'
|
||||
uniqueIDRoot: '.posteruid'
|
||||
uniqueID: '.posteruid > .hand'
|
||||
capcode: '.capcode.hand'
|
||||
pass: '.n-pu'
|
||||
flag: '.flag, .countryFlag'
|
||||
date: '.dateTime'
|
||||
nameBlock: '.nameBlock'
|
||||
quote: '.postNum > a:nth-of-type(2)'
|
||||
reply: '.replylink'
|
||||
icons:
|
||||
isSticky: '.stickyIcon'
|
||||
isClosed: '.closedIcon'
|
||||
isArchived: '.archivedIcon'
|
||||
file:
|
||||
text: '.file > :first-child'
|
||||
link: '.fileText > a'
|
||||
thumb: 'a.fileThumb > [data-md5]'
|
||||
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
|
||||
boardList: '#boardNavDesktop > .boardList'
|
||||
|
||||
xpath:
|
||||
thread: 'div[contains(concat(" ",@class," ")," thread ")]'
|
||||
postContainer: 'div[contains(@class,"postContainer")]'
|
||||
|
||||
regexp:
|
||||
quotelink:
|
||||
///
|
||||
^https?://boards\.4chan\.org/+
|
||||
([^/]+) # boardID
|
||||
/+thread/+
|
||||
(\d+) # threadID
|
||||
(?:[/?][^#]*)?
|
||||
(?:#p
|
||||
(\d+) # postID
|
||||
)?
|
||||
$
|
||||
///
|
||||
|
||||
bgColoredEl: ->
|
||||
$.el 'div', className: 'reply'
|
||||
|
||||
isThisPageLegit: ->
|
||||
# not 404 error page or similar.
|
||||
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']
|
||||
|
||||
is404: ->
|
||||
# XXX Sometimes threads don't 404 but are left over as stubs containing one garbage reply post.
|
||||
d.title in ['4chan - Temporarily Offline', '4chan - 404 Not Found'] or (g.VIEW is 'thread' and $('.board') and not $('.opContainer'))
|
||||
|
||||
isIncomplete: ->
|
||||
return g.VIEW in ['index', 'thread'] and not $('.board + *')
|
||||
|
||||
isAuxiliaryPage: ->
|
||||
location.hostname isnt 'boards.4chan.org'
|
||||
|
||||
scriptData: ->
|
||||
for script in $$ 'script:not([src])', d.head
|
||||
return script.textContent if /\bcooldowns *=/.test script.textContent
|
||||
''
|
||||
|
||||
parseThreadMetadata: (thread) ->
|
||||
scriptData = @scriptData()
|
||||
thread.postLimit = /\bbumplimit *= *1\b/.test scriptData
|
||||
thread.fileLimit = /\bimagelimit *= *1\b/.test scriptData
|
||||
thread.ipCount = if (m = scriptData.match /\bunique_ips *= *(\d+)\b/) then +m[1]
|
||||
|
||||
if g.BOARD.ID is 'f' and thread.OP.file
|
||||
{file} = thread.OP
|
||||
$.ajax "#{location.protocol}//a.4cdn.org/f/thread/#{thread}.json",
|
||||
timeout: $.MINUTE
|
||||
onloadend: ->
|
||||
if @response
|
||||
file.text.dataset.md5 = file.MD5 = @response.posts[0].md5
|
||||
|
||||
parseNodes: (post, nodes) ->
|
||||
# Add CSS classes to sticky/closed icons on /f/ to match other boards.
|
||||
if post.boardID is 'f'
|
||||
for type in ['Sticky', 'Closed'] when (icon = $ "img[alt=#{type}]", nodes.info)
|
||||
$.addClass icon, "#{type.toLowerCase()}Icon", 'retina'
|
||||
|
||||
parseFile: (post, file) ->
|
||||
{text, link, thumb} = file
|
||||
return false if not (info = link.nextSibling?.textContent.match /\(([\d.]+ [KMG]?B).*\)/)
|
||||
$.extend file,
|
||||
name: text.title or link.title or link.textContent
|
||||
size: info[1]
|
||||
dimensions: info[0].match(/\d+x\d+/)?[0]
|
||||
tag: info[0].match(/,[^,]*, ([a-z]+)\)/i)?[1]
|
||||
MD5: text.dataset.md5
|
||||
if thumb
|
||||
$.extend file,
|
||||
thumbURL: thumb.src
|
||||
MD5: thumb.dataset.md5
|
||||
isSpoiler: $.hasClass thumb.parentNode, 'imgspoiler'
|
||||
if file.isSpoiler
|
||||
file.thumbURL = if (m = link.href.match /\d+(?=\.\w+$)/) then "#{location.protocol}//#{ImageHost.thumbHost()}/#{post.board}/#{m[0]}s.jpg"
|
||||
true
|
||||
|
||||
cleanComment: (bq) ->
|
||||
if (abbr = $ '.abbr', bq) # 'Comment too long' or 'EXIF data available'
|
||||
for node in $$ '.abbr + br, .exif', bq
|
||||
$.rm node
|
||||
for i in [0...2]
|
||||
$.rm br if (br = abbr.previousSibling) and br.nodeName is 'BR'
|
||||
$.rm abbr
|
||||
|
||||
cleanCommentDisplay: (bq) ->
|
||||
$.rm b if (b = $ 'b', bq) and /^Rolled /.test(b.textContent)
|
||||
$.rm $('.fortune', bq)
|
||||
|
||||
insertTags: (bq) ->
|
||||
for node in $$ 's, .removed-spoiler', bq
|
||||
$.replace node, [$.tn('[spoiler]'), node.childNodes..., $.tn '[/spoiler]']
|
||||
for node in $$ '.prettyprint', bq
|
||||
$.replace node, [$.tn('[code]'), node.childNodes..., $.tn '[/code]']
|
||||
return
|
||||
24
src/site/Site.coffee
Normal file
24
src/site/Site.coffee
Normal file
@ -0,0 +1,24 @@
|
||||
Site =
|
||||
init: (cb) ->
|
||||
swDict = {}
|
||||
for line in Conf['siteSoftware'].split('\n') when line[0] isnt '#'
|
||||
[hostname, software] = line.split(' ')
|
||||
swDict[hostname] = software if software of SW
|
||||
{hostname} = location
|
||||
while hostname and hostname not of swDict
|
||||
hostname = hostname.replace(/^[^.]*\.?/, '')
|
||||
if hostname
|
||||
@set hostname, swDict[hostname]
|
||||
cb()
|
||||
else
|
||||
$.onExists doc, 'body', =>
|
||||
for software of SW
|
||||
if SW[software].detect?()
|
||||
@set location.hostname.replace(/^www\./, ''), software
|
||||
Conf['siteSoftware'] += "\n#{@hostname} #{@software}"
|
||||
$.set 'siteSoftware', Conf['siteSoftware']
|
||||
cb()
|
||||
return
|
||||
|
||||
set: (@hostname, @software) ->
|
||||
$.extend @, SW[@software]
|
||||
Loading…
x
Reference in New Issue
Block a user