From 538e117746a235a5f83af1ffb056ca27d9aac6bd Mon Sep 17 00:00:00 2001 From: Mayhem Date: Mon, 6 May 2013 06:57:29 +0200 Subject: [PATCH] Add archive selection. --- CHANGELOG.md | 3 + Gruntfile.js | 1 + css/style.css | 12 ++ html/General/Settings-section-Archives.html | 12 ++ json/archives.json | 100 +++++++++++++ json/archives1.json | 101 +++++++++++++ src/Archive/Redirect.coffee | 158 +++++++++----------- src/General/Get.coffee | 6 +- src/General/Main.coffee | 11 +- src/General/Settings.coffee | 64 ++++++++ src/Images/ImageExpand.coffee | 5 +- src/Images/ImageHover.coffee | 5 +- src/Menu/ArchiveLink.coffee | 8 +- src/Quotelinks/Quotify.coffee | 4 +- 14 files changed, 384 insertions(+), 106 deletions(-) create mode 100644 html/General/Settings-section-Archives.html create mode 100644 json/archives.json create mode 100644 json/archives1.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 477d2c7e4..4a032da5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +- **New feature**: `Archive selection` + - Select which archive you want for specific boards and redirection type. + - Access it in the `Archives` tab of the Settings window. - Fix quote previews getting 'stuck' in Opera. ### 3.3.1 - *2013-05-04* diff --git a/Gruntfile.js b/Gruntfile.js index d2ce937a1..38dc200ba 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -122,6 +122,7 @@ module.exports = function(grunt) { 'src/**/*', 'html/**/*', 'css/**/*', + 'json/**/*', 'img/**/*' ], tasks: 'build' diff --git a/css/style.css b/css/style.css index 0aa8ffa2d..520faec69 100644 --- a/css/style.css +++ b/css/style.css @@ -358,6 +358,18 @@ a[href="javascript:;"] { .section-rice textarea { height: 150px; } +.section-archives table { + width: 100%; +} +.section-archives th:not(:first-child) { + width: 30%; +} +.section-archives td { + text-align: center; +} +.section-archives select { + width: 90%; +} #fourchanx-settings fieldset { border: 1px solid; border-radius: 3px; diff --git a/html/General/Settings-section-Archives.html b/html/General/Settings-section-Archives.html new file mode 100644 index 000000000..086b33d92 --- /dev/null +++ b/html/General/Settings-section-Archives.html @@ -0,0 +1,12 @@ +
404 Redirect is disabled.
+

Disabled selections indicate that only one archive is available for that board and redirection type.

