Merge branch 'v3' into Av2

Conflicts:
	builds/4chan-X.js
	builds/4chan-X.user.js
	builds/crx.crx
	builds/crx/script.js
	src/config.coffee
	src/css/style.css
	src/features/misc/announcementhiding.coffee
	src/features/misc/header.coffee
	src/features/misc/keybinds.coffee
	src/main.coffee
	src/settings.coffee
This commit is contained in:
Zixaphir 2013-04-22 21:41:24 -07:00
commit 3d07017708
75 changed files with 1203 additions and 849 deletions

7
.gitignore vendored
View File

@ -1,7 +1,6 @@
node_modules/ node_modules/
tmp/
4chan_x.user.js
Cakefile
script.coffee
*~ *~
*.db *.db
tmp-crx/
tmp-userjs/
tmp-userscript/

View File

@ -1,3 +1,5 @@
- Added the option `Hide Unread Count at (0)`, disabled by default.
### 3.1.4 - *2013-04-17* ### 3.1.4 - *2013-04-17*
- Fix QR remembering the file spoiler state when it shouldn't, for real this time. - Fix QR remembering the file spoiler state when it shouldn't, for real this time.

View File

@ -1,4 +1,6 @@
## Reporting bugs ## Reporting bugs and suggestions
Reporting bugs:
1. Make sure both your **browser** and **4chan X** are up to date. 1. Make sure both your **browser** and **4chan X** are up to date.
2. Disable your other extensions & scripts to identify conflicts. 2. Disable your other extensions & scripts to identify conflicts.
@ -13,6 +15,12 @@ Open your console with:
- `Ctrl + Shift + K` on Firefox. - `Ctrl + Shift + K` on Firefox.
- `Ctrl + Shift + O` on Opera. - `Ctrl + Shift + O` on Opera.
Respect these guidelines:
- Describe the issue clearly, put some effort into it. A one-liner isn't a good enough description.
- If you want to get your suggestion implemented sooner, make it convincing.
- If you want to criticize, make it convincing and constructive.
- Be mature. Act like an idiot and you will be blocked without warning.
## Development & Contribution ## Development & Contribution
### Get started ### Get started

View File

