Merge from Appchan X: Whitespace and other changes not affecting compiled script.
This commit is contained in:
parent
1b7cc5270c
commit
8df52a0b24
@ -58,6 +58,7 @@ Redirect =
|
||||
protocol = Redirect.protocol archive
|
||||
URL = new String "#{protocol}#{archive.domain}/_/api/chan/post/?board=#{boardID}&num=#{postID}"
|
||||
return '' unless Redirect.securityCheck URL
|
||||
|
||||
URL.archive = archive
|
||||
URL
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ Anonymize =
|
||||
Post.callbacks.push
|
||||
name: 'Anonymize'
|
||||
cb: @node
|
||||
|
||||
node: ->
|
||||
return if @info.capcode or @isClone
|
||||
{name, tripcode, email} = @nodes
|
||||
@ -17,6 +18,7 @@ Anonymize =
|
||||
if @info.email
|
||||
$.replace email, name
|
||||
delete @nodes.email
|
||||
|
||||
archive: ->
|
||||
$.ready ->
|
||||
name.textContent = 'Anonymous' for name in $$ '.name'
|
||||
|
||||
@ -84,11 +84,13 @@ Filter =
|
||||
(value) -> regexp is value
|
||||
else
|
||||
(value) -> regexp.test value
|
||||
|
||||
settings =
|
||||
hide: !hl
|
||||
stub: stub
|
||||
class: hl
|
||||
top: top
|
||||
|
||||
(value, isReply) ->
|
||||
if isReply and op is 'only' or !isReply and op is 'no'
|
||||
return false
|
||||
@ -103,10 +105,7 @@ Filter =
|
||||
# Continue if there's nothing to filter (no tripcode for example).
|
||||
continue if value is false
|
||||
|
||||
for filter in Filter.filters[key]
|
||||
unless result = filter value, @isReply
|
||||
continue
|
||||
|
||||
for filter in Filter.filters[key] when result = filter value, @isReply
|
||||
# Hide
|
||||
if result.hide
|
||||
if @isReply
|
||||
|
||||
@ -12,12 +12,14 @@ PostHiding =
|
||||
|
||||
node: ->
|
||||
return if !@isReply or @isClone or @isFetchedQuote
|
||||
|
||||
if data = PostHiding.db.get {boardID: @board.ID, threadID: @thread.ID, postID: @ID}
|
||||
if data.thisPost
|
||||
PostHiding.hide @, data.makeStub, data.hideRecursively
|
||||
else
|
||||
Recursive.apply PostHiding.hide, @, data.makeStub, true
|
||||
Recursive.add PostHiding.hide, @, data.makeStub, true
|
||||
|
||||
return unless Conf['Reply Hiding Buttons']
|
||||
sideArrows = $('.sideArrows', @nodes.root)
|
||||
$.replace sideArrows.firstChild, PostHiding.makeButton @, 'hide'
|
||||
|
||||
@ -1,18 +1,16 @@
|
||||
Recursive =
|
||||
recursives: {}
|
||||
init: ->
|
||||
return if g.VIEW not in ['index', 'thread']
|
||||
|
||||
return unless g.VIEW in ['index', 'thread']
|
||||
Post.callbacks.push
|
||||
name: 'Recursive'
|
||||
cb: @node
|
||||
|
||||
node: ->
|
||||
return if @isClone or @isFetchedQuote
|
||||
for quote in @quotes
|
||||
if obj = Recursive.recursives[quote]
|
||||
for recursive, i in obj.recursives
|
||||
recursive @, obj.args[i]...
|
||||
for quote in @quotes when obj = Recursive.recursives[quote]
|
||||
for recursive, i in obj.recursives
|
||||
recursive @, obj.args[i]...
|
||||
return
|
||||
|
||||
add: (recursive, post, args...) ->
|
||||
|
||||
@ -334,6 +334,7 @@ Build =
|
||||
|
||||
root = $.el 'div',
|
||||
className: 'catalog-thread'
|
||||
|
||||
$.extend root, <%= html(
|
||||
'<a href="/${thread.board}/thread/${thread.ID}">' +
|
||||
'&{thumb}' +
|
||||
@ -352,12 +353,15 @@ Build =
|
||||
for quote in $$ '.quotelink', root.lastElementChild
|
||||
href = quote.getAttribute 'href'
|
||||
quote.href = "/#{thread.board}/thread/#{thread.ID}" + href if href[0] is '#'
|
||||
|
||||
for exif in $$ '.abbr, .exif', root.lastElementChild
|
||||
$.rm exif
|
||||
|
||||
for pp in $$ '.prettyprint', root.lastElementChild
|
||||
cc = $.el 'span', className: 'catalog-code'
|
||||
$.add cc, [pp.childNodes...]
|
||||
$.replace pp, cc
|
||||
|
||||
for br in $$ 'br', root.lastElementChild when br.previousSibling?.nodeName is 'BR'
|
||||
$.rm br
|
||||
|
||||
@ -366,6 +370,7 @@ Build =
|
||||
src: "#{staticPath}sticky#{gifIcon}"
|
||||
className: 'stickyIcon'
|
||||
title: 'Sticky'
|
||||
|
||||
if thread.isClosed
|
||||
$.add $('.catalog-icons', root), $.el 'img',
|
||||
src: "#{staticPath}closed#{gifIcon}"
|
||||
@ -374,6 +379,7 @@ Build =
|
||||
|
||||
if data.bumplimit
|
||||
$.addClass $('.post-count', root), 'warning'
|
||||
|
||||
if data.imagelimit
|
||||
$.addClass $('.file-count', root), 'warning'
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
Config =
|
||||
main:
|
||||
'Miscellaneous':
|
||||
'JSON Navigation' : [
|
||||
'JSON Navigation': [
|
||||
true
|
||||
'Replace the original board index with one supporting searching, sorting, infinite scrolling, and a catalog mode.'
|
||||
]
|
||||
@ -112,6 +112,7 @@ Config =
|
||||
true
|
||||
'<%= meta.name %> is NOT designed to work with the native extension.'
|
||||
]
|
||||
|
||||
'Linkification':
|
||||
'Linkify': [
|
||||
true
|
||||
@ -587,69 +588,69 @@ Config =
|
||||
|
||||
filter:
|
||||
name: """
|
||||
# Filter any namefags:
|
||||
#/^(?!Anonymous$)/
|
||||
"""
|
||||
# Filter any namefags:
|
||||
#/^(?!Anonymous$)/
|
||||
"""
|
||||
|
||||
uniqueID: """
|
||||
# Filter a specific ID:
|
||||
#/Txhvk1Tl/
|
||||
"""
|
||||
# Filter a specific ID:
|
||||
#/Txhvk1Tl/
|
||||
"""
|
||||
|
||||
tripcode: """
|
||||
# Filter any tripfag
|
||||
#/^!/
|
||||
"""
|
||||
# Filter any tripfag
|
||||
#/^!/
|
||||
"""
|
||||
|
||||
capcode: """
|
||||
# Set a custom class for mods:
|
||||
#/Mod$/;highlight:mod;op:yes
|
||||
# Set a custom class for moot:
|
||||
#/Admin$/;highlight:moot;op:yes
|
||||
"""
|
||||
# Set a custom class for mods:
|
||||
#/Mod$/;highlight:mod;op:yes
|
||||
# Set a custom class for moot:
|
||||
#/Admin$/;highlight:moot;op:yes
|
||||
"""
|
||||
|
||||
subject: """
|
||||
# Filter Generals on /v/:
|
||||
#/general/i;boards:v;op:only
|
||||
"""
|
||||
# Filter Generals on /v/:
|
||||
#/general/i;boards:v;op:only
|
||||
"""
|
||||
|
||||
comment: """
|
||||
# Filter Stallman copypasta on /g/:
|
||||
#/what you\'re refer+ing to as linux/i;boards:g
|
||||
"""
|
||||
# Filter Stallman copypasta on /g/:
|
||||
#/what you\'re refer+ing to as linux/i;boards:g
|
||||
"""
|
||||
|
||||
flag: ''
|
||||
filename: ''
|
||||
dimensions: """
|
||||
# Highlight potential wallpapers:
|
||||
#/1920x1080/;op:yes;highlight;top:no;boards:w,wg
|
||||
"""
|
||||
# Highlight potential wallpapers:
|
||||
#/1920x1080/;op:yes;highlight;top:no;boards:w,wg
|
||||
"""
|
||||
|
||||
filesize: ''
|
||||
|
||||
MD5: ''
|
||||
|
||||
sauces: """
|
||||
https://www.google.com/searchbyimage?image_url=%TURL
|
||||
http://iqdb.org/?url=%TURL
|
||||
#//tineye.com/search?url=%TURL
|
||||
#//saucenao.com/search.php?url=%TURL
|
||||
#http://3d.iqdb.org/?url=%TURL
|
||||
#http://regex.info/exif.cgi?imgurl=%URL
|
||||
# uploaders:
|
||||
#//imgur.com/upload?url=%URL;text:Upload to imgur
|
||||
# "View Same" in archives:
|
||||
#https://archive.moe/_/search/image/%MD5/;text:View same on archive.moe
|
||||
#https://archive.moe/%board/search/image/%MD5/;text:View same on archive.moe/%board/;boards:<%=
|
||||
grunt.file.readJSON('src/Archive/archives.json').filter(function(x) {return x.uid === 0})[0].files.join(',')
|
||||
%>
|
||||
#https://rbt.asia/%board/image/%MD5;text:View same on RBT /%board/;boards:<%=
|
||||
grunt.file.readJSON('src/Archive/archives.json').filter(function(x) {return x.uid === 8})[0].files.join(',')
|
||||
%>
|
||||
# Search with full image only for image file types:
|
||||
#https://www.google.com/searchbyimage?image_url=%URL;types:gif,jpg,png
|
||||
#https://www.google.com/searchbyimage?image_url=%TURL;types:webm,pdf
|
||||
"""
|
||||
https://www.google.com/searchbyimage?image_url=%TURL
|
||||
http://iqdb.org/?url=%TURL
|
||||
#//tineye.com/search?url=%TURL
|
||||
#//saucenao.com/search.php?url=%TURL
|
||||
#http://3d.iqdb.org/?url=%TURL
|
||||
#http://regex.info/exif.cgi?imgurl=%URL
|
||||
# uploaders:
|
||||
#//imgur.com/upload?url=%URL;text:Upload to imgur
|
||||
# "View Same" in archives:
|
||||
#https://archive.moe/_/search/image/%MD5/;text:View same on archive.moe
|
||||
#https://archive.moe/%board/search/image/%MD5/;text:View same on archive.moe/%board/;boards:<%=
|
||||
grunt.file.readJSON('src/Archive/archives.json').filter(function(x) {return x.uid === 0})[0].files.join(',')
|
||||
%>
|
||||
#https://rbt.asia/%board/image/%MD5;text:View same on RBT /%board/;boards:<%=
|
||||
grunt.file.readJSON('src/Archive/archives.json').filter(function(x) {return x.uid === 8})[0].files.join(',')
|
||||
%>
|
||||
# Search with full image only for image file types:
|
||||
#https://www.google.com/searchbyimage?image_url=%URL;types:gif,jpg,png
|
||||
#https://www.google.com/searchbyimage?image_url=%TURL;types:webm,pdf
|
||||
"""
|
||||
|
||||
FappeT:
|
||||
werk: false
|
||||
@ -678,29 +679,29 @@ http://iqdb.org/?url=%TURL
|
||||
'Custom Board Navigation': true
|
||||
|
||||
boardnav: """
|
||||
[ toggle-all ]
|
||||
a-replace
|
||||
c-replace
|
||||
g-replace
|
||||
k-replace
|
||||
v-replace
|
||||
vg-replace
|
||||
vr-replace
|
||||
ck-replace
|
||||
co-replace
|
||||
fit-replace
|
||||
jp-replace
|
||||
mu-replace
|
||||
sp-replace
|
||||
tv-replace
|
||||
vp-replace
|
||||
[external-text:"FAQ","<%= meta.faq %>"]
|
||||
[ toggle-all ]
|
||||
a-replace
|
||||
c-replace
|
||||
g-replace
|
||||
k-replace
|
||||
v-replace
|
||||
vg-replace
|
||||
vr-replace
|
||||
ck-replace
|
||||
co-replace
|
||||
fit-replace
|
||||
jp-replace
|
||||
mu-replace
|
||||
sp-replace
|
||||
tv-replace
|
||||
vp-replace
|
||||
[external-text:"FAQ","<%= meta.faq %>"]
|
||||
"""
|
||||
|
||||
QR:
|
||||
'QR.personas': """
|
||||
#options:"sage";boards:jp;always
|
||||
"""
|
||||
"""
|
||||
|
||||
time: '%m/%d/%y(%a)%H:%M:%S'
|
||||
|
||||
|
||||
@ -51,7 +51,7 @@ Get =
|
||||
# get all their backlinks.
|
||||
posts.forEach (qPost) ->
|
||||
if fullID in qPost.quotes
|
||||
handleQuotes qPost, 'quotelinks'
|
||||
handleQuotes qPost, 'quotelinks'
|
||||
|
||||
# Second:
|
||||
# If we have quote backlinks:
|
||||
@ -66,10 +66,12 @@ 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
|
||||
''
|
||||
|
||||
postClone: (boardID, threadID, postID, root, context) ->
|
||||
if post = g.posts["#{boardID}.#{postID}"]
|
||||
Get.insert post, root, context
|
||||
@ -81,6 +83,7 @@ Get =
|
||||
Get.fetchedPost @, boardID, threadID, postID, root, context
|
||||
else
|
||||
Get.archivedPost boardID, postID, root, context
|
||||
|
||||
insert: (post, root, context) ->
|
||||
# Stop here if the container has been removed while loading.
|
||||
return unless root.parentNode
|
||||
@ -95,6 +98,7 @@ Get =
|
||||
$.rmAll root
|
||||
$.add root, nodes.root
|
||||
$.event 'PostsInserted'
|
||||
|
||||
fetchedPost: (req, boardID, threadID, postID, root, context) ->
|
||||
# In case of multiple callbacks for the same request,
|
||||
# don't parse the same original post more than once.
|
||||
@ -127,6 +131,7 @@ Get =
|
||||
$.cache api, ->
|
||||
Get.fetchedPost @, boardID, threadID, postID, root, context
|
||||
return
|
||||
|
||||
# The post can be deleted by the time we check a quote.
|
||||
unless Get.archivedPost boardID, postID, root, context
|
||||
$.addClass root, 'warning'
|
||||
@ -141,6 +146,7 @@ Get =
|
||||
post.isFetchedQuote = true
|
||||
Main.callbackNodes Post, [post]
|
||||
Get.insert post, root, context
|
||||
|
||||
archivedPost: (boardID, postID, root, context) ->
|
||||
return false unless Conf['Resurrect Quotes']
|
||||
return false unless url = Redirect.to 'post', {boardID, postID}
|
||||
@ -161,6 +167,7 @@ Get =
|
||||
Get.parseArchivedPost response, boardID, postID, root, context
|
||||
return true
|
||||
return false
|
||||
|
||||
parseArchivedPost: (data, boardID, postID, root, context) ->
|
||||
# In case of multiple callbacks for the same request,
|
||||
# don't parse the same original post more than once.
|
||||
|
||||
@ -143,7 +143,10 @@ Header =
|
||||
$.extend boardList, <%= html(
|
||||
'<span id="custom-board-list"></span>' +
|
||||
'<span id="full-board-list" hidden>' +
|
||||
'<span class="hide-board-list-container brackets-wrap"><a href="javascript:;" class="hide-board-list-button"> - </a></span> ' +
|
||||
'<span class="hide-board-list-container brackets-wrap">' +
|
||||
'<a href="javascript:;" class="hide-board-list-button"> - </a>' +
|
||||
'</span>' +
|
||||
' ' +
|
||||
'<span class="boardList"></span>' +
|
||||
'</span>'
|
||||
) %>
|
||||
@ -185,11 +188,13 @@ Header =
|
||||
nodes = boardnav.match(/[\w@]+(-(all|title|replace|full|index|catalog|archive|expired|text:"[^"]+"(,"[^"]+")?))*|[^\w@]+/g).map (t) ->
|
||||
if /^[^\w@]/.test t
|
||||
return $.tn t
|
||||
|
||||
text = url = null
|
||||
t = t.replace /-text:"([^"]+)"(?:,"([^"]+)")?/g, (m0, m1, m2) ->
|
||||
text = m1
|
||||
url = m2
|
||||
''
|
||||
|
||||
if /^toggle-all/.test t
|
||||
a = $.el 'a',
|
||||
className: 'show-board-list-button'
|
||||
@ -197,12 +202,14 @@ Header =
|
||||
href: 'javascript:;'
|
||||
$.on a, 'click', Header.toggleBoardList
|
||||
return a
|
||||
|
||||
if /^external/.test t
|
||||
a = $.el 'a',
|
||||
href: url or 'javascript:;'
|
||||
textContent: text or '+'
|
||||
className: 'external'
|
||||
return a
|
||||
|
||||
boardID = if /^current/.test t
|
||||
g.BOARD.ID
|
||||
else
|
||||
@ -411,7 +418,6 @@ Header =
|
||||
hash = @location.hash[1..]
|
||||
return unless /^p\d+$/.test(hash) and post = $.id hash
|
||||
return if (Get.postFromRoot post).isHidden
|
||||
|
||||
Header.scrollTo post
|
||||
|
||||
scrollTo: (root, down, needed) ->
|
||||
@ -451,10 +457,12 @@ Header =
|
||||
headRect = Header.toggle.getBoundingClientRect()
|
||||
bottom -= clientHeight - headRect.bottom + headRect.height
|
||||
bottom
|
||||
|
||||
isNodeVisible: (node) ->
|
||||
return false if d.hidden or !doc.contains node
|
||||
{height} = node.getBoundingClientRect()
|
||||
Header.getTopOf(node) + height >= 0 and Header.getBottomOf(node) + height >= 0
|
||||
|
||||
isHidden: ->
|
||||
{top} = Header.bar.getBoundingClientRect()
|
||||
if Conf['Bottom header']
|
||||
|
||||
@ -161,6 +161,7 @@ Index =
|
||||
|
||||
catalogNode: ->
|
||||
$.on @nodes.thumb.parentNode, 'click', Index.onClick
|
||||
|
||||
onClick: (e) ->
|
||||
return if e.button isnt 0
|
||||
thread = g.threads[@parentNode.dataset.fullID]
|
||||
@ -169,6 +170,7 @@ Index =
|
||||
else
|
||||
return
|
||||
e.preventDefault()
|
||||
|
||||
toggleHide: (thread) ->
|
||||
$.rm thread.catalogView.nodes.root
|
||||
if Index.showHiddenThreads
|
||||
@ -178,6 +180,7 @@ Index =
|
||||
else
|
||||
ThreadHiding.hide thread
|
||||
ThreadHiding.saveHiddenState thread
|
||||
|
||||
cycleSortType: ->
|
||||
types = [Index.selectSort.options...].filter (option) -> !option.disabled
|
||||
for type, i in types
|
||||
@ -193,15 +196,18 @@ Index =
|
||||
'Show'
|
||||
Index.sort()
|
||||
Index.buildIndex()
|
||||
|
||||
mode: ->
|
||||
mode = @value
|
||||
unless mode is 'catalog'
|
||||
Conf['Previous Index Mode'] = mode
|
||||
$.set 'Previous Index Mode', mode
|
||||
Index.pageLoad Index.pushState {mode}
|
||||
|
||||
sort: ->
|
||||
Index.sort()
|
||||
Index.buildIndex()
|
||||
|
||||
size: (e) ->
|
||||
if Conf['Index Mode'] isnt 'catalog'
|
||||
$.rmClass Index.root, 'catalog-small'
|
||||
@ -213,10 +219,12 @@ Index =
|
||||
$.addClass Index.root, 'catalog-large'
|
||||
$.rmClass Index.root, 'catalog-small'
|
||||
Index.buildIndex() if e
|
||||
|
||||
replies: ->
|
||||
Index.buildThreads()
|
||||
Index.sort()
|
||||
Index.buildIndex()
|
||||
|
||||
popstate: (e) ->
|
||||
if e?.state
|
||||
{search, mode} = e.state
|
||||
@ -236,6 +244,7 @@ Index =
|
||||
scroll: true
|
||||
if state.command
|
||||
Index[if Conf['Refreshed Navigation'] then 'update' else 'pageLoad'] state
|
||||
|
||||
pageNav: (e) ->
|
||||
return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0
|
||||
switch e.target.nodeName
|
||||
@ -249,6 +258,7 @@ Index =
|
||||
return if a.textContent is 'Catalog'
|
||||
e.preventDefault()
|
||||
Index.userPageNav +a.pathname.split('/')[2] or 1
|
||||
|
||||
frontPage: (e) ->
|
||||
return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0
|
||||
e.preventDefault()
|
||||
@ -262,12 +272,14 @@ Index =
|
||||
1
|
||||
else
|
||||
+window.location.pathname.split('/')[2] or 1
|
||||
|
||||
userPageNav: (page, noRefresh) ->
|
||||
state = Index.pushState {page, scroll: true}
|
||||
if Conf['Refreshed Navigation'] and !noRefresh
|
||||
Index.update state
|
||||
else
|
||||
Index.pageLoad state if state.page
|
||||
|
||||
pushState: (state) ->
|
||||
{pathname, hash} = location
|
||||
pageBeforeSearch = history.state?.oldpage
|
||||
@ -309,6 +321,7 @@ Index =
|
||||
oldpage: pageBeforeSearch
|
||||
, '', pathname + hash
|
||||
state
|
||||
|
||||
pageLoad: ({sort, search, mode, scroll}) ->
|
||||
if sort or search?
|
||||
Index.sort()
|
||||
@ -318,6 +331,7 @@ Index =
|
||||
Index.buildIndex()
|
||||
Index.setPage()
|
||||
Index.scrollToIndex() if scroll
|
||||
|
||||
applyMode: ->
|
||||
for mode in ['paged', 'infinite', 'all pages', 'catalog']
|
||||
$[if mode is Conf['Index Mode'] then 'addClass' else 'rmClass'] doc, "#{mode.replace /\ /g, '-'}-mode"
|
||||
@ -331,8 +345,10 @@ Index =
|
||||
Math.ceil Index.sortedNodes.length / Index.threadsNumPerPage
|
||||
else
|
||||
Index.pagesNum
|
||||
|
||||
getMaxPageNum: ->
|
||||
Math.max 1, Index.getPagesNum()
|
||||
|
||||
buildPagelist: ->
|
||||
pagesRoot = $ '.pages', Index.pagelist
|
||||
maxPageNum = Index.getMaxPageNum()
|
||||
@ -345,10 +361,12 @@ Index =
|
||||
nodes.push $.tn('['), a, $.tn '] '
|
||||
$.rmAll pagesRoot
|
||||
$.add pagesRoot, nodes
|
||||
|
||||
setPage: ->
|
||||
pageNum = Index.getCurrentPage()
|
||||
maxPageNum = Index.getMaxPageNum()
|
||||
pagesRoot = $ '.pages', Index.pagelist
|
||||
|
||||
# Previous/Next buttons
|
||||
prev = pagesRoot.previousSibling.firstChild
|
||||
next = pagesRoot.nextSibling.firstChild
|
||||
@ -358,12 +376,14 @@ Index =
|
||||
href = Math.min pageNum + 1, maxPageNum
|
||||
next.href = if href is 1 then './' else href
|
||||
next.firstChild.disabled = href is pageNum
|
||||
|
||||
# <strong> current page
|
||||
if strong = $ 'strong', pagesRoot
|
||||
return if +strong.textContent is pageNum
|
||||
$.replace strong, strong.firstChild
|
||||
else
|
||||
strong = $.el 'strong'
|
||||
|
||||
a = pagesRoot.children[pageNum - 1]
|
||||
$.before a, strong
|
||||
$.add strong, a
|
||||
|
||||
@ -33,10 +33,13 @@ Main =
|
||||
else # string or number
|
||||
Conf[parent] = obj
|
||||
return
|
||||
|
||||
flatten null, Config
|
||||
|
||||
for db in DataBoard.keys
|
||||
Conf[db] = boards: {}
|
||||
Conf['selectedArchives'] = {}
|
||||
|
||||
$.get Conf, (items) ->
|
||||
$.extend Conf, items
|
||||
$.asap (-> doc = d.documentElement), Main.initFeatures
|
||||
@ -71,7 +74,7 @@ Main =
|
||||
pathname = location.pathname.split '/'
|
||||
if pathname[2] isnt 'thread' or pathname.length > 4
|
||||
pathname[2] = 'thread'
|
||||
history.replaceState null, '', pathname.slice(0,4).join('/') + location.hash
|
||||
history.replaceState null, '', pathname[0...4].join('/') + location.hash
|
||||
|
||||
# c.time 'All initializations'
|
||||
for [name, feature] in Main.features
|
||||
|
||||
@ -73,11 +73,13 @@ Settings =
|
||||
delete Settings.dialog
|
||||
|
||||
sections: []
|
||||
|
||||
addSection: (title, open) ->
|
||||
if typeof title isnt 'string'
|
||||
{title, open} = title.detail
|
||||
hyphenatedTitle = title.toLowerCase().replace /\s+/g, '-'
|
||||
Settings.sections.push {title, hyphenatedTitle, open}
|
||||
|
||||
openSection: ->
|
||||
if selected = $ '.tab-selected', Settings.dialog
|
||||
$.rmClass selected, 'tab-selected'
|
||||
@ -142,12 +144,14 @@ Settings =
|
||||
localStorage.removeItem "4chan-hide-t-#{boardID}"
|
||||
$.delete ['hiddenThreads', 'hiddenPosts']
|
||||
$.after $('input[name="Stubs"]', section).parentNode.parentNode, div
|
||||
|
||||
export: ->
|
||||
# Make sure to export the most recent data.
|
||||
$.get Conf, (Conf) ->
|
||||
# XXX don't export archives.
|
||||
delete Conf['archives']
|
||||
Settings.downloadExport {version: g.VERSION, date: Date.now(), Conf}
|
||||
|
||||
downloadExport: (data) ->
|
||||
a = $.el 'a',
|
||||
download: "<%= meta.name %> v#{g.VERSION}-#{data.date}.json"
|
||||
@ -160,12 +164,14 @@ Settings =
|
||||
a.click()
|
||||
import: ->
|
||||
$('input', @parentNode).click()
|
||||
|
||||
onImport: ->
|
||||
return unless file = @files[0]
|
||||
output = $('.imp-exp-result')
|
||||
unless confirm 'Your current settings will be entirely overwritten, are you sure?'
|
||||
output.textContent = 'Import aborted.'
|
||||
return
|
||||
|
||||
reader = new FileReader()
|
||||
reader.onload = (e) ->
|
||||
try
|
||||
@ -176,6 +182,7 @@ Settings =
|
||||
output.textContent = 'Import failed due to an error.'
|
||||
c.error err.stack
|
||||
reader.readAsText file
|
||||
|
||||
loadSettings: (data) ->
|
||||
version = data.version.split '.'
|
||||
if version[0] is '2'
|
||||
@ -259,6 +266,7 @@ Settings =
|
||||
data.Conf['watchedThreads'] = boards: ThreadWatcher.convert data.Conf['WatchedThreads']
|
||||
delete data.Conf['WatchedThreads']
|
||||
$.clear -> $.set data.Conf
|
||||
|
||||
reset: ->
|
||||
if confirm 'Your current settings will be entirely wiped, are you sure?'
|
||||
$.clear -> window.location.reload() if confirm 'Reset successful. Reload now?'
|
||||
@ -326,9 +334,11 @@ Settings =
|
||||
|
||||
interval = $ 'input[name="Interval"]', section
|
||||
customCSS = $ 'input[name="Custom CSS"]', section
|
||||
|
||||
interval.value = Conf['Interval']
|
||||
customCSS.checked = Conf['Custom CSS']
|
||||
inputs['usercss'].disabled = !Conf['Custom CSS']
|
||||
|
||||
$.on interval, 'change', ThreadUpdater.cb.interval
|
||||
$.on customCSS, 'change', Settings.togglecss
|
||||
$.on $('#apply-css', section), 'click', Settings.usercss
|
||||
@ -421,10 +431,13 @@ Settings =
|
||||
|
||||
boardnav: ->
|
||||
Header.generateBoardList @value
|
||||
|
||||
time: ->
|
||||
@nextElementSibling.textContent = Time.format @value, new Date()
|
||||
|
||||
backlink: ->
|
||||
@nextElementSibling.textContent = @value.replace /%(?:id|%)/g, (x) -> {'%id': '123456789', '%%': '%'}[x]
|
||||
|
||||
fileInfo: ->
|
||||
data =
|
||||
isReply: true
|
||||
@ -437,6 +450,7 @@ Settings =
|
||||
isImage: true
|
||||
isSpoiler: true
|
||||
FileInfo.format @value, data, @nextElementSibling
|
||||
|
||||
favicon: ->
|
||||
Favicon.switch()
|
||||
Unread.update() if g.VIEW is 'thread' and Conf['Unread Favicon']
|
||||
@ -445,12 +459,14 @@ Settings =
|
||||
img[1].src = Favicon.unreadSFW
|
||||
img[2].src = Favicon.unreadNSFW
|
||||
img[3].src = Favicon.unreadDead
|
||||
|
||||
togglecss: ->
|
||||
if $('textarea[name=usercss]', $.x 'ancestor::fieldset[1]', @).disabled = !@checked
|
||||
CustomCSS.rmStyle()
|
||||
else
|
||||
CustomCSS.addStyle()
|
||||
$.cb.checked.call @
|
||||
|
||||
usercss: ->
|
||||
CustomCSS.update()
|
||||
|
||||
@ -475,6 +491,7 @@ Settings =
|
||||
for key, val of items
|
||||
inputs[key].value = val
|
||||
return
|
||||
|
||||
keybind: (e) ->
|
||||
return if e.keyCode is 9 # tab
|
||||
e.preventDefault()
|
||||
|
||||
@ -328,6 +328,7 @@ UI = do ->
|
||||
if $.x 'ancestor::div[contains(@class,"inline")][1]', root
|
||||
$.on d, 'keydown', o.hoverend
|
||||
$.on root, 'mousemove', o.hover
|
||||
|
||||
<% if (type === 'userscript') { %>
|
||||
# Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=674955
|
||||
o.workaround = (e) -> o.hoverend(e) unless root.contains e.target
|
||||
|
||||
@ -443,6 +443,7 @@ do ->
|
||||
delete $.oldValue[key]
|
||||
cb undefined, key
|
||||
$.on window, 'storage', ({key}) -> onChange key
|
||||
|
||||
$.forceSync = (key) ->
|
||||
# Storage events don't work across origins
|
||||
# e.g. http://boards.4chan.org and https://boards.4chan.org
|
||||
@ -482,6 +483,7 @@ $.set = do ->
|
||||
$.oldValue[key] = val
|
||||
# for `storage` events
|
||||
localStorage.setItem key, val
|
||||
|
||||
(keys, val) ->
|
||||
if typeof keys is 'string'
|
||||
set keys, val
|
||||
|
||||
@ -62,7 +62,7 @@ class RandomAccessList
|
||||
|
||||
shift: ->
|
||||
@rm @first.ID
|
||||
|
||||
|
||||
order: ->
|
||||
order = [item = @first]
|
||||
order.push item while item = item.next
|
||||
|
||||
@ -28,6 +28,7 @@ class Thread
|
||||
icon.title = "This thread is on page #{pageNum} in the original index."
|
||||
icon.textContent = "[#{pageNum}]"
|
||||
@catalogView.nodes.pageCount.textContent = pageNum if @catalogView
|
||||
|
||||
setCount: (type, count, reachedLimit) ->
|
||||
return unless @catalogView
|
||||
el = @catalogView.nodes["#{type}Count"]
|
||||
@ -47,6 +48,7 @@ class Thread
|
||||
typeLC = type.toLowerCase()
|
||||
icon = $ ".#{typeLC}Icon", @OP.nodes.info
|
||||
return if !!icon is status
|
||||
|
||||
unless status
|
||||
$.rm icon.previousSibling
|
||||
$.rm icon
|
||||
@ -57,6 +59,7 @@ class Thread
|
||||
alt: type
|
||||
title: type
|
||||
className: "#{typeLC}Icon retina"
|
||||
|
||||
root = if type isnt 'Sticky' and @isSticky
|
||||
$ '.stickyIcon', @OP.nodes.info
|
||||
else
|
||||
|
||||
@ -78,6 +78,7 @@ Gallery =
|
||||
|
||||
$.on d, 'keydown', cb.keybinds
|
||||
$.off d, 'keydown', Keybinds.keydown if Conf['Keybinds']
|
||||
|
||||
for file in $$ '.post .file' when !$ '.fileDeletedRes, .fileDeleted', file
|
||||
post = Get.postFromNode file
|
||||
Gallery.generateThumb post
|
||||
@ -87,6 +88,7 @@ Gallery =
|
||||
if Header.getTopOf(candidate) + candidate.getBoundingClientRect().height >= 0
|
||||
image = candidate
|
||||
$.addClass doc, 'gallery-open'
|
||||
|
||||
$.add d.body, dialog
|
||||
|
||||
nodes.thumbs.scrollTop = 0
|
||||
@ -103,6 +105,7 @@ Gallery =
|
||||
return if post.isClone or post.isHidden
|
||||
return unless post.file and (post.file.isImage or post.file.isVideo or Conf['PDF in Gallery'])
|
||||
return if Gallery.fullIDs[post.fullID]
|
||||
|
||||
Gallery.fullIDs[post.fullID] = true
|
||||
|
||||
thumb = $.el 'a',
|
||||
@ -110,13 +113,14 @@ Gallery =
|
||||
href: post.file.URL
|
||||
target: '_blank'
|
||||
title: post.file.name
|
||||
thumb.dataset.id = Gallery.images.length
|
||||
|
||||
thumb.dataset.id = Gallery.images.length
|
||||
thumb.dataset.post = post.fullID
|
||||
|
||||
thumbImg = post.file.thumb.cloneNode false
|
||||
thumbImg.style.cssText = ''
|
||||
$.add thumb, thumbImg
|
||||
|
||||
|
||||
$.on thumb, 'click', Gallery.cb.open
|
||||
|
||||
Gallery.images.push thumb
|
||||
@ -135,6 +139,7 @@ Gallery =
|
||||
elType = 'img'
|
||||
elType = 'video' if /\.webm$/.test(thumb.href)
|
||||
elType = 'iframe' if /\.pdf$/.test(thumb.href)
|
||||
|
||||
$[if elType is 'iframe' then 'addClass' else 'rmClass'] doc, 'gal-pdf'
|
||||
file = $.el elType,
|
||||
title: name.download = name.textContent = thumb.title
|
||||
@ -142,7 +147,7 @@ Gallery =
|
||||
Gallery.error file, thumb
|
||||
file.src = name.href = thumb.href
|
||||
|
||||
$.extend file.dataset, thumb.dataset
|
||||
$.extend file.dataset, thumb.dataset
|
||||
nodes.current.pause?() unless nodes.current.error
|
||||
$.replace nodes.current, file
|
||||
if elType is 'video'
|
||||
@ -242,10 +247,12 @@ Gallery =
|
||||
Gallery.cb.open.call(
|
||||
Gallery.images[+Gallery.nodes.current.dataset.id + 1] or Gallery.images[0]
|
||||
)
|
||||
|
||||
click: (e) ->
|
||||
return if ImageCommon.onControls e
|
||||
e.preventDefault()
|
||||
Gallery.cb.advance()
|
||||
|
||||
advance: -> if Gallery.nodes.current.paused then Gallery.nodes.current.play() else Gallery.cb.next()
|
||||
toggle: -> (if Gallery.nodes then Gallery.cb.close else Gallery.build)()
|
||||
blank: (e) -> Gallery.cb.close() if e.target is @
|
||||
|
||||
@ -7,6 +7,7 @@ ImageExpand =
|
||||
textContent: 'EAI'
|
||||
title: 'Expand All Images'
|
||||
href: 'javascript:;'
|
||||
|
||||
$.on @EAI, 'click', @cb.toggleAll
|
||||
Header.addShortcut @EAI, 3
|
||||
$.on d, 'scroll visibilitychange', @cb.playVideos
|
||||
@ -18,14 +19,17 @@ ImageExpand =
|
||||
node: ->
|
||||
return unless @file and (@file.isImage or @file.isVideo)
|
||||
$.on @file.thumb.parentNode, 'click', ImageExpand.cb.toggle
|
||||
|
||||
if @isClone
|
||||
if @file.isExpanding
|
||||
# If we clone a post where the image is still loading,
|
||||
# make it loading in the clone too.
|
||||
ImageExpand.contract @
|
||||
ImageExpand.expand @
|
||||
|
||||
else if @file.isExpanded and @file.isVideo
|
||||
ImageExpand.setupVideo @, !@origin.file.fullImage?.paused or @origin.file.wasPlaying, @file.fullImage.controls
|
||||
|
||||
else if ImageExpand.on and !@isHidden and !@isFetchedQuote and
|
||||
(Conf['Expand spoilers'] or !@file.isSpoiler) and
|
||||
(Conf['Expand videos'] or !@file.isVideo)
|
||||
@ -247,7 +251,7 @@ ImageExpand =
|
||||
|
||||
el = $.el 'span',
|
||||
textContent: 'Image Expansion'
|
||||
className: 'image-expansion-link'
|
||||
className: 'image-expansion-link'
|
||||
|
||||
{createSubEntry} = ImageExpand.menu
|
||||
subEntries = []
|
||||
|
||||
@ -9,13 +9,16 @@ ImageHover =
|
||||
CatalogThread.callbacks.push
|
||||
name: 'Catalog Image Hover'
|
||||
cb: @catalogNode
|
||||
|
||||
node: ->
|
||||
return unless @file and (@file.isImage or @file.isVideo)
|
||||
$.on @file.thumb, 'mouseover', ImageHover.mouseover @
|
||||
|
||||
catalogNode: ->
|
||||
{file} = @thread.OP
|
||||
return unless file and (file.isImage or file.isVideo)
|
||||
$.on @nodes.thumb, 'mouseover', ImageHover.mouseover @thread.OP
|
||||
|
||||
mouseover: (post) -> (e) ->
|
||||
return unless doc.contains @
|
||||
{file} = post
|
||||
@ -30,6 +33,7 @@ ImageHover =
|
||||
el.dataset.fullID = post.fullID
|
||||
$.on el, 'error', error
|
||||
el.src = file.URL
|
||||
|
||||
if Conf['Restart when Opened']
|
||||
ImageCommon.rewind el
|
||||
ImageCommon.rewind @
|
||||
@ -46,7 +50,7 @@ ImageHover =
|
||||
maxWidth = Math.max left, doc.clientWidth - right
|
||||
maxHeight = doc.clientHeight - padding
|
||||
scale = Math.min 1, maxWidth / width, maxHeight / height
|
||||
el.style.maxWidth = "#{scale * width}px"
|
||||
el.style.maxWidth = "#{scale * width}px"
|
||||
el.style.maxHeight = "#{scale * height}px"
|
||||
UI.hover
|
||||
root: @
|
||||
@ -62,6 +66,7 @@ ImageHover =
|
||||
el.pause() if isVideo
|
||||
$.rm el
|
||||
el.removeAttribute 'style'
|
||||
|
||||
error: (post) -> ->
|
||||
return if ImageCommon.decodeError @, post
|
||||
ImageCommon.error @, post, 3 * $.SECOND, (URL) =>
|
||||
|
||||
@ -75,6 +75,7 @@ ImageLoader =
|
||||
if !chrome?
|
||||
$.on thumb, 'loadeddata', -> @removeAttribute 'poster'
|
||||
return
|
||||
|
||||
el = $.el if isImage then 'img' else 'video'
|
||||
if replace and isImage
|
||||
$.on el, 'load', ->
|
||||
|
||||
@ -40,7 +40,7 @@ Sauce =
|
||||
type
|
||||
ext = post.file.URL.match(/\.([^\.]*)$/)?[1] or ''
|
||||
return null unless !parts['boards'] or post.board.ID in parts['boards'].split ','
|
||||
return null unless !parts['types'] or ext in parts['types'].split ','
|
||||
return null unless !parts['types'] or ext in parts['types'].split ','
|
||||
a = Sauce.link.cloneNode true
|
||||
a.href = parts['url']
|
||||
a.textContent = parts['text']
|
||||
@ -49,8 +49,7 @@ Sauce =
|
||||
node: ->
|
||||
return if @isClone or !@file
|
||||
nodes = []
|
||||
for link in Sauce.links
|
||||
if node = Sauce.createSauceLink link, @
|
||||
# \u00A0 is nbsp
|
||||
nodes.push $.tn('\u00A0'), node
|
||||
for link in Sauce.links when node = Sauce.createSauceLink link, @
|
||||
# \u00A0 is nbsp
|
||||
nodes.push $.tn('\u00A0'), node
|
||||
$.add @file.text, nodes
|
||||
|
||||
@ -5,12 +5,14 @@ Menu =
|
||||
@button = $.el 'a',
|
||||
className: 'menu-button'
|
||||
href: 'javascript:;'
|
||||
|
||||
$.extend @button, <%= html('<i class="fa fa-angle-down"></i>') %>
|
||||
|
||||
@menu = new UI.Menu 'post'
|
||||
Post.callbacks.push
|
||||
name: 'Menu'
|
||||
cb: @node
|
||||
|
||||
CatalogThread.callbacks.push
|
||||
name: 'Menu'
|
||||
cb: @catalogNode
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
PSAHiding =
|
||||
init: ->
|
||||
return if !Conf['Announcement Hiding']
|
||||
return unless Conf['Announcement Hiding']
|
||||
$.addClass doc, 'hide-announcement'
|
||||
$.one d, '4chanXInitFinished', @setup
|
||||
|
||||
@ -24,7 +24,9 @@ PSAHiding =
|
||||
PSAHiding.btn = btn = $.el 'span',
|
||||
title: 'Mark announcement as read and hide.'
|
||||
className: 'hide-announcement'
|
||||
|
||||
$.extend btn, <%= html('[<a href="javascript:;">Dismiss</a>]') %>
|
||||
|
||||
$.on btn, 'click', PSAHiding.toggle
|
||||
|
||||
$.get 'hiddenPSA', 0, ({hiddenPSA}) ->
|
||||
@ -33,6 +35,7 @@ PSAHiding =
|
||||
$.rmClass doc, 'hide-announcement'
|
||||
|
||||
$.sync 'hiddenPSA', PSAHiding.sync
|
||||
|
||||
toggle: (e) ->
|
||||
if $.hasClass @, 'hide-announcement'
|
||||
UTC = +$.id('globalMessage').dataset.utc
|
||||
@ -41,6 +44,7 @@ PSAHiding =
|
||||
$.event 'CloseMenu'
|
||||
$.delete 'hiddenPSA'
|
||||
PSAHiding.sync UTC
|
||||
|
||||
sync: (UTC) ->
|
||||
{psa} = PSAHiding
|
||||
PSAHiding.hidden = PSAHiding.btn.hidden = UTC? and UTC >= +psa.dataset.utc
|
||||
|
||||
@ -55,7 +55,7 @@ Banner =
|
||||
i = Math.floor(Banner.choices.length * Math.random())
|
||||
banner = Banner.choices.splice i, 1
|
||||
$('img', @parentNode).src = "//s.4cdn.org/image/title/#{banner}"
|
||||
|
||||
|
||||
click: (e) ->
|
||||
if e.ctrlKey or e.metaKey
|
||||
@contentEditable = true
|
||||
|
||||
@ -2,12 +2,15 @@ CustomCSS =
|
||||
init: ->
|
||||
return unless Conf['Custom CSS']
|
||||
@addStyle()
|
||||
|
||||
addStyle: ->
|
||||
@style = $.addStyle Conf['usercss'], 'custom-css', -> $.id 'fourchanx-css'
|
||||
|
||||
rmStyle: ->
|
||||
if @style
|
||||
$.rm @style
|
||||
delete @style
|
||||
|
||||
update: ->
|
||||
unless @style
|
||||
@addStyle()
|
||||
|
||||
@ -12,10 +12,13 @@ ExpandComment =
|
||||
node: ->
|
||||
if a = $ '.abbr > a:not([onclick])', @nodes.comment
|
||||
$.on a, 'click', ExpandComment.cb
|
||||
|
||||
callbacks: []
|
||||
|
||||
cb: (e) ->
|
||||
e.preventDefault()
|
||||
ExpandComment.expand Get.postFromNode @
|
||||
|
||||
expand: (post) ->
|
||||
if post.nodes.longComment and !post.nodes.longComment.parentNode
|
||||
$.replace post.nodes.shortComment, post.nodes.longComment
|
||||
@ -24,12 +27,14 @@ ExpandComment =
|
||||
return unless a = $ '.abbr > a', post.nodes.comment
|
||||
a.textContent = "Post No.#{post} Loading..."
|
||||
$.cache "//a.4cdn.org#{a.pathname.split('/').splice(0,4).join('/')}.json", -> ExpandComment.parse @, a, post
|
||||
|
||||
contract: (post) ->
|
||||
return unless post.nodes.shortComment
|
||||
a = $ '.abbr > a', post.nodes.shortComment
|
||||
a.textContent = 'here'
|
||||
$.replace post.nodes.longComment, post.nodes.shortComment
|
||||
post.nodes.comment = post.nodes.shortComment
|
||||
|
||||
parse: (req, a, post) ->
|
||||
{status} = req
|
||||
unless status in [200, 304]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
Fourchan =
|
||||
init: ->
|
||||
return if g.VIEW not in ['index', 'thread']
|
||||
return unless g.VIEW in ['index', 'thread']
|
||||
|
||||
if g.BOARD.ID is 'g'
|
||||
$.globalEval '''
|
||||
|
||||
@ -12,6 +12,8 @@ IDColor =
|
||||
span = $ '.hand', @nodes.uniqueID
|
||||
return unless span and span.nodeName is 'SPAN'
|
||||
rgb = IDColor.compute uid
|
||||
|
||||
# Style the damn node.
|
||||
{style} = span
|
||||
style.color = rgb[3]
|
||||
style.backgroundColor = "rgb(#{rgb[0]},#{rgb[1]},#{rgb[2]})"
|
||||
@ -21,16 +23,24 @@ IDColor =
|
||||
compute: (uid) ->
|
||||
return IDColor.ids[uid] if IDColor.ids[uid]
|
||||
|
||||
# Convert chars to integers, bitshift and math to create a larger integer
|
||||
# Create a nice string of binary
|
||||
hash = IDColor.hash uid
|
||||
|
||||
# Convert binary string to numerical values with bitshift and '&' truncation.
|
||||
rgb = [
|
||||
(hash >> 24) & 0xFF
|
||||
(hash >> 16) & 0xFF
|
||||
(hash >> 8) & 0xFF
|
||||
]
|
||||
|
||||
# Weight color luminance values, assign a font color that should be readable.
|
||||
rgb[3] = if (rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114) > 125
|
||||
'#000'
|
||||
else
|
||||
'#fff'
|
||||
|
||||
# Cache.
|
||||
@ids[uid] = rgb
|
||||
|
||||
hash: (uid) ->
|
||||
@ -38,4 +48,4 @@ IDColor =
|
||||
i = 0
|
||||
while i < 8
|
||||
msg = (msg << 5) - msg + uid.charCodeAt i++
|
||||
msg
|
||||
msg
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
IDHighlight =
|
||||
init: ->
|
||||
return if g.VIEW not in ['index', 'thread']
|
||||
return unless g.VIEW in ['index', 'thread']
|
||||
|
||||
Post.callbacks.push
|
||||
name: 'Highlight by User ID'
|
||||
|
||||
@ -56,16 +56,16 @@ Keybinds =
|
||||
else
|
||||
return
|
||||
when Conf['Spoiler tags']
|
||||
return if target.nodeName isnt 'TEXTAREA'
|
||||
return unless target.nodeName is 'TEXTAREA'
|
||||
Keybinds.tags 'spoiler', target
|
||||
when Conf['Code tags']
|
||||
return if target.nodeName isnt 'TEXTAREA'
|
||||
return unless target.nodeName is 'TEXTAREA'
|
||||
Keybinds.tags 'code', target
|
||||
when Conf['Eqn tags']
|
||||
return if target.nodeName isnt 'TEXTAREA'
|
||||
return unless target.nodeName is 'TEXTAREA'
|
||||
Keybinds.tags 'eqn', target
|
||||
when Conf['Math tags']
|
||||
return if target.nodeName isnt 'TEXTAREA'
|
||||
return unless target.nodeName is 'TEXTAREA'
|
||||
Keybinds.tags 'math', target
|
||||
when Conf['Toggle sage']
|
||||
return unless QR.nodes and !QR.nodes.el.hidden
|
||||
|
||||
@ -62,7 +62,9 @@ Nav =
|
||||
# Add extra space to the end of the page if necessary so that all threads can be selected by keybinds.
|
||||
extra = Header.getTopOf(thread) + doc.clientHeight - d.body.getBoundingClientRect().bottom
|
||||
d.body.style.marginBottom = "#{extra}px" if extra > 0
|
||||
|
||||
Header.scrollTo thread
|
||||
|
||||
if extra > 0 and !Nav.haveExtra
|
||||
Nav.haveExtra = true
|
||||
$.on d, 'scroll', Nav.removeExtra
|
||||
|
||||
@ -8,9 +8,11 @@ RemoveSpoilers =
|
||||
Post.callbacks.push
|
||||
name: 'Reveal Spoilers'
|
||||
cb: @node
|
||||
|
||||
CatalogThread.callbacks.push
|
||||
name: 'Reveal Spoilers'
|
||||
cb: @node
|
||||
|
||||
if g.VIEW is 'archive'
|
||||
$.ready -> RemoveSpoilers.unspoiler $.id 'arc-list'
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ Time =
|
||||
Post.callbacks.push
|
||||
name: 'Time Formatting'
|
||||
cb: @node
|
||||
|
||||
node: ->
|
||||
return if @isClone
|
||||
@nodes.date.textContent = Time.format Conf['time'], @info.date
|
||||
@ -14,6 +15,7 @@ Time =
|
||||
Time.formatters[c].call(date)
|
||||
else
|
||||
s
|
||||
|
||||
day: [
|
||||
'Sunday'
|
||||
'Monday'
|
||||
@ -23,6 +25,7 @@ Time =
|
||||
'Friday'
|
||||
'Saturday'
|
||||
]
|
||||
|
||||
month: [
|
||||
'January'
|
||||
'February'
|
||||
@ -37,7 +40,9 @@ Time =
|
||||
'November'
|
||||
'December'
|
||||
]
|
||||
|
||||
zeroPad: (n) -> if n < 10 then "0#{n}" else n
|
||||
|
||||
formatters:
|
||||
a: -> Time.day[@getDay()][...3]
|
||||
A: -> Time.day[@getDay()]
|
||||
|
||||
@ -18,6 +18,7 @@ ThreadStats =
|
||||
$.extend sc, statsHTML
|
||||
$.ready ->
|
||||
Header.addShortcut sc
|
||||
|
||||
else
|
||||
@dialog = sc = UI.dialog 'thread-stats', 'bottom: 0px; right: 0px;',
|
||||
<%= html('<div class="move" title="${statsTitle}">&{statsHTML}</div>') %>
|
||||
|
||||
@ -8,7 +8,7 @@ ThreadUpdater =
|
||||
$.extend sc, <%= html('<span id="update-status"></span><span id="update-timer" title="Update now"></span>') %>
|
||||
$.ready ->
|
||||
Header.addShortcut sc
|
||||
else
|
||||
else
|
||||
@dialog = sc = UI.dialog 'updater', 'bottom: 0px; left: 0px;',
|
||||
<%= html('<div class="move"></div><span id="update-status"></span><span id="update-timer" title="Update now"></span>') %>
|
||||
$.addClass doc, 'float'
|
||||
@ -176,7 +176,7 @@ ThreadUpdater =
|
||||
|
||||
setInterval: ->
|
||||
i = ThreadUpdater.interval + 1
|
||||
|
||||
|
||||
if Conf['Optional Increase']
|
||||
# Lower the max refresh rate limit on visible tabs.
|
||||
cur = ThreadUpdater.outdateCount or 1
|
||||
|
||||
@ -11,10 +11,10 @@ ThreadWatcher =
|
||||
|
||||
@db = new DataBoard 'watchedThreads', @refresh, true
|
||||
@dialog = UI.dialog 'thread-watcher', 'top: 50px; left: 0px;', <%= importHTML('Monitoring/ThreadWatcher') %>
|
||||
|
||||
@status = $ '#watcher-status', @dialog
|
||||
@list = @dialog.lastElementChild
|
||||
@refreshButton = $ '.move > .refresh', @dialog
|
||||
|
||||
@unreaddb = Unread.db or new DataBoard 'lastReadPosts'
|
||||
|
||||
$.on d, 'QRPostSuccessful', @cb.post
|
||||
@ -23,6 +23,7 @@ ThreadWatcher =
|
||||
$.on $('.move > .close', @dialog), 'click', @toggleWatcher
|
||||
|
||||
$.on d, '4chanXInitFinished', @ready
|
||||
|
||||
switch g.VIEW
|
||||
when 'index'
|
||||
$.on d, 'IndexRefresh', @cb.onIndexRefresh
|
||||
@ -147,6 +148,7 @@ ThreadWatcher =
|
||||
fetchCount:
|
||||
fetched: 0
|
||||
fetching: 0
|
||||
|
||||
fetchAuto: ->
|
||||
clearTimeout ThreadWatcher.timeout
|
||||
return unless Conf['Auto Update Thread Watcher']
|
||||
@ -158,6 +160,7 @@ ThreadWatcher =
|
||||
ThreadWatcher.fetchAllStatus()
|
||||
db.save()
|
||||
ThreadWatcher.timeout = setTimeout ThreadWatcher.fetchAuto, interval
|
||||
|
||||
fetchAllStatus: ->
|
||||
ThreadWatcher.db.forceSync()
|
||||
ThreadWatcher.unreaddb.forceSync()
|
||||
@ -166,6 +169,7 @@ ThreadWatcher =
|
||||
for thread in threads
|
||||
ThreadWatcher.fetchStatus thread
|
||||
return
|
||||
|
||||
fetchStatus: (thread) ->
|
||||
{boardID, threadID, data} = thread
|
||||
return if data.isDead and !Conf['Show Unread Count']
|
||||
@ -177,6 +181,7 @@ ThreadWatcher =
|
||||
$.ajax "//a.4cdn.org/#{boardID}/thread/#{threadID}.json",
|
||||
onloadend: ->
|
||||
ThreadWatcher.parseStatus.call @, thread
|
||||
|
||||
parseStatus: ({boardID, threadID, data}) ->
|
||||
{fetchCount} = ThreadWatcher
|
||||
fetchCount.fetched++
|
||||
@ -233,6 +238,7 @@ ThreadWatcher =
|
||||
delete data.unread
|
||||
delete data.quotingYou
|
||||
ThreadWatcher.db.set {boardID, threadID, val: data}
|
||||
|
||||
ThreadWatcher.refresh()
|
||||
|
||||
getAll: ->
|
||||
@ -269,8 +275,8 @@ ThreadWatcher =
|
||||
div = $.el 'div'
|
||||
fullID = "#{boardID}.#{threadID}"
|
||||
div.dataset.fullID = fullID
|
||||
$.addClass div, 'current' if g.VIEW is 'thread' and fullID is "#{g.BOARD}.#{g.THREADID}"
|
||||
$.addClass div, 'dead-thread' if data.isDead
|
||||
$.addClass div, 'current' if g.VIEW is 'thread' and fullID is "#{g.BOARD}.#{g.THREADID}"
|
||||
$.addClass div, 'dead-thread' if data.isDead
|
||||
if Conf['Show Unread Count']
|
||||
$.addClass div, 'replies-unread' if data.unread
|
||||
$.addClass div, 'replies-quoting-you' if data.quotingYou
|
||||
@ -336,6 +342,7 @@ ThreadWatcher =
|
||||
ThreadWatcher.rm boardID, threadID
|
||||
else
|
||||
ThreadWatcher.add thread
|
||||
|
||||
add: (thread) ->
|
||||
data = {}
|
||||
boardID = thread.board.ID
|
||||
@ -350,6 +357,7 @@ ThreadWatcher =
|
||||
ThreadWatcher.refresh()
|
||||
if Conf['Show Unread Count']
|
||||
ThreadWatcher.fetchStatus {boardID, threadID, data}
|
||||
|
||||
rm: (boardID, threadID) ->
|
||||
ThreadWatcher.db.delete {boardID, threadID}
|
||||
ThreadWatcher.refresh()
|
||||
@ -423,6 +431,7 @@ ThreadWatcher =
|
||||
@refreshers.push refresh.bind entry if refresh
|
||||
@menu.addEntry entry
|
||||
return
|
||||
|
||||
createSubEntry: (name, desc) ->
|
||||
entry =
|
||||
type: 'thread watcher'
|
||||
@ -430,6 +439,6 @@ ThreadWatcher =
|
||||
entry.el.title = desc
|
||||
input = entry.el.firstElementChild
|
||||
$.on input, 'change', $.cb.checked
|
||||
$.on input, 'change', ThreadWatcher.refresh if name in ['Current Board', 'Show Unread Count']
|
||||
$.on input, 'change', ThreadWatcher.refresh if name in ['Current Board', 'Show Unread Count']
|
||||
$.on input, 'change', ThreadWatcher.fetchAuto if name in ['Show Unread Count', 'Auto Update Thread Watcher']
|
||||
entry
|
||||
|
||||
@ -20,6 +20,7 @@ Unread =
|
||||
Thread.callbacks.push
|
||||
name: 'Unread'
|
||||
cb: @node
|
||||
|
||||
Post.callbacks.push
|
||||
name: 'Unread'
|
||||
cb: @addPost
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
Captcha.noscript =
|
||||
lifetime: 2 * $.MINUTE
|
||||
lifetime: 2 * $.MINUTE
|
||||
iframeURL: '//www.google.com/recaptcha/api/fallback?k=<%= meta.recaptchaKey %>'
|
||||
|
||||
init: ->
|
||||
|
||||
@ -113,6 +113,7 @@ QR =
|
||||
return
|
||||
if Conf['QR Shortcut']
|
||||
$.rmClass $('.qr-shortcut'), 'disabled'
|
||||
|
||||
close: ->
|
||||
if QR.req
|
||||
QR.abort()
|
||||
@ -129,6 +130,7 @@ QR =
|
||||
QR.cooldown.auto = false
|
||||
QR.status()
|
||||
QR.captcha.destroy()
|
||||
|
||||
focus: ->
|
||||
$.queueTask ->
|
||||
return unless QR.nodes
|
||||
@ -142,18 +144,22 @@ QR =
|
||||
$.on d, 'scroll', QR.scrollLock
|
||||
else
|
||||
$.off d, 'scroll', QR.scrollLock
|
||||
|
||||
scrollLock: (e) ->
|
||||
if d.activeElement and QR.nodes.el.contains(d.activeElement) and d.activeElement.nodeName is 'IFRAME'
|
||||
window.scroll window.scrollX, QR.scrollY
|
||||
else
|
||||
$.off d, 'scroll', QR.scrollLock
|
||||
|
||||
hide: ->
|
||||
d.activeElement.blur()
|
||||
$.addClass QR.nodes.el, 'autohide'
|
||||
QR.nodes.autohide.checked = true
|
||||
|
||||
unhide: ->
|
||||
$.rmClass QR.nodes.el, 'autohide'
|
||||
QR.nodes.autohide.checked = false
|
||||
|
||||
toggleHide: ->
|
||||
if @checked
|
||||
QR.hide()
|
||||
@ -195,6 +201,7 @@ QR =
|
||||
<% } %>
|
||||
|
||||
notifications: []
|
||||
|
||||
cleanNotifications: ->
|
||||
for notification in QR.notifications
|
||||
notification.close()
|
||||
@ -449,7 +456,8 @@ QR =
|
||||
|
||||
dialog: ->
|
||||
QR.nodes = nodes =
|
||||
el: dialog = UI.dialog 'qr', 'top: 50px; right: 0px;', <%= importHTML('Features/QuickReply') %>
|
||||
el: dialog = UI.dialog 'qr', 'top: 50px; right: 0px;',
|
||||
<%= importHTML('Features/QuickReply') %>
|
||||
|
||||
setNode = (name, query) ->
|
||||
nodes[name] = $ query, dialog
|
||||
@ -478,7 +486,7 @@ QR =
|
||||
setNode 'spoilerPar', '#qr-spoiler-label'
|
||||
setNode 'status', '[type=submit]'
|
||||
setNode 'fileInput', '[type=file]'
|
||||
|
||||
|
||||
rules = $('ul.rules').textContent.trim()
|
||||
match_min = rules.match(/.+smaller than (\d+)x(\d+).+/)
|
||||
match_max = rules.match(/.+greater than (\d+)x(\d+).+/)
|
||||
@ -516,7 +524,9 @@ QR =
|
||||
nodes.spoiler.parentElement.hidden = true
|
||||
|
||||
if g.BOARD.ID is 'f' and g.VIEW isnt 'thread'
|
||||
nodes.flashTag = $.el 'select', name: 'filetag'
|
||||
nodes.flashTag = $.el 'select',
|
||||
name: 'filetag'
|
||||
|
||||
$.extend nodes.flashTag, <%= html(
|
||||
'<option value="0">Hentai</option>' +
|
||||
'<option value="6">Porn</option>' +
|
||||
@ -526,6 +536,7 @@ QR =
|
||||
'<option value="5">Loop</option>' +
|
||||
'<option value="4" selected>Other</option>'
|
||||
) %>
|
||||
|
||||
nodes.flashTag.dataset.default = '4'
|
||||
$.add nodes.form, nodes.flashTag
|
||||
|
||||
@ -537,8 +548,8 @@ QR =
|
||||
$.on nodes.urlButton, 'click', QR.handleUrl
|
||||
$.on nodes.addPost, 'click', -> new QR.post true
|
||||
$.on nodes.form, 'submit', QR.submit
|
||||
$.on nodes.fileRM, 'click', -> QR.selected.rmFile()
|
||||
$.on nodes.fileExtras, 'click', (e) -> e.stopPropagation()
|
||||
$.on nodes.fileRM, 'click', -> QR.selected.rmFile()
|
||||
$.on nodes.fileExtras, 'click', (e) -> e.stopPropagation()
|
||||
$.on nodes.spoiler, 'change', -> QR.selected.nodes.spoiler.click()
|
||||
$.on nodes.fileInput, 'change', QR.handleFiles
|
||||
|
||||
@ -566,6 +577,7 @@ QR =
|
||||
QR.status()
|
||||
QR.cooldown.init()
|
||||
QR.captcha.init()
|
||||
|
||||
$.add d.body, dialog
|
||||
QR.captcha.setup()
|
||||
|
||||
@ -798,7 +810,7 @@ QR =
|
||||
QR.close()
|
||||
else
|
||||
post.rm()
|
||||
QR.captcha.setup (d.activeElement is QR.nodes.status)
|
||||
QR.captcha.setup(d.activeElement is QR.nodes.status)
|
||||
|
||||
QR.cooldown.add req.uploadEndTime, threadID, postID
|
||||
|
||||
@ -806,6 +818,7 @@ QR =
|
||||
"#{window.location.origin}/#{g.BOARD}/thread/#{threadID}"
|
||||
else if g.VIEW is 'index' and !QR.cooldown.auto and Conf['Open Post in New Tab'] # replying from the index
|
||||
"#{window.location.origin}/#{g.BOARD}/thread/#{threadID}#p#{postID}"
|
||||
|
||||
if URL
|
||||
if Conf['Open Post in New Tab'] or postsCount
|
||||
$.open URL
|
||||
|
||||
@ -71,6 +71,7 @@ QR.post = class
|
||||
(QR.posts[index-1] or QR.posts[index+1]).select()
|
||||
QR.posts.splice index, 1
|
||||
QR.status()
|
||||
|
||||
delete: ->
|
||||
$.rm @nodes.el
|
||||
URL.revokeObjectURL @URL
|
||||
@ -103,10 +104,13 @@ QR.post = class
|
||||
|
||||
load: ->
|
||||
# Load this post's values.
|
||||
|
||||
for name in ['thread', 'name', 'email', 'sub', 'com', 'filename']
|
||||
continue unless node = QR.nodes[name]
|
||||
node.value = @[name] or node.dataset.default or null
|
||||
|
||||
(if @thread isnt 'new' then $.addClass else $.rmClass) QR.nodes.el, 'reply-to-thread'
|
||||
|
||||
@showFileData()
|
||||
QR.characterCount()
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ QuoteThreading =
|
||||
@enabled = true
|
||||
@controls = $.el 'span',
|
||||
<%= html('<label><input id="threadingControl" type="checkbox" checked> Threading</label>') %>
|
||||
|
||||
@threadNewLink = $.el 'span',
|
||||
className: 'brackets-wrap threadnewlink'
|
||||
hidden: true
|
||||
@ -27,6 +28,7 @@ QuoteThreading =
|
||||
Thread.callbacks.push
|
||||
name: 'Quote Threading'
|
||||
cb: @setThread
|
||||
|
||||
Post.callbacks.push
|
||||
name: 'Quote Threading'
|
||||
cb: @node
|
||||
|
||||
@ -55,7 +55,6 @@ Quotify =
|
||||
className: 'quotelink deadlink'
|
||||
target: '_blank'
|
||||
textContent: "#{quote}\u00A0(Dead)"
|
||||
|
||||
$.extend a.dataset, {boardID, threadID: post.thread.ID, postID}
|
||||
|
||||
else
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user