+ + + + + + + + + +
Archived boards
BoardThread redirectionPost fetchingFile redirection
diff --git a/json/archives.json b/json/archives.json new file mode 100644 index 000000000..405c30029 --- /dev/null +++ b/json/archives.json @@ -0,0 +1,100 @@ +[{ + "uid": 0, + "name": "Foolz", + "domain": "archive.foolz.us", + "http": true, + "https": true, + "software": "foolfuuka", + "boards": ["a", "co", "gd", "jp", "m", "q", "sp", "tg", "tv", "vp", "vr", "wsg"], + "files": ["a", "gd", "jp", "m", "q", "tg", "vp", "vr", "wsg"] +}, { + "uid": 1, + "name": "NSFW Foolz", + "domain": "nsfw.foolz.us", + "http": true, + "https": true, + "software": "foolfuuka", + "boards": ["u"], + "files": ["u"] +}, { + "uid": 2, + "name": "The Dark Cave", + "domain": "archive.thedarkcave.org", + "http": true, + "https": true, + "software": "foolfuuka", + "boards": ["c", "int", "out", "po"], + "files": ["c", "po"] +}, { + "uid": 3, + "name": "4plebs", + "domain": "archive.4plebs.org", + "http": true, + "https": false, + "software": "foolfuuka", + "boards": ["hr", "tg", "tv", "x"], + "files": ["hr", "tg", "tv", "x"] +}, { + "uid": 4, + "name": "Nyafuu", + "domain": "archive.nyafuu.org", + "http": true, + "https": true, + "software": "foolfuuka", + "boards": ["c", "w", "wg"], + "files": ["c", "w", "wg"] +}, { + "uid": 5, + "name": "Love is Over", + "domain": "loveisover.me", + "http": true, + "https": true, + "software": "foolfuuka", + "boards": ["d", "h", "v"], + "files": ["d", "h", "v"] +}, { + "uid": 6, + "name": "nth-chan", + "domain": "nth.pensivenonsen.se", + "http": true, + "https": false, + "software": "foolfuuka", + "boards": [], + "files": [] +}, { + "uid": 7, + "name": "Install Gentoo", + "domain": "archive.installgentoo.net", + "http": true, + "https": true, + "software": "fuuka", + "boards": ["diy", "g", "sci"], + "files": [] +}, { + "uid": 8, + "name": "Rebecca Black Tech", + "domain": "rbt.asia", + "http": true, + "https": true, + "software": "fuuka", + "boards": ["cgl", "g", "mu", "w"], + "files": ["cgl", "g", "mu", "w"] +}, { + "uid": 9, + "name": "Heinessen", + "domain": "archive.heinessen.com", + "http": true, + "https": false, + "software": "fuuka", + "boards": ["an", "fit", "k", "mlp", "r9k", "toy", "x"], + "files": ["an", "k", "toy", "x"] +}, { + "uid": 10, + "name": "warosu", + "domain": "fuuka.warosu.org", + "http": true, + "https": true, + "software": "fuuka", + "boards": ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "q", "s4s", "tg", "vr"], + "files": ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "q", "s4s", "vr"] +}] diff --git a/json/archives1.json b/json/archives1.json new file mode 100644 index 000000000..ed724e4f6 --- /dev/null +++ b/json/archives1.json @@ -0,0 +1,101 @@ +{ + "Foolz": { + "uid": 0, + "domain": "archive.foolz.us", + "http": true, + "https": true, + "software": "foolfuuka", + "boards": ["a", "co", "gd", "jp", "m", "q", "sp", "tg", "tv", "vp", "vr", "wsg"], + "files": ["a", "gd", "jp", "m", "q", "tg", "vp", "vr", "wsg"] + }, + "NSFW Foolz": { + "uid": 1, + "domain": "nsfw.foolz.us", + "http": true, + "https": true, + "software": "foolfuuka", + "boards": ["u"], + "files": ["u"] + }, + "The Dark Cave": { + "uid": 2, + "domain": "archive.thedarkcave.org", + "http": true, + "https": true, + "software": "foolfuuka", + "boards": ["c", "int", "out", "po"], + "files": ["c", "po"] + }, + "4plebs": { + "uid": 3, + "domain": "archive.4plebs.org", + "http": true, + "https": false, + "software": "foolfuuka", + "boards": ["hr", "tg", "tv", "x"], + "files": ["hr", "tg", "tv", "x"] + }, + "Nyafuu": { + "uid": 4, + "domain": "archive.nyafuu.org", + "http": true, + "https": true, + "software": "foolfuuka", + "boards": ["c", "w", "wg"], + "files": ["c", "w", "wg"] + }, + "Love is Over": { + "uid": 5, + "domain": "loveisover.me", + "http": true, + "https": true, + "software": "foolfuuka", + "boards": ["d", "h", "v"], + "files": ["d", "h", "v"] + }, + "nth-chan": { + "uid": 6, + "domain": "nth.pensivenonsen.se", + "http": true, + "https": false, + "software": "foolfuuka", + "boards": ["vg"], + "files": ["vg"] + }, + "Install Gentoo": { + "uid": 7, + "domain": "archive.installgentoo.net", + "http": true, + "https": true, + "software": "fuuka", + "boards": ["diy", "g", "sci"], + "files": [] + }, + "Rebecca Black Tech": { + "uid": 8, + "domain": "rbt.asia", + "http": true, + "https": true, + "software": "fuuka", + "boards": ["cgl", "g", "mu", "w"], + "files": ["cgl", "g", "mu", "w"] + }, + "Heinessen": { + "uid": 9, + "domain": "archive.heinessen.com", + "http": true, + "https": false, + "software": "fuuka", + "boards": ["an", "fit", "k", "mlp", "r9k", "toy", "x"], + "files": ["an", "k", "toy", "x"] + }, + "warosu": { + "uid": 10, + "domain": "fuuka.warosu.org", + "http": true, + "https": true, + "software": "fuuka", + "boards": ["cgl", "ck", "fa", "jp", "lit", "q", "s4s", "tg", "vr"], + "files": ["cgl", "ck", "fa", "jp", "lit", "q", "s4s", "tg", "vr"] + } +} diff --git a/src/Archive/Redirect.coffee b/src/Archive/Redirect.coffee index da18202c7..284cda71f 100644 --- a/src/Archive/Redirect.coffee +++ b/src/Archive/Redirect.coffee @@ -1,102 +1,78 @@ Redirect = - image: (boardID, filename) -> - # Do not use g.BOARD, the image url can originate from a cross-quote. - switch boardID - when 'a', 'gd', 'jp', 'm', 'q', 'tg', 'vp', 'vr', 'wsg' - "//archive.foolz.us/#{boardID}/full_image/#{filename}" - when 'u' - "//nsfw.foolz.us/#{boardID}/full_image/#{filename}" - when 'po' - "//archive.thedarkcave.org/#{boardID}/full_image/#{filename}" - when 'hr', 'tv' - "http://archive.4plebs.org/#{boardID}/full_image/#{filename}" - when 'c', 'w', 'wg' - "//archive.nyafuu.org/#{boardID}/full_image/#{filename}" - when 'd', 'h', 'v' - "//loveisover.me/#{boardID}/full_image/#{filename}" - when 'vg' - "http://nth.pensivenonsen.se/#{boardID}/full_image/#{filename}" - when 'ck', 'fa', 'lit', 's4s' - "//fuuka.warosu.org/#{boardID}/full_image/#{filename}" - when 'cgl', 'g', 'mu' - "//rbt.asia/#{boardID}/full_image/#{filename}" - when 'an', 'k', 'toy', 'x' - "http://archive.heinessen.com/#{boardID}/full_image/#{filename}" - post: (boardID, postID) -> - # XXX foolz had HSTS set for 120 days, which broke XHR+CORS+Redirection when on HTTP. - # Remove necessary HTTPS procotol in September 2013. - switch boardID - when 'a', 'co', 'gd', 'jp', 'm', 'q', 'sp', 'tg', 'tv', 'vp', 'vr', 'wsg' - "https://archive.foolz.us/_/api/chan/post/?board=#{boardID}&num=#{postID}" - when 'u' - "https://nsfw.foolz.us/_/api/chan/post/?board=#{boardID}&num=#{postID}" - when 'int', 'out', 'po' - "//archive.thedarkcave.org/_/api/chan/post/?board=#{boardID}&num=#{postID}" - when 'hr', 'x' - "http://archive.4plebs.org/_/api/chan/post/?board=#{boardID}&num=#{postID}" - when 'c', 'w', 'wg' - "//archive.nyafuu.org/_/api/chan/post/?board=#{boardID}&num=#{postID}" - when 'd', 'h', 'v' - "//loveisover.me/_/api/chan/post/?board=#{boardID}&num=#{postID}" - when 'vg' - "http://nth.pensivenonsen.se/_/api/chan/post/?board=#{boardID}&num=#{postID}" - # for fuuka-based archives: - # https://github.com/eksopl/fuuka/issues/27 - to: (data) -> - {boardID} = data - switch boardID - when 'a', 'co', 'gd', 'jp', 'm', 'q', 'sp', 'tg', 'tv', 'vp', 'vr', 'wsg' - Redirect.path '//archive.foolz.us', 'foolfuuka', data - when 'u' - Redirect.path '//nsfw.foolz.us', 'foolfuuka', data - when 'int', 'out', 'po' - Redirect.path '//archive.thedarkcave.org', 'foolfuuka', data - when 'hr' - Redirect.path 'http://archive.4plebs.org', 'foolfuuka', data - when 'c', 'w', 'wg' - Redirect.path '//archive.nyafuu.org', 'foolfuuka', data - when 'd', 'h', 'v' - Redirect.path '//loveisover.me', 'foolfuuka', data - when 'vg' - Redirect.path 'http://nth.pensivenonsen.se', 'foolfuuka', data - when 'ck', 'fa', 'lit', 's4s' - Redirect.path '//fuuka.warosu.org', 'fuuka', data - when 'diy', 'g', 'sci' - Redirect.path '//archive.installgentoo.net', 'fuuka', data - when 'cgl', 'mu' - Redirect.path '//rbt.asia', 'fuuka', data - when 'an', 'fit', 'k', 'mlp', 'r9k', 'toy', 'x' - Redirect.path 'http://archive.heinessen.com', 'fuuka', data - else - if data.threadID then "//boards.4chan.org/#{boardID}/" else '' - path: (base, archiver, data) -> - if data.isSearch - {boardID, type, value} = data - type = if type is 'name' - 'username' - else if type is 'MD5' - 'image' - else - type - value = encodeURIComponent value - return if archiver is 'foolfuuka' - "#{base}/#{boardID}/search/#{type}/#{value}" - else if type is 'image' - "#{base}/#{boardID}/?task=search2&search_media_hash=#{value}" - else - "#{base}/#{boardID}/?task=search2&search_#{type}=#{value}" + archives: `<%= JSON.stringify(grunt.file.readJSON('json/archives.json')) %>` + thread: {} + post: {} + file: {} - {boardID, threadID, postID} = data - # keep the number only if the location.hash was sent f.e. + init: -> + for boardID, data of Conf['selectedArchives'] + for type, uid of data + for archive in Redirect.archives + continue if archive.uid isnt uid or type is 'post' and archive.software isnt 'foolfuuka' + arr = if type is 'file' + archive.files + else + archive.boards + Redirect[type][boardID] = archive if boardID in arr + for archive in Redirect.archives + for boardID in archive.boards + unless boardID of Redirect.thread + Redirect.thread[boardID] = archive + unless boardID of Redirect.post or archive.software isnt 'foolfuuka' + Redirect.post[boardID] = archive + unless boardID of Redirect.file or boardID not in archive.files + Redirect.file[boardID] = archive + return + + to: (dest, data) -> + archive = (if dest is 'search' then Redirect.thread else Redirect[dest])[data.boardID] + return '' unless archive + Redirect[dest] archive, data + + protocol: (archive) -> + protocol = location.protocol + unless archive[protocol[0...-1]] + protocol = if protocol is 'https:' then 'http:' else 'https:' + "#{protocol}//" + + thread: (archive, {boardID, threadID, postID}) -> + # Keep the post number only if the location.hash was sent f.e. path = if threadID "#{boardID}/thread/#{threadID}" else "#{boardID}/post/#{postID}" - if archiver is 'foolfuuka' + if archive.software is 'foolfuuka' path += '/' if threadID and postID - path += if archiver is 'foolfuuka' + path += if archive.software is 'foolfuuka' "##{postID}" else "#p#{postID}" - "#{base}/#{path}" + "#{Redirect.protocol archive}#{archive.domain}/#{path}" + + post: (archive, {boardID, postID}) -> + # For fuuka-based archives: + # https://github.com/eksopl/fuuka/issues/27 + protocol = Redirect.protocol archive + # XXX foolz had HSTS set for 120 days, which broke XHR+CORS+Redirection when on HTTP. + # Remove necessary HTTPS procotol in September 2013. + if archive.name in ['Foolz', 'NSFW Foolz'] + protocol = 'https://' + "#{protocol}#{archive.domain}/_/api/chan/post/?board=#{boardID}&num=#{postID}" + + file: (archive, {boardID, filename}) -> + "#{Redirect.protocol archive}#{archive.domain}/#{boardID}/full_image/#{filename}" + + search: (archive, {boardID, type, value}) -> + type = if type is 'name' + 'username' + else if type is 'MD5' + 'image' + else + type + value = encodeURIComponent value + path = if archive.software is 'foolfuuka' + "#{boardID}/search/#{type}/#{value}" + else + "#{boardID}/?task=search2&search_#{if type is 'image' then 'media_hash' else type}=#{value}" + "#{Redirect.protocol archive}#{archive.domain}/#{path}" diff --git a/src/General/Get.coffee b/src/General/Get.coffee index adcc770ce..235d2781e 100644 --- a/src/General/Get.coffee +++ b/src/General/Get.coffee @@ -71,7 +71,7 @@ Get = if threadID $.cache "//api.4chan.org/#{boardID}/res/#{threadID}.json", -> Get.fetchedPost @, boardID, threadID, postID, root, context - else if url = Redirect.post boardID, postID + else if url = Redirect.to 'post', {boardID, postID} $.cache url, -> Get.archivedPost @, boardID, postID, root, context insert: (post, root, context) -> @@ -97,7 +97,7 @@ Get = {status} = req if status not in [200, 304] # The thread can die by the time we check a quote. - if url = Redirect.post boardID, postID + if url = Redirect.to 'post', {boardID, postID} $.cache url, -> Get.archivedPost @, boardID, postID, root, context else @@ -115,7 +115,7 @@ Get = break if post.no is postID # we found it! if post.no > postID # The post can be deleted by the time we check a quote. - if url = Redirect.post boardID, postID + if url = Redirect.to 'post', {boardID, postID} $.cache url, -> Get.archivedPost @, boardID, postID, root, context else diff --git a/src/General/Main.coffee b/src/General/Main.coffee index 18d9b5fba..35b4363bc 100644 --- a/src/General/Main.coffee +++ b/src/General/Main.coffee @@ -14,6 +14,7 @@ Main = flatten null, Config for db in DataBoards Conf[db] = boards: {} + Conf['selectedArchives'] = {} $.get Conf, Main.initFeatures $.on d, '4chanMainInit', Main.initStyle @@ -43,8 +44,11 @@ Main = when 'images.4chan.org' $.ready -> if Conf['404 Redirect'] and d.title is '4chan - 404 Not Found' - url = Redirect.image pathname[1], pathname[3] - location.href = url if url + Redirect.init() + URL = Redirect.to 'file', + boardID: pathname[1] + filename: pathname[3] + location.href = URL if URL return initFeature = (name, module) -> @@ -65,6 +69,7 @@ Main = initFeature 'Announcement Hiding', PSAHiding initFeature 'Fourchan thingies', Fourchan initFeature 'Custom CSS', CustomCSS + initFeature 'Redirect', Redirect initFeature 'Resurrect Quotes', Quotify initFeature 'Filter', Filter initFeature 'Thread Hiding', ThreadHiding @@ -148,7 +153,7 @@ Main = initReady: -> if d.title is '4chan - 404 Not Found' if Conf['404 Redirect'] and g.VIEW is 'thread' - href = Redirect.to + href = Redirect.to 'thread', boardID: g.BOARD.ID threadID: g.THREADID postID: +location.hash.match /\d+/ # post number or 0 diff --git a/src/General/Settings.coffee b/src/General/Settings.coffee index 42739d968..87edb431a 100644 --- a/src/General/Settings.coffee +++ b/src/General/Settings.coffee @@ -41,6 +41,7 @@ Settings = Settings.addSection 'QR', Settings.qr Settings.addSection 'Sauce', Settings.sauce Settings.addSection 'Rice', Settings.rice + Settings.addSection 'Archives', Settings.archives Settings.addSection 'Keybinds', Settings.keybinds $.on d, 'AddSettingsSection', Settings.addSection $.on d, 'OpenSettings', (e) -> Settings.open e.detail @@ -386,6 +387,69 @@ Settings = usercss: -> CustomCSS.update() + archives: (section) -> + section.innerHTML = """ + <%= grunt.file.read('html/General/Settings-section-Archives.html').replace(/>\s+<').trim() %> + """ + + boards = {} + for archive in Redirect.archives + for boardID in archive.boards + data = boards[boardID] or= { + thread: [] + post: [] + file: [] + } + data.thread.push archive + if archive.software is 'foolfuuka' + data.post.push archive + if boardID in archive.files + data.file.push archive + + rows = [] + for boardID in Object.keys(boards).sort() # Alphabetical order + row = $.el 'tr' + rows.push row + $.add row, $.el 'th', + textContent: "/#{boardID}/" + className: if boardID is g.BOARD.ID then 'warning' else '' + + data = boards[boardID] + Settings.addArchiveCell row, boardID, data, 'thread' + Settings.addArchiveCell row, boardID, data, 'post' + Settings.addArchiveCell row, boardID, data, 'file' + $.add $('tbody', section), rows + $.get 'selectedArchives', Conf['selectedArchives'], ({selectedArchives}) -> + for boardID, data of selectedArchives + for type, uid of data + if option = $ "select[data-boardid='#{boardID}'][data-type='#{type}'] > option[value='#{uid}']", section + option.selected = true + return + addArchiveCell: (row, boardID, data, type) -> + options = [] + for archive in data[type] + options.push $.el 'option', + textContent: archive.name + value: archive.uid + td = $.el 'td' + {length} = options + if length + td.innerHTML = '' + select = td.firstElementChild + unless select.disabled = length is 1 + # XXX GM can't into datasets + select.setAttribute 'data-boardid', boardID + select.setAttribute 'data-type', type + $.on select, 'change', Settings.saveSelectedArchive + $.add select, options + else + td.textContent = 'N/A' + $.add row, td + saveSelectedArchive: -> + $.get 'selectedArchives', Conf['selectedArchives'], ({selectedArchives}) => + (selectedArchives[@dataset.boardid] or= {})[@dataset.type] = +@value + $.set 'selectedArchives', selectedArchives + keybinds: (section) -> section.innerHTML = """ <%= grunt.file.read('html/General/Settings-section-Keybinds.html').replace(/>\s+<').trim() %> diff --git a/src/Images/ImageExpand.coffee b/src/Images/ImageExpand.coffee index 7735ed990..38284688f 100644 --- a/src/Images/ImageExpand.coffee +++ b/src/Images/ImageExpand.coffee @@ -136,7 +136,10 @@ ImageExpand = src = @src.split '/' if src[2] is 'images.4chan.org' - if URL = Redirect.image src[3], src[5] + URL = Redirect.to 'file', + boardID: src[3] + filename: src[5].replace /\?.+$/, '' + if URL setTimeout ImageExpand.expand, 10000, post, URL return if g.DEAD or post.isDead or post.file.isDead diff --git a/src/Images/ImageHover.coffee b/src/Images/ImageHover.coffee index f6f2945ab..cff961806 100644 --- a/src/Images/ImageHover.coffee +++ b/src/Images/ImageHover.coffee @@ -28,7 +28,10 @@ ImageHover = src = @src.split '/' if src[2] is 'images.4chan.org' - if URL = Redirect.image src[3], src[5].replace /\?.+$/, '' + URL = Redirect.to 'file', + boardID: src[3] + filename: src[5].replace /\?.+$/, '' + if URL @src = URL return if g.DEAD or post.isDead or post.file.isDead diff --git a/src/Menu/ArchiveLink.coffee b/src/Menu/ArchiveLink.coffee index a3a6fd184..f691d6f95 100644 --- a/src/Menu/ArchiveLink.coffee +++ b/src/Menu/ArchiveLink.coffee @@ -10,8 +10,7 @@ ArchiveLink = el: div order: 90 open: ({ID, thread, board}) -> - redirect = Redirect.to {postID: ID, threadID: thread.ID, boardID: board.ID} - redirect isnt "//boards.4chan.org/#{board}/" + !!Redirect.to 'thread', {postID: ID, threadID: thread.ID, boardID: board.ID} subEntries: [] for type in [ @@ -35,18 +34,17 @@ ArchiveLink = open = if type is 'post' ({ID, thread, board}) -> - el.href = Redirect.to {postID: ID, threadID: thread.ID, boardID: board.ID} + el.href = Redirect.to 'thread', {postID: ID, threadID: thread.ID, boardID: board.ID} true else (post) -> value = Filter[type] post # We want to parse the exact same stuff as the filter does already. return false unless value - el.href = Redirect.to + el.href = Redirect.to 'search', boardID: post.board.ID type: type value: value - isSearch: true true return { diff --git a/src/Quotelinks/Quotify.coffee b/src/Quotelinks/Quotify.coffee index 8319439f5..0acd189f9 100644 --- a/src/Quotelinks/Quotify.coffee +++ b/src/Quotelinks/Quotify.coffee @@ -47,14 +47,14 @@ Quotify = a.setAttribute 'data-boardid', boardID a.setAttribute 'data-threadid', post.thread.ID a.setAttribute 'data-postid', postID - else if redirect = Redirect.to {boardID, threadID: 0, postID} + else if redirect = Redirect.to 'thread', {boardID, threadID: 0, postID} # Replace the .deadlink span if we can redirect. a = $.el 'a', href: redirect className: 'deadlink' target: '_blank' textContent: "#{quote}\u00A0(Dead)" - if Redirect.post boardID, postID + if Redirect.to 'post', {boardID, postID} # Make it function as a normal quote if we can fetch the post. $.addClass a, 'quotelink' a.setAttribute 'data-boardid', boardID