@ -5,6 +5,11 @@ module.exports = (grunt) ->
process: process:
data: pkg data: pkg
shellOptions =
stdout: true
stderr: true
failOnError: true
# Project configuration. # Project configuration.
grunt.initConfig grunt.initConfig
pkg: pkg pkg: pkg
@ -12,12 +17,12 @@ module.exports = (grunt) ->
coffee: coffee:
options: concatOptions options: concatOptions
src: [ src: [
'src/code/config.coffee' 'src/config.coffee'
'src/code/globals.coffee' 'src/globals.coffee'
'src/lib/*.coffee' 'src/lib/*.coffee'
'src/code/*/*.coffee' 'src/features/*/*.coffee'
'src/code/settings.coffee' 'src/settings.coffee'
'src/code/main.coffee' 'src/main.coffee'
] ]
dest: 'tmp-<%= pkg.type %>/script.coffee' dest: 'tmp-<%= pkg.type %>/script.coffee'
@ -68,27 +73,25 @@ module.exports = (grunt) ->
'build-userscript' 'build-userscript'
] ]
exec: shell:
commit: commit:
command: -> options: shellOptions
release = "#{pkg.meta.name} v#{pkg.version}" command: [
return [ 'git checkout <%= pkg.meta.mainBranch %>',
'git checkout ' + pkg.meta.mainBranch, 'git commit -am "Release <%= pkg.meta.name %> v<%= pkg.version %>."',
'git commit -am "Release ' + release + '."', 'git tag -a <%= pkg.version %> -m "<%= pkg.meta.name %> v<%= pkg.version %>."',
'git tag -a ' + pkg.version + ' -m "' + release + '."', 'git tag -af stable-v3 -m "<%= pkg.meta.name %> v<%= pkg.version %>."'
'git tag -af stable-v3 -m "' + release + '."' ].join(' && ')
].join(' && ');
stdout: true stdout: true
push: push:
command: 'git push origin --all && git push origin --tags' options: shellOptions
stdout: true command: 'git push origin --tags -f && git push origin --all'
watch: watch:
all: all:
options: options:
interrupt: true interrupt: true
nospawn: true
files: [ files: [
'Gruntfile.coffee' 'Gruntfile.coffee'
'package.json' 'package.json'
@ -120,7 +123,7 @@ module.exports = (grunt) ->
grunt.loadNpmTasks 'grunt-contrib-concat' grunt.loadNpmTasks 'grunt-contrib-concat'
grunt.loadNpmTasks 'grunt-contrib-copy' grunt.loadNpmTasks 'grunt-contrib-copy'
grunt.loadNpmTasks 'grunt-contrib-watch' grunt.loadNpmTasks 'grunt-contrib-watch'
grunt.loadNpmTasks 'grunt-exec' grunt.loadNpmTasks 'grunt-shell'
grunt.registerTask 'default', [ grunt.registerTask 'default', [
'build' 'build'
@ -161,8 +164,8 @@ module.exports = (grunt) ->
grunt.registerTask 'release', [ grunt.registerTask 'release', [
'default' 'default'
'exec:commit' 'shell:commit'
'exec:push' 'shell:push'
] ]
grunt.registerTask 'patch', [ grunt.registerTask 'patch', [

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -18,15 +18,15 @@
}, },
"devDependencies": { "devDependencies": {
"grunt": "~0.4.1", "grunt": "~0.4.1",
"grunt-bump": "~0.0.0", "grunt-bump": "~0.0.2",
"grunt-concurrent": "~0.1.1", "grunt-concurrent": "~0.2.0",
"grunt-contrib-clean": "~0.4.0", "grunt-contrib-clean": "~0.4.1",
"grunt-contrib-coffee": "~0.6.6", "grunt-contrib-coffee": "~0.6.7",
"grunt-contrib-compress": "~0.4.10", "grunt-contrib-compress": "~0.4.10",
"grunt-contrib-concat": "~0.2.0", "grunt-contrib-concat": "~0.2.0",
"grunt-contrib-copy": "~0.4.1", "grunt-contrib-copy": "~0.4.1",
"grunt-contrib-watch": "~0.3.1", "grunt-contrib-watch": "~0.3.1",
"grunt-exec": "~0.4.0" "grunt-shell": "~0.2.2"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -1,82 +0,0 @@
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', 'vg', '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 'ck', 'fa', 'lit', 's4s'
"//fuuka.warosu.org/#{boardID}/full_image/#{filename}"
when 'cgl', 'g', 'mu', 'w'
"//rbt.asia/#{boardID}/full_image/#{filename}"
when 'an', 'k', 'toy', 'x'
"http://archive.heinessen.com/#{boardID}/full_image/#{filename}"
when 'c'
"//archive.nyafuu.org/#{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', 'v', 'vg', '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 'c', 'int', 'out', 'po'
"//archive.thedarkcave.org/_/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', 'v', 'vg', '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 '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', 'w'
Redirect.path '//rbt.asia', 'fuuka', data
when 'an', 'fit', 'k', 'mlp', 'r9k', 'toy', 'x'
Redirect.path 'http://archive.heinessen.com', 'fuuka', data
when 'c'
Redirect.path '//archive.nyafuu.org', '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}"
{boardID, threadID, postID} = data
# keep the number only if the location.hash was sent f.e.
path = if threadID
"#{boardID}/thread/#{threadID}"
else
"#{boardID}/post/#{postID}"
if archiver is 'foolfuuka'
path += '/'
if threadID and postID
path += if archiver is 'foolfuuka'
"##{postID}"
else
"#p#{postID}"
"#{base}/#{path}"

View File

@ -9,18 +9,14 @@ Config =
false false
'Link to external catalog instead of the internal one.' 'Link to external catalog instead of the internal one.'
] ]
'Enable 4chan\'s Extension': [
false
'Compatibility between <%= meta.name %> and 4chan\'s inline extension is NOT guaranteed.'
]
'Fixed Header': [
false
'Mayhem X\'s Fixed Header (kinda).'
]
'Custom Board Navigation': [ 'Custom Board Navigation': [
false false
'Show custom links instead of the full board list.' 'Show custom links instead of the full board list.'
] ]
'QR Shortcut': [
false,
'Adds a small [QR] link in the header.'
]
'Announcement Hiding': [ 'Announcement Hiding': [
true true
'Add button to hide 4chan announcements.' 'Add button to hide 4chan announcements.'
@ -105,10 +101,6 @@ Config =
true true
'Add buttons to hide single replies.' 'Add buttons to hide single replies.'
] ]
'Hiding Buttons': [
true
'Add buttons to hide threads / replies, in addition to menu links.'
]
'Stubs': [ 'Stubs': [
true true
'Show stubs of hidden threads / replies.' 'Show stubs of hidden threads / replies.'
@ -145,7 +137,7 @@ Config =
] ]
'Fappe Tyme': [ 'Fappe Tyme': [
false false
'Hide posts without images when toggled.' 'Hide posts without images when toggled. *hint* *hint*'
] ]
'Menu': 'Menu':
@ -153,6 +145,10 @@ Config =
true true
'Add a drop-down menu to posts.' 'Add a drop-down menu to posts.'
] ]
'Report Link': [
true
'Add a report link to the menu.'
]
'Thread Hiding Link': [ 'Thread Hiding Link': [
true true
'Add a link to hide entire threads.' 'Add a link to hide entire threads.'
@ -160,19 +156,17 @@ Config =
'Reply Hiding Link': [ 'Reply Hiding Link': [
true true
'Add a link to hide single replies.' 'Add a link to hide single replies.'
]
'Report Link': [
true
'Add a report link to the menu.'
] ]
'Delete Link': [ 'Delete Link': [
true true
'Add post and image deletion links to the menu.' 'Add post and image deletion links to the menu.'
] ]
<% if (type === 'crx') { %>
'Download Link': [ 'Download Link': [
true true
'Add a download with original filename link to the menu. Chrome-only currently.' 'Add a download with original filename link to the menu. Chrome-only currently.'
] ]
<% } %>
'Archive Link': [ 'Archive Link': [
true true
'Add an archive link to the menu.' 'Add an archive link to the menu.'
@ -187,6 +181,10 @@ Config =
true true
'Show the unread posts count in the tab title.' 'Show the unread posts count in the tab title.'
] ]
'Hide Unread Count at (0)': [
false
'Hide the unread posts count when it reaches 0.'
]
'Unread Tab Icon': [ 'Unread Tab Icon': [
true true
'Show a different favicon when there are unread posts.' 'Show a different favicon when there are unread posts.'
@ -195,6 +193,10 @@ Config =
true true
'Show a line to distinguish read posts from unread ones.' 'Show a line to distinguish read posts from unread ones.'
] ]
'Scroll to Last Read Post': [
true
'Scroll back to the last read post when reopening a thread.'
]
'Thread Excerpt': [ 'Thread Excerpt': [
true true
'Show an excerpt of the thread in the tab title.' 'Show an excerpt of the thread in the tab title.'

View File

@ -78,7 +78,7 @@ body > a[style="cursor: pointer; float: right;"]::after {
#boardNavDesktopFoot { #boardNavDesktopFoot {
top: 16px !important; top: 16px !important;
} }
#{if _conf['Boards Navigation'] is 'top' or _conf['Boards Navigation'] is 'sticky top' then '#boardNavDesktop' else if _conf['Pagination'] is 'top' or _conf['Pagination'] is 'sticky top' then '.pagelist'} { #{if _conf['Boards Navigation'] is 'top' or _conf['Boards Navigation'] is 'sticky top' then '#header-bar' else if _conf['Pagination'] is 'top' or _conf['Pagination'] is 'sticky top' then '.pagelist'} {
#{if _conf['4chan SS Navigation'] #{if _conf['4chan SS Navigation']
"padding-#{align}: #{iconOffset}px;" "padding-#{align}: #{iconOffset}px;"
else else

View File

@ -78,7 +78,7 @@ div.navLinks > a:first-of-type::after {
width: #{233 + Style.sidebarOffset.W}px !important; width: #{233 + Style.sidebarOffset.W}px !important;
#{align}: 18px !important; #{align}: 18px !important;
} }
#{if _conf['Boards Navigation'] is 'top' or _conf['Boards Navigation'] is 'sticky top' then '#boardNavDesktop' else if _conf['Pagination'] is 'top' or _conf['Pagination'] is 'sticky top' then '.pagelist'} { #{if _conf['Boards Navigation'] is 'top' or _conf['Boards Navigation'] is 'sticky top' then '#header-bar' else if _conf['Pagination'] is 'top' or _conf['Pagination'] is 'sticky top' then '.pagelist'} {
#{if _conf['4chan SS Navigation'] #{if _conf['4chan SS Navigation']
"padding-#{align}: #{iconOffset}px;" "padding-#{align}: #{iconOffset}px;"
else else

View File

@ -146,7 +146,7 @@ hr {
margin: 1.5px; margin: 1.5px;
} }
/* Header */ /* Header */
#boardNavDesktop { #header-bar {
z-index: 6; z-index: 6;
border-width: 1px; border-width: 1px;
position: absolute; position: absolute;
@ -168,34 +168,34 @@ else "
" else ""} " else ""}
text-align: #{_conf["Navigation Alignment"]}; text-align: #{_conf["Navigation Alignment"]};
} }
.fixed #boardNavDesktop { .fixed #header-bar {
position: fixed; position: fixed;
} }
.top #boardNavDesktop { .top #header-bar {
top: 0; top: 0;
border-top-width: 0; border-top-width: 0;
#{if _conf["Rounded Edges"] then "border-radius: 0 0 3px 3px;" else ""}" #{if _conf["Rounded Edges"] then "border-radius: 0 0 3px 3px;" else ""}"
} }
.fixed.bottom #boardNavDesktop { .fixed.bottom #header-bar {
bottom: 0; bottom: 0;
border-bottom-width: 0; border-bottom-width: 0;
#{if _conf["Rounded Edges"] then "border-radius: 3px 3px 0 0;" else ""}" #{if _conf["Rounded Edges"] then "border-radius: 3px 3px 0 0;" else ""}"
} }
.hide #boardNavDesktop { .hide #header-bar {
position: fixed; position: fixed;
top: 110%; top: 110%;
bottom: auto; bottom: auto;
} }
/* Header Autohide */ /* Header Autohide */
.fixed #boardNavDesktop.autohide:not(:hover) { .fixed #header-bar.autohide:not(:hover) {
box-shadow: none; box-shadow: none;
transition: all .8s .6s cubic-bezier(.55, .055, .675, .19); transition: all .8s .6s cubic-bezier(.55, .055, .675, .19);
} }
.fixed.top #boardNavDesktop.autohide:not(:hover) { .fixed.top #header-bar.autohide:not(:hover) {
margin-bottom: -1em; margin-bottom: -1em;
#{agent}transform: translateY(-100%); #{agent}transform: translateY(-100%);
} }
.fixed.bottom #boardNavDesktop.autohide:not(:hover) { .fixed.bottom #header-bar.autohide:not(:hover) {
#{agent}transform: translateY(100%); #{agent}transform: translateY(100%);
} }
#toggle-header-bar { #toggle-header-bar {
@ -204,22 +204,22 @@ else "
height: 10px; height: 10px;
position: absolute; position: absolute;
} }
#boardNavDesktop #toggle-header-bar { #header-bar #toggle-header-bar {
display: none; display: none;
} }
.fixed #boardNavDesktop #toggle-header-bar { .fixed #header-bar #toggle-header-bar {
display: block; display: block;
} }
.fixed #boardNavDesktop #toggle-header-bar { .fixed #header-bar #toggle-header-bar {
cursor: n-resize; cursor: n-resize;
} }
.fixed.top boardNavDesktop #toggle-header-bar { .fixed.top header-bar #toggle-header-bar {
top: 100%; top: 100%;
} }
.fixed.bottom #boardNavDesktop #toggle-header-bar { .fixed.bottom #header-bar #toggle-header-bar {
bottom: 100%; bottom: 100%;
} }
.fixed #boardNavDesktop #header-bar.autohide #toggle-header-bar { .fixed #header-bar #header-bar.autohide #toggle-header-bar {
cursor: s-resize; cursor: s-resize;
} }
/* Notifications */ /* Notifications */
@ -345,7 +345,7 @@ else "
.pagelist div { .pagelist div {
vertical-align: middle; vertical-align: middle;
} }
#boardNavDesktop a { #header-bar a {
font-size: #{parseInt(_conf["Font Size"], 10)}px; font-size: #{parseInt(_conf["Font Size"], 10)}px;
} }
#{if _conf["Hide Navigation Decorations"] then " #{if _conf["Hide Navigation Decorations"] then "

