diff --git a/src/Filtering/Filter.coffee b/src/Filtering/Filter.coffee
index 0075c907e..f5790e148 100644
--- a/src/Filtering/Filter.coffee
+++ b/src/Filtering/Filter.coffee
@@ -2,7 +2,8 @@ Filter =
filters: {}
results: {}
init: ->
- return unless g.VIEW in ['index', 'thread'] and Conf['Filter']
+ return unless g.VIEW in ['index', 'thread', 'catalog'] and Conf['Filter']
+ return if g.VIEW is 'catalog' and not Conf['Filter in Native Catalog']
unless Conf['Filtered Backlinks']
$.addClass doc, 'hide-backlinks'
@@ -89,9 +90,12 @@ Filter =
(@filters[key] or= []).push filter
return unless Object.keys(@filters).length
- Callbacks.Post.push
- name: 'Filter'
- cb: @node
+ if g.VIEW is 'catalog'
+ Filter.catalog()
+ else
+ Callbacks.Post.push
+ name: 'Filter'
+ cb: @node
# Parse comma-separated list of boards.
# Sites can be specified by a beginning part of the site domain followed by a colon.
@@ -166,6 +170,41 @@ Filter =
if noti and Unread.posts and (@ID > Unread.lastReadPost) and not QuoteYou.isYou(@)
Unread.openNotification @, ' triggered a notification filter'
+ catalog: ->
+ return unless (url = g.SITE.urls.catalogJSON?(g.BOARD))
+ Filter.catalogData = {}
+ $.ajax url,
+ onloadend: Filter.catalogParse
+ Callbacks.CatalogThreadNative.push
+ name: 'Filter'
+ cb: @catalogNode
+
+ catalogParse: ->
+ if @status not in [200, 404]
+ new Notice 'warning', "Failed to fetch catalog JSON data. #{if @status then "Error #{@statusText} (#{@status})" else 'Connection Error'}", 1
+ return
+ for page in @response
+ for item in page.threads
+ Filter.catalogData[item.no] = item
+ g.BOARD.threads.forEach (thread) ->
+ if thread.catalogViewNative
+ Filter.catalogNode.call thread.catalogViewNative
+ return
+
+ catalogNode: ->
+ return unless @boardID is g.BOARD.ID and Filter.catalogData[@ID]
+ return if QuoteYou.db?.get {siteID: g.SITE.ID, boardID: @boardID, threadID: @ID, postID: @ID}
+ {hide, hl, top} = Filter.test(g.SITE.Build.parseJSON Filter.catalogData[@ID], @)
+ if hide
+ @nodes.root.hidden = true
+ else
+ if hl
+ @highlights = hl
+ $.addClass @nodes.root, hl...
+ if top
+ $.prepend @nodes.root.parentNode, @nodes.root
+ g.SITE.catalogPin? @nodes.root
+
isHidden: (post) ->
!!Filter.test(post).hide
diff --git a/src/General/Settings.coffee b/src/General/Settings.coffee
index bd639ab02..133164d82 100644
--- a/src/General/Settings.coffee
+++ b/src/General/Settings.coffee
@@ -492,6 +492,9 @@ Settings =
if compareString < '00001.00014.00009.00001'
if data['Use Faster Image Host']? and not data['fourchanImageHost']?
set 'fourchanImageHost', (if data['Use Faster Image Host'] then 'i.4cdn.org' else '')
+ if compareString < '00001.00014.00010.00001'
+ unless data['Filter in Native Catalog']?
+ set 'Filter in Native Catalog', false
changes
loadSettings: (data, cb) ->
diff --git a/src/General/Settings/Filter-guide.html b/src/General/Settings/Filter-guide.html
index 8a6a3ea84..02bba5805 100644
--- a/src/General/Settings/Filter-guide.html
+++ b/src/General/Settings/Filter-guide.html
@@ -51,7 +51,3 @@
For example: type:filename+filesize+dimensions;.
-
- Note: If you're using the native catalog rather than <%= meta.name %>'s catalog, <%= meta.name %>'s filters do not apply there.
- The native catalog has its own separate filter list.
-
diff --git a/src/classes/Callbacks.coffee b/src/classes/Callbacks.coffee
index f35629d9b..31aee94f5 100644
--- a/src/classes/Callbacks.coffee
+++ b/src/classes/Callbacks.coffee
@@ -2,6 +2,7 @@ class Callbacks
@Post = new Callbacks 'Post'
@Thread = new Callbacks 'Thread'
@CatalogThread = new Callbacks 'Catalog Thread'
+ @CatalogThreadNative = new Callbacks 'Catalog Thread'
constructor: (@type) ->
@keys = []
diff --git a/src/classes/CatalogThreadNative.coffee b/src/classes/CatalogThreadNative.coffee
new file mode 100644
index 000000000..f66bb1ab9
--- /dev/null
+++ b/src/classes/CatalogThreadNative.coffee
@@ -0,0 +1,12 @@
+class CatalogThreadNative
+ toString: -> @ID
+
+ constructor: (root) ->
+ @nodes =
+ root: root
+ thumb: $(g.SITE.selectors.catalog.thumb, root)
+ @siteID = g.SITE.ID
+ @boardID = @nodes.thumb.parentNode.pathname.split(/\/+/)[1]
+ @board = g.boards[@boardID] or new Board(@boardID)
+ @ID = @threadID = +(root.dataset.id or root.id).match(/\d*$/)[0]
+ @thread = @board.threads[@ID] or new Thread(@ID, @board)
diff --git a/src/config/Config.coffee b/src/config/Config.coffee
index 2e538533a..c3032bfae 100644
--- a/src/config/Config.coffee
+++ b/src/config/Config.coffee
@@ -187,6 +187,11 @@ Config =
'When enabled, shows backlinks to filtered posts with a line-through decoration. Otherwise, hides the backlinks.'
1
]
+ 'Filter in Native Catalog': [
+ true
+ 'Apply 4chan X filters in native catalog.'
+ 1
+ ]
'Recursive Hiding': [
true
'Hide replies of hidden posts, recursively.'
diff --git a/src/css/style.css b/src/css/style.css
index 12d309922..0be891aea 100644
--- a/src/css/style.css
+++ b/src/css/style.css
@@ -1462,7 +1462,8 @@ input[name="Default Volume"] {
:root:not(.werkTyme) .catalog-thread.filter-highlight .catalog-thumb,
: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.werkTyme.catalog-hover-expand .catalog-thread.filter-highlight > .catalog-container:hover > .catalog-post,
+:root.catalog $site$catalog$thread.filter-highlight$site$relative$catalogHighlight {
box-shadow: 0 0 3px 3px rgba(255, 0, 0, .5);
}
:root:not(.werkTyme) .catalog-thread.watched .catalog-thumb,
diff --git a/src/main/Main.coffee b/src/main/Main.coffee
index 8fd3ce8ba..812513430 100644
--- a/src/main/Main.coffee
+++ b/src/main/Main.coffee
@@ -325,7 +325,9 @@ Main =
new Notice 'warning', msg
# Parse HTML or skip it and start building from JSON.
- unless Index.enabled
+ if g.VIEW is 'catalog'
+ Main.initCatalog()
+ else if !Index.enabled
Main.initThread()
else
Main.expectInitFinished = true
@@ -431,6 +433,49 @@ Main =
$.event 'PostsRemoved', null, thread.nodes.root
return
+ initCatalog: ->
+ s = g.SITE.selectors.catalog
+ if s and (board = $ s.board)
+ threads = []
+ errors = []
+
+ Main.addCatalogThreadsObserver = new MutationObserver Main.addCatalogThreads
+ Main.addCatalogThreadsObserver.observe board, {childList: true}
+
+ Main.parseCatalogThreads $$(s.thread, board), threads, errors
+ Main.handleErrors errors if errors.length
+
+ Main.callbackNodes 'CatalogThreadNative', threads
+
+ Main.expectInitFinished = true
+ $.event '4chanXInitFinished'
+
+ parseCatalogThreads: (threadRoots, threads, errors) ->
+ for threadRoot in threadRoots
+ try
+ thread = new CatalogThreadNative threadRoot
+ if thread.thread.catalogViewNative?.nodes.root isnt threadRoot
+ thread.thread.catalogViewNative = thread
+ threads.push thread
+ catch err
+ # Skip threads that we failed to parse.
+ errors.push
+ message: "Parsing of Catalog Thread No.#{(threadRoot.dataset.id or threadRoot.id).match(/\d+/)} failed. Thread will be skipped."
+ error: err
+ return
+
+ addCatalogThreads: (records) ->
+ threadRoots = []
+ for record in records
+ for node in record.addedNodes when node.nodeType is Node.ELEMENT_NODE and node.matches(g.SITE.selectors.catalog.thread)
+ threadRoots.push node
+ return unless threadRoots.length
+ threads = []
+ errors = []
+ Main.parseCatalogThreads threadRoots, threads, errors
+ Main.handleErrors errors if errors.length
+ Main.callbackNodes 'CatalogThreadNative', threads
+
callbackNodes: (klass, nodes) ->
i = 0
cb = Callbacks[klass]
diff --git a/src/site/SW.tinyboard.coffee b/src/site/SW.tinyboard.coffee
index 2ab00499f..cc78ec912 100644
--- a/src/site/SW.tinyboard.coffee
+++ b/src/site/SW.tinyboard.coffee
@@ -94,9 +94,14 @@ SW.tinyboard =
opHighlight: ' > .op'
replyPost: '.reply'
replyOriginal: 'div[id^="reply_"]:not(.hidden)'
+ catalogHighlight: ' > .thread'
comment: '.body'
spoiler: '.spoiler'
quotelink: 'a[onclick^="highlightReply("]'
+ catalog:
+ board: '#Grid'
+ thread: '.mix'
+ thumb: '.thread-image'
boardList: '.boardlist'
boardListBottom: '.boardlist.bottom'
styleSheet: '#stylesheet'
@@ -186,3 +191,6 @@ SW.tinyboard =
isLinkified: (link) ->
/\bnofollow\b/.test(link.rel)
+
+ catalogPin: (threadRoot) ->
+ threadRoot.dataset.sticky = 'true'
diff --git a/src/site/SW.yotsuba.coffee b/src/site/SW.yotsuba.coffee
index fcd2e12fa..38a74994c 100644
--- a/src/site/SW.yotsuba.coffee
+++ b/src/site/SW.yotsuba.coffee
@@ -54,9 +54,14 @@ SW.yotsuba =
opHighlight: '.opContainer'
replyPost: ' > .reply'
replyOriginal: '.replyContainer:not([data-clone])'
+ catalogHighlight: ''
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
+ catalog:
+ board: '#threads'
+ thread: '.thread'
+ thumb: '.thumb'
boardList: '#boardNavDesktop > .boardList'
boardListBottom: '#boardNavDesktopFoot > .boardList'
styleSheet: 'link[title=switch]'