View File

@ -173,7 +173,7 @@ textarea.field:focus {
background: #{theme["Highlighted Reply Background"]}; background: #{theme["Highlighted Reply Background"]};
border: 1px solid #{theme["Highlighted Reply Border"]}; border: 1px solid #{theme["Highlighted Reply Border"]};
} }
#boardNavDesktop, #header-bar,
.pagelist { .pagelist {
background: #{theme["Navigation Background"]}; background: #{theme["Navigation Background"]};
border-style: solid; border-style: solid;
@ -286,7 +286,7 @@ textarea {
border: 1px solid #{theme["Buttons Border"]}; border: 1px solid #{theme["Buttons Border"]};
} }
.pages a, .pages a,
#boardNavDesktop a { #header-bar a {
color: #{theme["Navigation Links"]}; color: #{theme["Navigation Links"]};
} }
input[type=checkbox]:checked + .rice { input[type=checkbox]:checked + .rice {
@ -331,7 +331,7 @@ a .postertrip:hover,
a:hover { a:hover {
color: #{theme["Hovered Links"]}; color: #{theme["Hovered Links"]};
} }
#boardNavDesktop a:hover, #header-bar a:hover,
#boardTitle a:hover { #boardTitle a:hover {
color: #{theme["Hovered Navigation Links"]}; color: #{theme["Hovered Navigation Links"]};
} }

View File

@ -127,7 +127,7 @@ ThreadHiding =
ThreadHiding.saveHiddenState thread ThreadHiding.saveHiddenState thread
hide: (thread, makeStub=Conf['Stubs']) -> hide: (thread, makeStub=Conf['Stubs']) ->
return if thread.hidden return if thread.isHidden
{OP} = thread {OP} = thread
threadRoot = OP.nodes.root.parentNode threadRoot = OP.nodes.root.parentNode
threadRoot.hidden = thread.isHidden = true threadRoot.hidden = thread.isHidden = true

View File

@ -175,7 +175,7 @@ ImageExpand =
$.event 'AddMenuEntry', $.event 'AddMenuEntry',
type: 'header' type: 'header'
el: el el: el
order: 80 order: 105
subEntries: subEntries subEntries: subEntries
createSubEntry: (type, config) -> createSubEntry: (type, config) ->

View File

@ -1,14 +1,7 @@
DownloadLink = DownloadLink =
init: -> init: ->
<% if (type === 'userscript') { %>
# Firefox won't let us download cross-domain content.
return
<% } %>
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Download Link'] return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Download Link']
# Test for download feature support.
return unless 'download' of $.el 'a'
a = $.el 'a', a = $.el 'a',
className: 'download-link' className: 'download-link'
textContent: 'Download file' textContent: 'Download file'

View File

@ -12,6 +12,7 @@ PSAHiding =
PSAHiding.btn = btn = $.el 'a', PSAHiding.btn = btn = $.el 'a',
title: 'Toggle announcement.' title: 'Toggle announcement.'
innerHTML: '<span></span>'
href: 'javascript:;' href: 'javascript:;'
textContent: '[ - ]' textContent: '[ - ]'
$.on btn, 'click', PSAHiding.toggle $.on btn, 'click', PSAHiding.toggle

View File

@ -2,28 +2,33 @@ CatalogLinks =
init: -> init: ->
$.ready @ready $.ready @ready
return unless Conf['Catalog Links'] return unless Conf['Catalog Links']
el = $.el 'a', el = $.el 'label',
id: 'toggleCatalog' id: 'toggleCatalog'
href: 'javascript:;' href: 'javascript:;'
className: if Conf['Header catalog links'] then 'disabled' else '' innerHTML: "<input type=checkbox #{if Conf['Header catalog links'] then 'checked' else ''}>Catalog"
textContent: 'Catalog'
title: "Turn catalog links #{if Conf['Header catalog links'] then 'off' else 'on'}." title: "Turn catalog links #{if Conf['Header catalog links'] then 'off' else 'on'}."
$.on el, 'click', @toggle
Header.addShortcut el input = $ 'input', el
$.on input, 'change', @toggle
$.sync 'Header catalog links', CatalogLinks.set
$.asap (-> d.body), -> $.event 'AddMenuEntry',
return unless Main.isThisPageLegit() type: 'header'
# Wait for #boardNavMobile instead of #boardNavDesktop, el: el
# it might be incomplete otherwise. order: 95
$.asap (-> $.id 'boardNavMobile'), ->
# Set links on load. $.on d, '4chanXInitFinished', ->
CatalogLinks.toggle.call el # Set links on load.
CatalogLinks.set Conf['Header catalog links']
toggle: -> toggle: ->
$.set 'Header catalog links', useCatalog = @className is 'disabled' $.event 'CloseMenu'
$.toggleClass @, 'disabled' $.set 'Header catalog links', useCatalog = @checked
for a in $$ 'a', $.id('boardNavDesktop') CatalogLinks.set useCatalog
set: (useCatalog) ->
path = if useCatalog then 'catalog' else ''
for a in $$ 'a', $.id('board-list')
board = a.pathname.split('/')[1] board = a.pathname.split('/')[1]
continue if ['f', 'status', '4chan'].contains(board) or !board continue if ['f', 'status', '4chan'].contains(board) or !board
if Conf['External Catalog'] if Conf['External Catalog']
@ -32,9 +37,9 @@ CatalogLinks =
else else
"//boards.4chan.org/#{board}/" "//boards.4chan.org/#{board}/"
else else
a.pathname = "/#{board}/#{if useCatalog then 'catalog' else ''}" a.pathname = "/#{board}/#{path}"
a.title = if useCatalog then "#{a.title} - Catalog" else a.title.replace(/\ -\ Catalog$/, '') a.title = if useCatalog then "#{a.title} - Catalog" else a.title.replace(/\ -\ Catalog$/, '')
@title = "Turn catalog links #{if useCatalog then 'off' else 'on'}." @title = "Turn catalog links #{if useCatalog then 'off' else 'on'}."
external: (board) -> external: (board) ->
return ( return (
@ -50,4 +55,4 @@ CatalogLinks =
if catalogLink = ($('.pages.cataloglink a', d.body) or $ '[href=".././catalog"]', d.body) if catalogLink = ($('.pages.cataloglink a', d.body) or $ '[href=".././catalog"]', d.body)
if g.VIEW isnt 'thread' if g.VIEW isnt 'thread'
$.add d.body, catalogLink $.add d.body, catalogLink
catalogLink.id = 'catalog' catalogLink.id = 'catalog'

View File

@ -94,8 +94,4 @@ ExpandThread =
Main.callbackNodes Post, posts Main.callbackNodes Post, posts
$.after a, nodes $.after a, nodes
# Enable 4chan features. Fourchan.parseThread thread.ID, 1, nodes.length
if Conf['Enable 4chan\'s Extension']
$.globalEval "Parser.parseThread(#{thread.ID}, 1, #{nodes.length})"
else
Fourchan.parseThread thread.ID, 1, nodes.length

View File

@ -5,34 +5,30 @@ Header =
id: 'main-menu' id: 'main-menu'
@menu = new UI.Menu 'header' @menu = new UI.Menu 'header'
$.on @menuButton, 'click', @menuToggle
$.on @toggle, 'mousedown', @toggleBarVisibility
$.on window, 'load hashchange', Header.hashScroll
@positionToggler = $.el 'span', headerToggler = $.el 'label',
textContent: 'Header Position' innerHTML: '<input type=checkbox name="Header auto-hide"> Auto-hide header'
className: 'header-position-link'
@headerToggler = headerToggler.firstElementChild
$.on @menuButton, 'click', @menuToggle
$.on window, 'load hashchange', Header.hashScroll
$.on @headerToggler, 'change', @toggleBarVisibility
{createSubEntry} = Header {createSubEntry} = Header
subEntries = [] subEntries = []
for setting in ['sticky top', 'sticky bottom', 'top', 'hide'] for setting in ['sticky top', 'sticky bottom', 'top', 'hide']
subEntries.push createSubEntry setting subEntries.push createSubEntry setting
subEntries.push {el: headerToggler}
$.event 'AddMenuEntry', $.event 'AddMenuEntry',
type: 'header' type: 'header'
el: @positionToggler el: $.el 'span',
order: 108 textContent: 'Header'
order: 105
subEntries: subEntries subEntries: subEntries
@headerToggler = $.el 'label',
innerHTML: "<input type=checkbox #{if Conf['Header auto-hide'] then 'checked' else ''}> Auto-hide header"
$.on @headerToggler.firstElementChild, 'change', @toggleBarVisibility
$.event 'AddMenuEntry',
type: 'header'
el: @headerToggler
order: 109
$.on d, 'CreateNotification', @createNotification $.on d, 'CreateNotification', @createNotification
$.asap (-> d.body), -> $.asap (-> d.body), ->
@ -56,7 +52,7 @@ Header =
toggle: $.el 'div', toggle: $.el 'div',
id: 'toggle-header-bar' id: 'toggle-header-bar'
createSubEntry: (setting)-> createSubEntry: (setting) ->
label = $.el 'label', label = $.el 'label',
textContent: "#{setting}" textContent: "#{setting}"
@ -66,15 +62,15 @@ Header =
setBoardList: -> setBoardList: ->
Header.nav = nav = $.id 'boardNavDesktop' Header.nav = nav = $.id 'boardNavDesktop'
nav.id = 'header-bar'
if a = $ "a[href*='/#{g.BOARD}/']", nav if a = $ "a[href*='/#{g.BOARD}/']", nav
a.className = 'current' a.className = 'current'
fullBoardList = $.el 'span', boardList = $.el 'span',
id: 'full-board-list' id: 'board-list'
hidden: true
customBoardList = $.el 'span', $.add boardList, fullBoardList = $.el 'span',
id: 'custom-board-list' id: 'full-board-list'
Header.setBarPosition.call textContent: "#{Conf['Boards Navigation']}" Header.setBarPosition.call textContent: "#{Conf['Boards Navigation']}"
$.sync 'Boards Navigation', Header.changeBarPosition $.sync 'Boards Navigation', Header.changeBarPosition
@ -86,18 +82,25 @@ Header =
$.add settings, Header.menuButton $.add settings, Header.menuButton
$.add fullBoardList, [nav.childNodes...] $.add fullBoardList, [nav.childNodes...]
$.add nav, [customBoardList, fullBoardList, Header.shortcuts, Header.bar, Header.toggle] $.add nav, [boardList, Header.shortcuts, Header.bar, Header.toggle]
if Conf['Custom Board Navigation'] if Conf['Custom Board Navigation']
fullBoardList.hidden = true
customBoardList = $.el 'span',
id: 'custom-board-list'
$.add boardList, customBoardList
Header.generateBoardList Conf['boardnav'] Header.generateBoardList Conf['boardnav']
$.sync 'boardnav', Header.generateBoardList $.sync 'boardnav', Header.generateBoardList
btn = $.el 'span', btn = $.el 'span',
className: 'hide-board-list-button' className: 'hide-board-list-button'
innerHTML: '[<a href=javascript:;> - </a>]\u00A0' innerHTML: '[<a href=javascript:;> - </a>]\u00A0'
$.on btn, 'click', Header.toggleBoardList $.on btn, 'click', Header.toggleBoardList
$.prepend fullBoardList, btn $.prepend fullBoardList, btn
else else
$.rm $ '#custom-board-list', nav
fullBoardList.hidden = false fullBoardList.hidden = false
generateBoardList: (text) -> generateBoardList: (text) ->
@ -105,7 +108,7 @@ Header =
$.rmAll list $.rmAll list
return unless text return unless text
as = $$('#full-board-list a', Header.nav)[0...-2] # ignore the Settings and Home links as = $$('#full-board-list a', Header.nav)[0...-2] # ignore the Settings and Home links
nodes = text.match(/[\w@]+(-(all|title|full|index|catalog|text:"[^"]+"))*|[^\w@]+/g).map (t) -> nodes = text.match(/[\w@]+(-(all|title|replace|full|index|catalog|text:"[^"]+"))*|[^\w@]+/g).map (t) ->
if /^[^\w@]/.test t if /^[^\w@]/.test t
return $.tn t return $.tn t
if /^toggle-all/.test t if /^toggle-all/.test t
@ -124,6 +127,9 @@ Header =
a = a.cloneNode true a = a.cloneNode true
if /-title/.test t if /-title/.test t
a.textContent = a.title a.textContent = a.title
else if /-replace/.test t
if $.hasClass a, 'current'
a.textContent = a.title
else if /-full/.test t else if /-full/.test t
a.textContent = "/#{board}/ - #{a.title}" a.textContent = "/#{board}/ - #{a.title}"
else if /-(index|catalog|text)/.test t else if /-(index|catalog|text)/.test t
@ -173,7 +179,8 @@ Header =
$.addClass doc, 'hide' $.addClass doc, 'hide'
setBarVisibility: (hide) -> setBarVisibility: (hide) ->
Header.headerToggler.firstElementChild.checked = hide Header.headerToggler.checked = hide
$.event 'CloseMenu'
(if hide then $.addClass else $.rmClass) Header.nav, 'autohide' (if hide then $.addClass else $.rmClass) Header.nav, 'autohide'
hashScroll: -> hashScroll: ->
@ -193,20 +200,33 @@ Header =
hide = if @nodeName is 'INPUT' hide = if @nodeName is 'INPUT'
@checked @checked
else else
!$.hasClass Header.nav, 'autohide' !$.hasClass Header.bar, 'autohide'
Conf['Header auto-hide'] = hide
$.set 'Header auto-hide', hide
Header.setBarVisibility hide Header.setBarVisibility hide
message = if hide message = if hide
'The header bar will automatically hide itself.' 'The header bar will automatically hide itself.'
else else
'The header bar will remain visible.' 'The header bar will remain visible.'
new Notification 'info', message, 2 new Notification 'info', message, 2
$.set 'Header auto-hide', hide
hashScroll: ->
return unless (hash = @location.hash) and post = $.id hash[1..]
return if (Get.postFromRoot post).isHidden
Header.scrollToPost post
scrollToPost: (post) ->
{top} = post.getBoundingClientRect()
if Conf['Boards Navigation'] is 'sticky top'
headRect = Header.bar.getBoundingClientRect()
top += - headRect.top - headRect.height
<% if (type === 'crx') { %>d.body<% } else { %>doc<% } %>.scrollTop += top
addShortcut: (el) -> addShortcut: (el) ->
shortcut = $.el 'span', shortcut = $.el 'span',
className: 'shortcut' className: 'shortcut'
$.add shortcut, [$.tn(' ['), el, $.tn(']')] $.add shortcut, [$.tn(' ['), el, $.tn(']')]
$.add Header.shortcuts, shortcut $.prepend Header.shortcuts, shortcut
menuToggle: (e) -> menuToggle: (e) ->
Header.menu.toggle e, @, g Header.menu.toggle e, @, g

View File

@ -133,8 +133,10 @@ Keybinds =
do QR.open do QR.open
if quote if quote
QR.quote.call $ 'input', $('.post.highlight', thread) or thread QR.quote.call $ 'input', $('.post.highlight', thread) or thread
do QR.nodes.com.focus do QR.nodes.com.focus
$.rmClass $('.qr-shortcut'), 'disabled' if Conf['QR Shortcut']
$.rmClass $('.qr-shortcut'), 'disabled'
tags: (tag, ta) -> tags: (tag, ta) ->
value = ta.value value = ta.value

View File

@ -0,0 +1,133 @@
Redirect =
init: ->
$.sync 'archs', @updateArchives
updateArchives: ->
$.get 'archivers', {}, ({archivers}) ->
Conf['archivers'] = archivers
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', 'vg', '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 'ck', 'fa', 'lit', 's4s'
"//fuuka.warosu.org/#{boardID}/full_image/#{filename}"
when 'cgl', 'g', 'mu', 'w'
"//rbt.asia/#{boardID}/full_image/#{filename}"
when 'an', 'k', 'toy', 'x'
"http://archive.heinessen.com/#{boardID}/full_image/#{filename}"
when 'c'
"//archive.nyafuu.org/#{boardID}/full_image/#{filename}"
post: (boardID, postID) ->
unless Redirect.post[boardID]?
for name, archive of @archiver
if archive.type is 'foolfuuka' and archive.boards.contains boardID
Redirect.post[boardID] = archive.base
break
Redirect.post[boardID] or= false
return if Redirect.post[boardID]
"#{Redirect.post[boardID]}/_/api/chan/post/?board=#{boardID}&num=#{postID}"
else
null
select: (board) ->
for name, archive of @archiver
continue unless archive.boards.contains board
name
to: (data) ->
{boardID} = data
unless (arch = Conf.archivers[boardID])?
Conf.archivers[boardID] = arch = @select(boardID)[0]
$.set 'archivers', Conf.archivers
return (if arch and archive = @archiver[arch]
Redirect.path archive.base, archive.type, data
else if data.threadID
"//boards.4chan.org/#{boardID}/"
else
null)
archiver:
'Foolz':
base: 'https://archive.foolz.us'
boards: ['a', 'co', 'gd', 'jp', 'm', 'q', 'sp', 'tg', 'tv', 'v', 'vg', 'vp', 'vr', 'wsg']
type: 'foolfuuka'
'NSFWFoolz':
base: 'https://nsfw.foolz.us'
boards: ['u']
type: 'foolfuuka'
'TheDarkCave':
base: 'http://archive.thedarkcave.org'
boards: ['c', 'int', 'out', 'po']
type: 'foolfuuka'
'4plebs':
base: 'http://archive.4plebs.org'
boards: ['hr', 'tg', 'tv', 'x']
base: 'foolfuuka'
'Warosu':
base: '//fuuka.warosu.org'
boards: ['cgl', 'ck', 'fa', 'jp', 'lit', 's4s', 'q', 'tg']
type: 'fuuka'
'RebeccaBlackTech':
base: '//rbt.asia'
boards: ['an', 'cgl', 'g', 'mu', 'w']
type: 'fuuka_mail'
'InstallGentoo':
base: '//archive.installgentoo.net'
boards: ['diy', 'g', 'sci']
type: 'fuuka'
'Heinessen':
base: 'http://archive.heinessen.com'
boards: ['an', 'fit', 'k', 'mlp', 'r9k', 'toy', 'x']
type: 'fuuka'
'Cliche':
base: '//www.cliché.net/4chan/cgi-board.pl'
boards: ['e']
type: 'fuuka'
'NyaFuu':
base: '//archive.nyafuu.org'
boards: ['c', 'w']
type: 'fuuka'
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}"
{boardID, threadID, postID} = data
# keep the number only if the location.hash was sent f.e.
path = if threadID
"#{boardID}/thread/#{threadID}"
else
"#{boardID}/post/#{postID}"
if archiver is 'foolfuuka'
path += '/'
if threadID and postID
path += if archiver is 'foolfuuka'
"##{postID}"
else
"#p#{postID}"
"#{base}/#{path}"

View File

@ -6,7 +6,4 @@ ThreadExcerpt =
name: 'Thread Excerpt' name: 'Thread Excerpt'
cb: @node cb: @node
node: -> node: ->
d.title = if (excerpt = Get.threadExcerpt @).length > 80 d.title = Get.threadExcerpt @
"#{excerpt[...77]}..."
else
excerpt

View File

@ -290,10 +290,7 @@ ThreadUpdater =
# Enable 4chan features. # Enable 4chan features.
threadID = ThreadUpdater.thread.ID threadID = ThreadUpdater.thread.ID
{length} = $$ '.thread > .postContainer', ThreadUpdater.root {length} = $$ '.thread > .postContainer', ThreadUpdater.root
if Conf['Enable 4chan\'s Extension'] Fourchan.parseThread threadID, length - count, length
$.globalEval "Parser.parseThread(#{threadID}, #{-count})"
else
Fourchan.parseThread threadID, length - count, length
$.event 'ThreadUpdate', $.event 'ThreadUpdate',
404: false 404: false

View File

@ -26,18 +26,22 @@ Unread =
$.on d, 'ThreadUpdate', Unread.onUpdate $.on d, 'ThreadUpdate', Unread.onUpdate
$.on d, 'scroll visibilitychange', Unread.read $.on d, 'scroll visibilitychange', Unread.read
$.on d, 'visibilitychange', Unread.setLine if Conf['Unread Line'] $.on d, 'visibilitychange', Unread.setLine if Conf['Unread Line']
if Conf['Scroll to Last Read Post']
$.on window, 'load', (posts) =>
Unread.scroll.apply @, posts
return unless Conf['Scroll to Last Read Post'] scroll: (posts) ->
# Let the header's onload callback handle it. # Let the header's onload callback handle it.
return if (hash = location.hash.match /\d+/) and hash[0] of @posts return if (hash = location.hash.match /\d+/) and hash[0] of @posts
if Unread.posts.length if Unread.posts.length
# Scroll to before the first unread post. # Scroll to before the first unread post.
while root = $.x 'preceding-sibling::div[contains(@class,"postContainer")][1]', Unread.posts[0].nodes.root while root = $.x 'preceding-sibling::div[contains(@class,"postContainer")][1]', Unread.posts[0].nodes.root
break unless (Get.postFromRoot root).isHidden break unless (Get.postFromRoot root).isHidden
return unless root
root.scrollIntoView false root.scrollIntoView false
else if posts.length else if posts.length
# Scroll to the last read post. # Scroll to the last read post.
Header.scrollToPost posts[posts.length - 1].nodes.root Header.scrollToPost (posts[post.length - 1]).nodes.root
sync: -> sync: ->
lastReadPost = Unread.db.get lastReadPost = Unread.db.get
@ -98,7 +102,7 @@ Unread =
break if post.ID > Unread.lastReadPost break if post.ID > Unread.lastReadPost
arr.splice 0, i arr.splice 0, i
read: (e) -> read: $.debounce 50, (e) ->
return if d.hidden or !Unread.posts.length return if d.hidden or !Unread.posts.length
height = doc.clientHeight height = doc.clientHeight
{posts} = Unread {posts} = Unread
@ -106,8 +110,8 @@ Unread =
i = posts.length i = posts.length
while post = posts[--i] while post = posts[--i]
{bottom, top} = post.nodes.root.getBoundingClientRect() {bottom} = post.nodes.root.getBoundingClientRect()
if (bottom < height) and (top > 0) # post is completely read if (bottom < height) # post is completely read
ID = post.ID ID = post.ID
posts.remove post posts.remove post
return unless ID return unless ID
@ -136,10 +140,7 @@ Unread =
count = Unread.posts.length count = Unread.posts.length
if Conf['Unread Count'] if Conf['Unread Count']
d.title = if g.DEAD d.title = "#{if count or !Conf['Hide Unread Count at (0)'] then "(#{count}) " else ''}#{if g.DEAD then "/#{g.BOARD}/ - 404" else "#{Unread.title}"}"
"(#{Unread.posts.length}) /#{g.BOARD}/ - 404"
else
"(#{Unread.posts.length}) #{Unread.title}"
<% if (type === 'crx') { %> <% if (type === 'crx') { %>
# XXX Chrome bug where it doesn't always update the tab title. # XXX Chrome bug where it doesn't always update the tab title.
# crbug.com/124381 # crbug.com/124381

View File

@ -81,7 +81,8 @@ QR =
QR.cleanNotifications() QR.cleanNotifications()
d.activeElement.blur() d.activeElement.blur()
$.rmClass QR.nodes.el, 'dump' $.rmClass QR.nodes.el, 'dump'
$.toggleClass $('.qr-shortcut'), 'disabled' if Conf['QR Shortcut']
$.toggleClass $('.qr-shortcut'), 'disabled'
for i in QR.posts for i in QR.posts
QR.posts[0].rm() QR.posts[0].rm()
QR.cooldown.auto = false QR.cooldown.auto = false
@ -303,7 +304,8 @@ QR =
QR.selected.save com QR.selected.save com
QR.selected.save thread QR.selected.save thread
$.rmClass $('.qr-shortcut'), 'disabled' if Conf['QR Shortcut']
$.rmClass $('.qr-shortcut'), 'disabled'
characterCount: -> characterCount: ->
counter = QR.nodes.charCount counter = QR.nodes.charCount
@ -850,7 +852,7 @@ QR =
QR.mimeTypes = mimeTypes.split ', ' QR.mimeTypes = mimeTypes.split ', '
# Add empty mimeType to avoid errors with URLs selected in Window's file dialog. # Add empty mimeType to avoid errors with URLs selected in Window's file dialog.
QR.mimeTypes.push '' QR.mimeTypes.push ''
nodes.fileInput.max = $('input[name=MAX_FILE_SIZE]').value nodes.fileInput.max = $('input[name=MAX_FILE_SIZE]').value
<% if (type !== 'userjs') { %> <% if (type !== 'userjs') { %>
# Opera's accept attribute is fucked up # Opera's accept attribute is fucked up
nodes.fileInput.accept = "text/*, #{mimeTypes}" nodes.fileInput.accept = "text/*, #{mimeTypes}"
@ -887,8 +889,8 @@ QR =
$.on elm, 'blur', QR.focusout $.on elm, 'blur', QR.focusout
$.on elm, 'focus', QR.focusin $.on elm, 'focus', QR.focusin
<% } %> <% } %>
$.on QR.nodes.el, 'focusin', QR.focusin $.on dialog, 'focusin', QR.focusin
$.on QR.nodes.el, 'focusout', QR.focusout $.on dialog, 'focusout', QR.focusout
$.on nodes.autohide, 'change', QR.toggleHide $.on nodes.autohide, 'change', QR.toggleHide
$.on nodes.close, 'click', QR.close $.on nodes.close, 'click', QR.close
$.on nodes.dumpButton, 'click', -> nodes.el.classList.toggle 'dump' $.on nodes.dumpButton, 'click', -> nodes.el.classList.toggle 'dump'

View File

@ -72,22 +72,23 @@ QuoteThreading =
return false unless Unread.posts.contains(qpost) or ((bottom < height) and (top > 0)) return false unless Unread.posts.contains(qpost) or ((bottom < height) and (top > 0))
qroot = qpost.nodes.root qroot = qpost.nodes.root
threadContainer = qroot.nextSibling unless $.hasClass qroot, 'threadOP'
if threadContainer?.className isnt 'threadContainer' $.addClass qroot, 'threadOP'
threadContainer = $.el 'div', threadContainer = $.el 'div',
className: 'threadContainer' className: 'threadContainer'
$.after qroot, threadContainer $.after qroot, threadContainer
else
threadContainer = qroot.nextSibling
$.add threadContainer, @nodes.root $.add threadContainer, @nodes.root
return true return true
toggle: -> toggle: ->
thread = $ '.thread' thread = $ '.thread'
replies = $$ '.thread > .replyContainer, .threadContainer > .replyContainer', thread replies = $$ '.thread > .replyContainer, .threadContainer > .replyContainer', thread
QuoteThreading.enabled = @checked QuoteThreading.enabled = @checked
if @checked if @checked
nodes = (Get.postFromNode reply for reply in replies) nodes = (Get.postFromNode reply for reply in replies)
Unread.node.call node for node in nodes
QuoteThreading.node node for node in nodes QuoteThreading.node node for node in nodes
else else
replies.sort (a, b) -> replies.sort (a, b) ->
@ -98,6 +99,7 @@ QuoteThreading =
containers = $$ '.threadContainer', thread containers = $$ '.threadContainer', thread
$.rm container for container in containers $.rm container for container in containers
Unread.update true Unread.update true
return
kb: -> kb: ->
control = $.id 'threadingControl' control = $.id 'threadingControl'

View File

@ -5,6 +5,8 @@ Get =
OP.info.comment.replace(/\n+/g, ' // ') or OP.info.comment.replace(/\n+/g, ' // ') or
Conf['Anonymize'] and 'Anonymous' or Conf['Anonymize'] and 'Anonymous' or
$('.nameBlock', OP.nodes.info).textContent.trim() $('.nameBlock', OP.nodes.info).textContent.trim()
if excerpt.length > 70
excerpt = "#{excerpt[...67]}..."
"/#{thread.board}/ - #{excerpt}" "/#{thread.board}/ - #{excerpt}"
postFromRoot: (root) -> postFromRoot: (root) ->
link = $ 'a[title="Highlight this post"]', root link = $ 'a[title="Highlight this post"]', root

View File

@ -54,14 +54,13 @@ UI = do ->
menu = @makeMenu() menu = @makeMenu()
currentMenu = menu currentMenu = menu
lastToggledButton = button lastToggledButton = button
@entries.sort (first, second) ->
first.order - second.order
for entry in @entries for entry in @entries
@insertEntry entry, menu, data @insertEntry entry, menu, data
entry = $ '.entry', menu
while prevEntry = @findNextEntry entry, -1
entry = prevEntry
@focus entry
$.on d, 'click', @close $.on d, 'click', @close
$.on d, 'CloseMenu', @close $.on d, 'CloseMenu', @close
Rice.nodes menu Rice.nodes menu
@ -87,6 +86,14 @@ UI = do ->
style.right = "#{right}px" style.right = "#{right}px"
style.bottom = "#{bottom}px" style.bottom = "#{bottom}px"
style.left = "#{left}px" style.left = "#{left}px"
if right
$.addClass menu, 'left'
entry = $ '.entry', menu
# We've removed flexbox, so we don't user order anymore.
# while prevEntry = @findNextEntry entry, -1
# entry = prevEntry
@focus entry
menu.focus() menu.focus()

View File

@ -14,7 +14,6 @@ Main =
flatten null, Config flatten null, Config
for db in DataBoards for db in DataBoards
Conf[db] = boards: {} Conf[db] = boards: {}
# Unflattened Config. # Unflattened Config.
$.extend Conf, $.extend Conf,
'userThemes': [] 'userThemes': []
@ -24,6 +23,7 @@ Main =
'Enabled Mascots nsfw': [] 'Enabled Mascots nsfw': []
'Deleted Mascots': [] 'Deleted Mascots': []
'Hidden Categories': ["Questionable"] 'Hidden Categories': ["Questionable"]
'archivers': {}
$.get Conf, Main.initFeatures $.get Conf, Main.initFeatures
@ -93,6 +93,7 @@ Main =
'Rice': Rice 'Rice': Rice
'Banner': Banner 'Banner': Banner
'Announcements': GlobalMessage 'Announcements': GlobalMessage
'Redirection': Redirect
'Header': Header 'Header': Header
'Catalog Links': CatalogLinks 'Catalog Links': CatalogLinks
'Settings': Settings 'Settings': Settings
@ -125,6 +126,7 @@ Main =
'Time Formatting': Time 'Time Formatting': Time
'Relative Post Dates': RelativeDates 'Relative Post Dates': RelativeDates
'File Info Formatting': FileInfo 'File Info Formatting': FileInfo
'Fappe Tyme': FappeTyme
'Sauce': Sauce 'Sauce': Sauce
'Image Expansion': ImageExpand 'Image Expansion': ImageExpand
'Image Expansion (Menu)': ImageExpand.menu 'Image Expansion (Menu)': ImageExpand.menu

View File

@ -7,6 +7,7 @@ Settings =
href: 'javascript:;' href: 'javascript:;'
$.on link, 'click', Settings.open $.on link, 'click', Settings.open
$.asap (-> d.body), -> $.asap (-> d.body), ->
return unless Main.isThisPageLegit() return unless Main.isThisPageLegit()
# Wait for #boardNavMobile instead of #boardNavDesktop, # Wait for #boardNavMobile instead of #boardNavDesktop,
@ -41,10 +42,10 @@ Settings =
Settings.addSection 'Sauce', Settings.sauce Settings.addSection 'Sauce', Settings.sauce
Settings.addSection 'Rice', Settings.rice Settings.addSection 'Rice', Settings.rice
Settings.addSection 'Keybinds', Settings.keybinds Settings.addSection 'Keybinds', Settings.keybinds
$.on d, 'AddSettingsSection', Settings.addSection $.on d, 'AddSettingsSection', Settings.addSection
$.on d, 'OpenSettings', (e) -> Settings.open e.detail $.on d, 'OpenSettings', (e) -> Settings.open e.detail
return if Conf['Enable 4chan\'s Extension']
settings = JSON.parse(localStorage.getItem '4chan-settings') or {} settings = JSON.parse(localStorage.getItem '4chan-settings') or {}
return if settings.disableAll return if settings.disableAll
settings.disableAll = true settings.disableAll = true
@ -73,7 +74,7 @@ Settings =
<div class=credits> <div class=credits>
<a href='<%= meta.page %>' target=_blank><%= meta.name %></a> | <a href='<%= meta.page %>' target=_blank><%= meta.name %></a> |
<a href='<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md' target=_blank>#{g.VERSION}</a> | <a href='<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md' target=_blank>#{g.VERSION}</a> |
<a href='<%= meta.repo %>blob/<%= meta.mainBranch %>/CONTRIBUTING.md#reporting-bugs' target=_blank>Issues</a> | <a href='<%= meta.repo %>blob/<%= meta.mainBranch %>/CONTRIBUTING.md#reporting-bugs-and-suggestions' target=_blank>Issues</a> |
<a href=javascript:; class=close title=Close>×</a> <a href=javascript:; class=close title=Close>×</a>
</div> </div>
</nav> </nav>
@ -403,6 +404,11 @@ Settings =
rice: (section) -> rice: (section) ->
section.innerHTML = """ section.innerHTML = """
<fieldset>
<legend>Archiver</legend>
Select an Archiver for this board:
<select name=archiver></select>
</fieldset>
<fieldset> <fieldset>
<legend>Custom Board Navigation <span class=warning #{if Conf['Custom Board Navigation'] then 'hidden' else ''}>is disabled.</span></legend> <legend>Custom Board Navigation <span class=warning #{if Conf['Custom Board Navigation'] then 'hidden' else ''}>is disabled.</span></legend>
<div><input name=boardnav class=field spellcheck=false></div> <div><input name=boardnav class=field spellcheck=false></div>
@ -416,6 +422,7 @@ Settings =
</div> </div>
<div>Board link: <code>board</code></div> <div>Board link: <code>board</code></div>
<div>Title link: <code>board-title</code></div> <div>Title link: <code>board-title</code></div>
<div>Board link (Replace with title when on that board): <code>board-replace</code></div>
<div>Full text link: <code>board-full</code></div> <div>Full text link: <code>board-full</code></div>
<div>Custom text link: <code>board-text:"VIP Board"</code></div> <div>Custom text link: <code>board-text:"VIP Board"</code></div>
<div>Index-only link: <code>board-index</code></div> <div>Index-only link: <code>board-index</code></div>
@ -479,6 +486,21 @@ Settings =
else else
'input' 'input'
$.on input, event, $.cb.value $.on input, event, $.cb.value
# Archiver
archiver = $ 'select[name=archiver]', section
toSelect = Redirect.select g.BOARD.ID
toSelect = ['No Archive Available'] unless toSelect[0]
$.add archiver, $.el('option', {textContent: name}) for name in toSelect
if toSelect[1]
Conf['archivers'][g.BOARD]
archiver.value = Conf['archivers'][g.BOARD] or toSelect[0]
$.on archiver, 'change', ->
Conf['archivers'][g.BOARD] = @value
$.set 'archivers', Conf.archivers
$.get items, (items) -> $.get items, (items) ->
for key, val of items for key, val of items
input = inputs[key] input = inputs[key]
@ -1096,4 +1118,4 @@ Settings =
userThemes = item["userThemes"] userThemes = item["userThemes"]
userThemes[@id] = Themes[@id] userThemes[@id] = Themes[@id]
$.set 'userThemes', userThemes $.set 'userThemes', userThemes
$.rm @ $.rm @