Merge branch 'v3' into Av2
Conflicts: builds/4chan-X.meta.js css/style.css lib/ui.coffee package.json src/features.coffee src/qr.coffee
This commit is contained in:
commit
30880e499a
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,3 @@
|
||||
builds/
|
||||
node_modules/
|
||||
tmp/
|
||||
4chan_x.user.js
|
||||
|
||||
8996
4chan-X.user.js
8996
4chan-X.user.js
File diff suppressed because one or more lines are too long
16
CHANGELOG.md
16
CHANGELOG.md
@ -1,3 +1,17 @@
|
||||
- Fix resurrecting dead quotelinks on HTTP.
|
||||
|
||||
### 3.0.6 - *2013-04-14*
|
||||
|
||||
- Fix regression concerning thread selection when quoting on the index.
|
||||
|
||||
### 3.0.5 - *2013-04-14*
|
||||
|
||||
- `Scroll to Last Read Post` is now optional, enabled by default.
|
||||
- The QR won't auto-hide when auto-hide is enabled and one of its input is focused. Doesn't work on Firefox.
|
||||
- Added the `Remember QR Size` setting back in, disabled by default. Only on Firefox.
|
||||
- Fix QR remembering the file spoiler state when it shouldn't.
|
||||
- Fix QR cooldown in Opera.
|
||||
|
||||
### 3.0.4 - *2013-04-11*
|
||||
|
||||
- More minor fixes.
|
||||
@ -9,7 +23,7 @@
|
||||
### 3.0.2 - *2013-04-09*
|
||||
|
||||
- Added a setting in the Header's menu to move it at the bottom of the screen.
|
||||
- Added Cooldown setting back in.
|
||||
- Added the `Cooldown` setting back in.
|
||||
- Fixed the Header going above posts when following quotelinks for example.
|
||||
- Fixed a bug where dead quotelinks would disappear.
|
||||
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
module.exports = (grunt) ->
|
||||
|
||||
pkg = grunt.file.readJSON 'package.json'
|
||||
concatOptions =
|
||||
process:
|
||||
data: pkg
|
||||
|
||||
# Project configuration.
|
||||
grunt.initConfig
|
||||
pkg: pkg
|
||||
concat:
|
||||
coffee:
|
||||
options:
|
||||
process:
|
||||
data: pkg
|
||||
options: concatOptions
|
||||
src: [
|
||||
'src/config.coffee'
|
||||
'src/globals.coffee'
|
||||
@ -27,9 +28,7 @@ module.exports = (grunt) ->
|
||||
dest: 'tmp/script.coffee'
|
||||
|
||||
crx:
|
||||
options:
|
||||
process:
|
||||
data: pkg
|
||||
options: concatOptions
|
||||
files:
|
||||
'builds/crx/manifest.json': 'src/manifest.json'
|
||||
'builds/crx/script.js': [
|
||||
@ -38,9 +37,7 @@ module.exports = (grunt) ->
|
||||
]
|
||||
|
||||
userjs:
|
||||
options:
|
||||
process:
|
||||
data: pkg
|
||||
options: concatOptions
|
||||
src: [
|
||||
'src/metadata.js'
|
||||
'src/banner.js'
|
||||
@ -49,12 +46,10 @@ module.exports = (grunt) ->
|
||||
dest: 'builds/<%= pkg.name %>.js'
|
||||
|
||||
userscript:
|
||||
options:
|
||||
process:
|
||||
data: pkg
|
||||
options: concatOptions
|
||||
files:
|
||||
'<%= pkg.name %>.meta.js': 'src/metadata.js'
|
||||
'<%= pkg.name %>.user.js': [
|
||||
'builds/<%= pkg.name %>.meta.js': 'src/metadata.js'
|
||||
'builds/<%= pkg.name %>.user.js': [
|
||||
'src/metadata.js'
|
||||
'src/banner.js'
|
||||
'tmp/script.js'
|
||||
@ -170,22 +165,29 @@ module.exports = (grunt) ->
|
||||
|
||||
grunt.registerTask 'patch', [
|
||||
'bump'
|
||||
'reloadPkh'
|
||||
'updcl:3'
|
||||
]
|
||||
|
||||
grunt.registerTask 'minor', [
|
||||
'bump:minor'
|
||||
'reloadPkh'
|
||||
'updcl:2'
|
||||
]
|
||||
|
||||
grunt.registerTask 'major', [
|
||||
'bump:major'
|
||||
'reloadPkh'
|
||||
'updcl:1'
|
||||
]
|
||||
|
||||
grunt.registerTask 'updcl', 'Update the changelog', (i) ->
|
||||
grunt.registerTask 'reloadPkg', 'Reload the package', ->
|
||||
# Update the `pkg` object with the new version.
|
||||
pkg = grunt.file.readJSON('package.json');
|
||||
pkg = grunt.file.readJSON('package.json')
|
||||
concatOptions.process.data = pkg
|
||||
grunt.log.ok('pkg reloaded.')
|
||||
|
||||
grunt.registerTask 'updcl', 'Update the changelog', (i) ->
|
||||
# i is the number of #s for markdown.
|
||||
version = []
|
||||
version.length = +i + 1
|
||||
|
||||
File diff suppressed because one or more lines are too long
13596
builds/appchan-x.user.js
Normal file
13596
builds/appchan-x.user.js
Normal file
File diff suppressed because one or more lines are too long
BIN
builds/crx/icon128.png
Normal file
BIN
builds/crx/icon128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 255 B |
BIN
builds/crx/icon16.png
Normal file
BIN
builds/crx/icon16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 185 B |
BIN
builds/crx/icon48.png
Normal file
BIN
builds/crx/icon48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 238 B |
21
builds/crx/manifest.json
Normal file
21
builds/crx/manifest.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "appchan x",
|
||||
"version": "2.0.0",
|
||||
"manifest_version": 2,
|
||||
"description": "The most comprehensive 4chan userscript.",
|
||||
"icons": {
|
||||
"16": "icon16.png",
|
||||
"48": "icon48.png",
|
||||
"128": "icon128.png"
|
||||
},
|
||||
"content_scripts": [{
|
||||
"js": ["script.js"],
|
||||
"matches": ["*://api.4chan.org/*","*://boards.4chan.org/*","*://images.4chan.org/*","*://sys.4chan.org/*"],
|
||||
"run_at": "document_start"
|
||||
}],
|
||||
"homepage_url": "http://zixaphir.github.com/appchan-x/",
|
||||
"minimum_chrome_version": "26",
|
||||
"permissions": [
|
||||
"storage"
|
||||
]
|
||||
}
|
||||
13521
builds/crx/script.js
Normal file
13521
builds/crx/script.js
Normal file
File diff suppressed because one or more lines are too long
@ -6,9 +6,7 @@ body > div.navLinks > a:first-of-type::after,
|
||||
#{if Conf['Announcements'] is 'slideout' then '#globalMessage::after,' else ''}
|
||||
#boardNavDesktopFoot::after,
|
||||
body > a[style="cursor: pointer; float: right;"]::after,
|
||||
#imgControls .expand-all-shortcut,
|
||||
#imgControls .contract-all-shortcut,
|
||||
#imgControls .menu-button,
|
||||
#img-controls,
|
||||
#catalog::after,
|
||||
#fappeTyme {
|
||||
z-index: 18;
|
||||
@ -34,9 +32,7 @@ body > div.navLinks > a:first-of-type:hover,
|
||||
#{if Conf['Announcements'] is 'slideout' then '#globalMessage:hover,' else ''}
|
||||
#boardNavDesktopFoot:hover,
|
||||
body > a[style="cursor: pointer; float: right;"]:hover,
|
||||
#imgControls .expand-all-shortcut,
|
||||
#imgControls .contract-all-shortcut,
|
||||
#imgControls .menu-button,
|
||||
#img-controls,
|
||||
#catalog:hover {
|
||||
z-index: 17;
|
||||
}
|
||||
@ -62,8 +58,7 @@ body > a[style="cursor: pointer; float: right;"]::after {
|
||||
cursor: pointer;
|
||||
background-position: 0 -75px;
|
||||
}
|
||||
#imgControls .expand-all-shortcut,
|
||||
#imgControls .contract-all-shortcut {
|
||||
#img-controls {
|
||||
background-position: 0 -90px;
|
||||
}
|
||||
#navtopright .exlinksOptionsLink::after {
|
||||
@ -78,9 +73,7 @@ body > a[style="cursor: pointer; float: right;"]::after {
|
||||
}
|
||||
#boardNavDesktopFoot:hover::after,
|
||||
#globalMessage:hover::after,
|
||||
#imgControls .expand-all-shortcut:hover,
|
||||
#imgControls .contract-all-shortcut:hover,
|
||||
#imgControls .menu-button:hover,
|
||||
#img-controls:hover,
|
||||
#navlinks a:hover,
|
||||
#appchanOptions:hover,
|
||||
#navtopright .exlinksOptionsLink:hover::after,
|
||||
|
||||
@ -24,12 +24,7 @@ body > a[style="cursor: pointer; float: right;"]::after {
|
||||
#{align}: #{position[i++]}px;
|
||||
}
|
||||
/* Expand Images */
|
||||
#imgControls .expand-all-shortcut,
|
||||
#imgControls .contract-all-shortcut {
|
||||
#{align}: #{position[i++]}px;
|
||||
}
|
||||
/* Expand Images Menu */
|
||||
#imgControls .menu-button {
|
||||
#img-controls {
|
||||
#{align}: #{position[i++]}px;
|
||||
}
|
||||
/* 4chan Catalog */
|
||||
@ -66,9 +61,7 @@ div.navLinks > a:first-of-type::after {
|
||||
#appchanOptions,
|
||||
#watcher::after,
|
||||
#globalMessage::after,
|
||||
#imgControls .expand-all-shortcut,
|
||||
#imgControls .contract-all-shortcut,
|
||||
#imgControls .menu-button,
|
||||
#img-controls,
|
||||
#fappeTyme,
|
||||
div.navLinks > a:first-of-type::after,
|
||||
#catalog::after,
|
||||
|
||||
@ -1,11 +1,6 @@
|
||||
|
||||
/* Expand Images */
|
||||
#imgControls .expand-all-shortcut,
|
||||
#imgControls .contract-all-shortcut {
|
||||
top: #{position[i++]}px;
|
||||
}
|
||||
/* Expand Images Menu */
|
||||
#imgControls .menu-button {
|
||||
#img-controls {
|
||||
top: #{position[i++]}px;
|
||||
}
|
||||
/* 4chan X Options */
|
||||
@ -64,9 +59,7 @@ div.navLinks > a:first-of-type::after {
|
||||
#appchanOptions,
|
||||
#boardNavDesktopFoot::after,
|
||||
#globalMessage::after,
|
||||
#imgControls .expand-all-shortcut,
|
||||
#imgControls .contract-all-shortcut,
|
||||
#imgControls .menu-button,
|
||||
#img-controls,
|
||||
#fappeTyme,
|
||||
#{if _conf["Slideout Watcher"] then "#watcher::after," else ""}
|
||||
body > a[style="cursor: pointer; float: right;"]::after,
|
||||
|
||||
131
css/layout.css
131
css/layout.css
@ -118,7 +118,7 @@ hr {
|
||||
opacity: 0.5;
|
||||
}
|
||||
/* Symbols */
|
||||
.dropmarker {
|
||||
.drop-marker {
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
margin: 2px 2px 3px;
|
||||
@ -126,6 +126,12 @@ hr {
|
||||
border-right: .3em solid transparent;
|
||||
border-left: .3em solid transparent;
|
||||
}
|
||||
.brackets-wrap::before {
|
||||
content: "\\00a0[";
|
||||
}
|
||||
.brackets-wrap::after {
|
||||
content: "]\\00a0";
|
||||
}
|
||||
/* Thread / Reply Nav */
|
||||
#navlinks a {
|
||||
position: fixed;
|
||||
@ -140,30 +146,7 @@ hr {
|
||||
#boardNavDesktop {
|
||||
z-index: 6;
|
||||
border-width: 1px;
|
||||
#{{
|
||||
"sticky top": "
|
||||
position: fixed;
|
||||
top: 0;
|
||||
border-top-width: 0;
|
||||
#{if _conf["Rounded Edges"] then "border-radius: 0 0 3px 3px;" else ""}"
|
||||
|
||||
"sticky bottom": "
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
border-bottom-width: 0;
|
||||
#{if _conf["Rounded Edges"] then "border-radius: 3px 3px 0 0;" else ""}"
|
||||
|
||||
"top": "
|
||||
position: absolute;
|
||||
top: 0;
|
||||
border-top-width: 0;
|
||||
#{if _conf["Rounded Edges"] then "border-radius: 0 0 3px 3px;" else ""}"
|
||||
|
||||
"hide": "
|
||||
position: fixed;
|
||||
top: 110%;"
|
||||
|
||||
}[_conf['Boards Navigation']]}
|
||||
#{
|
||||
if _conf['4chan SS Navigation'] then "
|
||||
left: 0;
|
||||
@ -182,6 +165,71 @@ else "
|
||||
" else ""}
|
||||
text-align: #{_conf["Navigation Alignment"]};
|
||||
}
|
||||
.fixed #boardNavDesktop {
|
||||
position: fixed;
|
||||
}
|
||||
.top #boardNavDesktop {
|
||||
top: 0;
|
||||
border-top-width: 0;
|
||||
#{if _conf["Rounded Edges"] then "border-radius: 0 0 3px 3px;" else ""}"
|
||||
}
|
||||
.fixed.bottom #boardNavDesktop {
|
||||
bottom: 0;
|
||||
border-bottom-width: 0;
|
||||
#{if _conf["Rounded Edges"] then "border-radius: 3px 3px 0 0;" else ""}"
|
||||
}
|
||||
.hide #boardNavDesktop {
|
||||
position: fixed;
|
||||
top: 110%;
|
||||
bottom: auto;
|
||||
}
|
||||
/* Header Autohide */
|
||||
.fixed #boardNavDesktop.autohide:not(:hover) {
|
||||
box-shadow: none;
|
||||
transition: all .8s .6s cubic-bezier(.55, .055, .675, .19);
|
||||
}
|
||||
.fixed.top #boardNavDesktop.autohide:not(:hover) {
|
||||
margin-bottom: -1em;
|
||||
-webkit-transform: translateY(-100%);
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
.fixed.bottom #boardNavDesktop.autohide:not(:hover) {
|
||||
-webkit-transform: translateY(100%);
|
||||
transform: translateY(100%);
|
||||
}
|
||||
#toggle-header-bar {
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 10px;
|
||||
position: absolute;
|
||||
}
|
||||
#boardNavDesktop #toggle-header-bar {
|
||||
display: none;
|
||||
}
|
||||
.fixed #boardNavDesktop #toggle-header-bar {
|
||||
display: block;
|
||||
}
|
||||
.fixed #boardNavDesktop #toggle-header-bar {
|
||||
cursor: n-resize;
|
||||
}
|
||||
.fixed.top boardNavDesktop #toggle-header-bar {
|
||||
top: 100%;
|
||||
}
|
||||
.fixed.bottom #boardNavDesktop #toggle-header-bar {
|
||||
bottom: 100%;
|
||||
}
|
||||
.fixed #boardNavDesktop #header-bar.autohide #toggle-header-bar {
|
||||
cursor: s-resize;
|
||||
}
|
||||
/* Notifications */
|
||||
.notification {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
width: 300px;
|
||||
}
|
||||
.close {
|
||||
float: right;
|
||||
}
|
||||
/* Pagination */
|
||||
.pagelist {
|
||||
border-width: 1px;
|
||||
@ -443,27 +491,6 @@ else "
|
||||
z-index: 10;
|
||||
}
|
||||
/* Image Expansion */
|
||||
#imgControls a.menu-button {
|
||||
margin: 0;
|
||||
margin: 1px;
|
||||
border: 2px solid;
|
||||
border-radius: 10px;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
#{Style.sizing}: border-box;
|
||||
}
|
||||
#imgControls .menu-button::after {
|
||||
content: '';
|
||||
font-size: 10px;
|
||||
position: absolute;
|
||||
bottom: 50%;
|
||||
right: 50%;
|
||||
#{agent}transform: translate(50%, 60%);
|
||||
display: block;
|
||||
border-top: 6px solid rgb(130, 130, 130);
|
||||
border-left: 3px solid transparent;
|
||||
border-right: 3px solid transparent;
|
||||
}
|
||||
.fit-width .full-image {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
@ -691,7 +718,7 @@ hide: "
|
||||
.menu-button {
|
||||
position: relative;
|
||||
}
|
||||
.menu-button,
|
||||
.post .menu-button,
|
||||
.hide-thread-button,
|
||||
.hide-reply-button {
|
||||
float: right;
|
||||
@ -1037,7 +1064,7 @@ input:checked + .rice {
|
||||
#{agent}transform: translateX(#{xOffset}93%);
|
||||
}
|
||||
#qr:hover,
|
||||
#qr.focus,
|
||||
#qr.has-focus,
|
||||
#qr.dump {
|
||||
#{agent}transform: translate(0);
|
||||
}"
|
||||
@ -1051,7 +1078,7 @@ input:checked + .rice {
|
||||
#{agent}transform: translateX(#{xOffset}100%);
|
||||
}
|
||||
#qr:hover,
|
||||
#qr.focus,
|
||||
#qr.has-focus,
|
||||
#qr.dump {
|
||||
#{agent}transform: translateX(0);
|
||||
}
|
||||
@ -1067,7 +1094,7 @@ input:checked + .rice {
|
||||
cursor: default;
|
||||
}
|
||||
#qr:hover #qrtab,
|
||||
#qr.focus #qrtab,
|
||||
#qr.has-focus #qrtab,
|
||||
#qr.dump #qrtab {
|
||||
opacity: 0;
|
||||
#{Style.sidebarLocation[0]}: #{252 + Style.sidebarOffset.W}px;
|
||||
@ -1090,7 +1117,7 @@ input:checked + .rice {
|
||||
#{agent}transition: opacity .3s ease-in-out 1s;
|
||||
}
|
||||
#qr:hover,
|
||||
#qr.focus,
|
||||
#qr.has-focus,
|
||||
#qr.dump {
|
||||
opacity: 1;
|
||||
#{agent}transition: opacity .3s linear;
|
||||
@ -1103,7 +1130,7 @@ unless _conf['Post Form Style'] is 'tabbed slideout'
|
||||
unless _conf['Post Form Style'] is 'float' or _conf['Show Post Form Header']
|
||||
"#qrtab { display: none; }"
|
||||
else unless _conf['Post Form Style'] is 'slideout'
|
||||
".autohide:not(:hover):not(.focus) > form { display: none !important; }"
|
||||
".autohide:not(:hover):not(.has-focus) > form { display: none !important; }"
|
||||
else ""
|
||||
) + "#qrtab { margin-bottom: 1px; }"
|
||||
else ""}
|
||||
@ -1114,7 +1141,7 @@ if _conf['Post Form Style'] isnt 'float' and _conf["Post Form Slideout Transitio
|
||||
#{agent}transition: #{agent}transform .3s ease-in-out 1s;
|
||||
}
|
||||
#qr:hover,
|
||||
#qr.focus,
|
||||
#qr.has-focus,
|
||||
#qr.dump {
|
||||
#{agent}transition: #{agent}transform .3s linear;
|
||||
}
|
||||
|
||||
@ -211,10 +211,6 @@ a[style="cursor: pointer; float: right;"] ~ div[style^="width: 100%;"] > table {
|
||||
#navlinks a:last-of-type {
|
||||
border-top: 11px solid rgb(130,130,130);
|
||||
}
|
||||
#imgControls .menu-button {
|
||||
border-color: rgb(130,130,130);
|
||||
color: rgb(130,130,130);
|
||||
}
|
||||
#charCount {
|
||||
color: #{(if Style.lightTheme then "rgba(0,0,0,0.7)" else "rgba(255,255,255,0.7)")};
|
||||
}
|
||||
@ -396,8 +392,7 @@ a .name {
|
||||
#globalMessage::after,
|
||||
#boardNavDesktopFoot::after,
|
||||
a[style="cursor: pointer; float: right;"]::after,
|
||||
#imgControls .expand-all-shortcut,
|
||||
#imgControls .contract-all-shortcut,
|
||||
#img-controls,
|
||||
#catalog::after,
|
||||
#fappeTyme {
|
||||
background-image: url('#{icons}');
|
||||
|
||||
12
lib/$.coffee
12
lib/$.coffee
@ -140,8 +140,16 @@ $.extend $,
|
||||
el.classList.toggle className
|
||||
hasClass: (el, className) ->
|
||||
el.classList.contains className
|
||||
rm: (el) ->
|
||||
el.parentNode.removeChild el
|
||||
rm: do ->
|
||||
if 'remove' of Element.prototype
|
||||
(el) -> el.remove()
|
||||
else
|
||||
(el) -> el.parentNode?.removeChild el
|
||||
rmAll: (root) ->
|
||||
# jsperf.com/emptify-element
|
||||
while node = root.firstChild
|
||||
$.rm node
|
||||
return
|
||||
tn: (s) ->
|
||||
d.createTextNode s
|
||||
frag: ->
|
||||
|
||||
@ -4,7 +4,8 @@ UI = do ->
|
||||
className: 'dialog'
|
||||
innerHTML: html
|
||||
id: id
|
||||
el.style.cssText = localStorage.getItem("#{g.NAMESPACE}#{id}.position") or position
|
||||
$.get "#{id}.position", position, (item) ->
|
||||
el.style.cssText = item["#{id}.position"]
|
||||
move = $ '.move', el
|
||||
$.on move, 'touchstart mousedown', dragstart
|
||||
for child in move.children
|
||||
@ -280,7 +281,7 @@ UI = do ->
|
||||
else # mouseup
|
||||
$.off d, 'mousemove', @move
|
||||
$.off d, 'mouseup', @up
|
||||
localStorage.setItem "#{g.NAMESPACE}#{@id}.position", @style.cssText
|
||||
$.set "#{@id}.position", @style.cssText
|
||||
|
||||
hoverstart = ({root, el, latestEvent, endEvents, asapTest, cb, close}) ->
|
||||
o = {
|
||||
|
||||
@ -240,7 +240,6 @@ Style =
|
||||
$ '#navtopright .exlinksOptionsLink', d.body
|
||||
notCatalog and $ 'body > a[style="cursor: pointer; float: right;"]', d.body
|
||||
notEither and _conf['Image Expansion']
|
||||
notEither and _conf['Image Expansion']
|
||||
notEither
|
||||
g.VIEW is 'thread'
|
||||
notEither and _conf['Fappe Tyme']
|
||||
@ -265,7 +264,6 @@ Style =
|
||||
position = aligner(
|
||||
2 + (if _conf["4chan Banner"] is "at sidebar top" then (Style.logoOffset + 19) else 0)
|
||||
[
|
||||
notEither and _conf['Image Expansion']
|
||||
notEither and _conf['Image Expansion']
|
||||
notCatalog
|
||||
_conf['Slideout Navigation'] isnt 'hide'
|
||||
@ -314,12 +312,11 @@ Style =
|
||||
Style.padding.pages.property = _conf["Pagination"].split(" ")
|
||||
Style.padding.pages.property = Style.padding.pages.property[Style.padding.pages.property.length - 1]
|
||||
css = "body::before {\n"
|
||||
if _conf["4chan SS Emulation"]
|
||||
if Style.padding.pages and (_conf["Pagination"] is "sticky top" or _conf["Pagination"] is "sticky bottom")
|
||||
css += " #{Style.padding.pages.property}: #{Style.padding.pages.offsetHeight}px !important;\n"
|
||||
if Style.padding.pages and (_conf["Pagination"] is "sticky top" or _conf["Pagination"] is "sticky bottom")
|
||||
css += " #{Style.padding.pages.property}: #{Style.padding.pages.offsetHeight}px !important;\n"
|
||||
|
||||
if _conf["Boards Navigation"] is "sticky top" or _conf["Boards Navigation"] is "sticky bottom"
|
||||
css += " #{Style.padding.nav.property}: #{Style.padding.nav.offsetHeight}px !important;\n"
|
||||
if _conf["Boards Navigation"] is "sticky top" or _conf["Boards Navigation"] is "sticky bottom"
|
||||
css += " #{Style.padding.nav.property}: #{Style.padding.nav.offsetHeight}px !important;\n"
|
||||
|
||||
css += """
|
||||
}
|
||||
|
||||
@ -13,6 +13,10 @@ Config =
|
||||
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': [
|
||||
false
|
||||
'Show custom links instead of the full board list.'
|
||||
@ -225,6 +229,12 @@ Config =
|
||||
false
|
||||
'Remember the subject field, instead of resetting after posting.'
|
||||
]
|
||||
<% if (type === 'userscript') { %>
|
||||
'Remember QR Size': [
|
||||
false
|
||||
'Remember the size of the Quick reply.'
|
||||
]
|
||||
<% } %>
|
||||
'Remember Spoiler': [
|
||||
false
|
||||
'Remember the spoiler state, instead of resetting after posting.'
|
||||
@ -586,11 +596,6 @@ Config =
|
||||
]
|
||||
|
||||
Navigation:
|
||||
'Boards Navigation': [
|
||||
'sticky top'
|
||||
'The position of 4chan board navigation'
|
||||
['sticky top', 'sticky bottom', 'top', 'hide']
|
||||
]
|
||||
'Navigation Alignment': [
|
||||
'center'
|
||||
'Change the text alignment of the navigation.'
|
||||
@ -716,7 +721,11 @@ http://iqdb.org/?url=%TURL
|
||||
#//archive.installgentoo.net/%board/image/%MD5;text:View same on installgentoo /%board/
|
||||
"""
|
||||
|
||||
'Custom CSS': false
|
||||
'Boards Navigation': 'sticky top'
|
||||
|
||||
'Custom CSS': false
|
||||
|
||||
'Bottom header': false
|
||||
|
||||
'Header auto-hide': false
|
||||
|
||||
|
||||
@ -1,13 +1,37 @@
|
||||
Header =
|
||||
init: ->
|
||||
@bar = $.el 'div',
|
||||
id: 'notifications'
|
||||
@shortcuts = $.el 'span',
|
||||
id: 'shortcuts'
|
||||
@hover = $.el 'div',
|
||||
id: 'hoverUI'
|
||||
@menuButton = $.el 'span',
|
||||
className: 'menu-button'
|
||||
innerHTML: '<a class=brackets-wrap href=javascript:;><i class=drop-marker></i></a>'
|
||||
|
||||
$.on window, 'load hashchange', Header.hashScroll
|
||||
@menu = new UI.Menu 'header'
|
||||
$.on @menuButton, 'click', @menuToggle
|
||||
$.on @toggle, 'mousedown', @toggleBarVisibility
|
||||
$.on window, 'load hashchange', Header.hashScroll
|
||||
|
||||
@positionToggler = $.el 'span',
|
||||
textContent: 'Header Position'
|
||||
className: 'header-position-link'
|
||||
|
||||
{createSubEntry} = Header
|
||||
subEntries = []
|
||||
for setting in ['sticky top', 'sticky bottom', 'top', 'hide']
|
||||
subEntries.push createSubEntry setting
|
||||
|
||||
$.event 'AddMenuEntry',
|
||||
type: 'header'
|
||||
el: @positionToggler
|
||||
order: 108
|
||||
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
|
||||
|
||||
@ -20,18 +44,48 @@ Header =
|
||||
$.ready ->
|
||||
$.add d.body, Header.hover
|
||||
|
||||
setBoardList: ->
|
||||
bar: $.el 'div',
|
||||
id: 'notifications'
|
||||
|
||||
shortcuts: $.el 'span',
|
||||
id: 'shortcuts'
|
||||
|
||||
hover: $.el 'div',
|
||||
id: 'hoverUI'
|
||||
|
||||
toggle: $.el 'div',
|
||||
id: 'toggle-header-bar'
|
||||
|
||||
createSubEntry: (setting)->
|
||||
label = $.el 'label',
|
||||
textContent: "#{setting}"
|
||||
|
||||
$.on label, 'click', Header.setBarPosition
|
||||
|
||||
el: label
|
||||
|
||||
setBoardList: ->
|
||||
Header.nav = nav = $.id 'boardNavDesktop'
|
||||
if a = $ "a[href*='/#{g.BOARD}/']", nav
|
||||
a.className = 'current'
|
||||
|
||||
fullBoardList = $.el 'span',
|
||||
id: 'full-board-list'
|
||||
hidden: true
|
||||
|
||||
customBoardList = $.el 'span',
|
||||
id: 'custom-board-list'
|
||||
|
||||
Header.setBarPosition.call textContent: "#{Conf['Boards Navigation']}"
|
||||
$.sync 'Boards Navigation', ->
|
||||
Header.setBarPosition.call textContent: "#{Conf['Boards Navigation']}"
|
||||
|
||||
Header.setBarVisibility Conf['Header auto-hide']
|
||||
$.sync 'Header auto-hide', Header.setBarVisibility
|
||||
|
||||
$.add fullBoardList, [nav.childNodes...]
|
||||
$.add nav, [customBoardList, fullBoardList, Header.shortcuts, $ '#navtopright', fullBoardList, Header.bar]
|
||||
$.add nav, [Header.menuButton, customBoardList, fullBoardList, Header.shortcuts, $('#navtopright', fullBoardList), Header.toggle]
|
||||
$.add d.body, Header.bar
|
||||
|
||||
if Conf['Custom Board Navigation']
|
||||
Header.generateBoardList Conf['boardnav']
|
||||
@ -47,7 +101,7 @@ Header =
|
||||
|
||||
generateBoardList: (text) ->
|
||||
list = $ '#custom-board-list', Header.nav
|
||||
list.innerHTML = null
|
||||
$.rmAll list
|
||||
return unless text
|
||||
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) ->
|
||||
@ -92,8 +146,41 @@ Header =
|
||||
custom.hidden = !showBoardList
|
||||
full.hidden = showBoardList
|
||||
|
||||
setBarPosition: ->
|
||||
$.event 'CloseMenu'
|
||||
|
||||
switch @textContent
|
||||
when 'sticky top'
|
||||
$.addClass doc, 'top'
|
||||
$.addClass doc, 'fixed'
|
||||
$.rmClass doc, 'bottom'
|
||||
$.rmClass doc, 'hide'
|
||||
when 'sticky bottom'
|
||||
$.rmClass doc, 'top'
|
||||
$.addClass doc, 'fixed'
|
||||
$.addClass doc, 'bottom'
|
||||
$.rmClass doc, 'hide'
|
||||
when 'top'
|
||||
$.addClass doc, 'top'
|
||||
$.rmClass doc, 'fixed'
|
||||
$.rmClass doc, 'bottom'
|
||||
$.rmClass doc, 'hide'
|
||||
when 'hide'
|
||||
$.rmClass doc, 'top'
|
||||
$.rmClass doc, 'fixed'
|
||||
$.rmClass doc, 'bottom'
|
||||
$.addClass doc, 'hide'
|
||||
|
||||
Conf['Boards Navigation'] = @textContent
|
||||
$.set 'Boards Navigation', @textContent
|
||||
|
||||
setBarVisibility: (hide) ->
|
||||
Header.headerToggler.firstElementChild.checked = hide
|
||||
(if hide then $.addClass else $.rmClass) Header.nav, 'autohide'
|
||||
|
||||
hashScroll: ->
|
||||
return unless post = $.id @location.hash[1..]
|
||||
return if (Get.postFromRoot post).isHidden
|
||||
Header.scrollToPost post
|
||||
|
||||
scrollToPost: (post) ->
|
||||
@ -103,12 +190,29 @@ Header =
|
||||
top += - headRect.top - headRect.height
|
||||
(if $.engine is 'webkit' then d.body else doc).scrollTop += top
|
||||
|
||||
toggleBarVisibility: (e) ->
|
||||
return if e.type is 'mousedown' and e.button isnt 0 # not LMB
|
||||
hide = if @nodeName is 'INPUT'
|
||||
@checked
|
||||
else
|
||||
!$.hasClass Header.nav, 'autohide'
|
||||
Header.setBarVisibility hide
|
||||
message = if hide
|
||||
'The header bar will automatically hide itself.'
|
||||
else
|
||||
'The header bar will remain visible.'
|
||||
new Notification 'info', message, 2
|
||||
$.set 'Header auto-hide', hide
|
||||
|
||||
addShortcut: (el) ->
|
||||
shortcut = $.el 'span',
|
||||
className: 'shortcut'
|
||||
$.add shortcut, [$.tn(' ['), el, $.tn(']')]
|
||||
$.add Header.shortcuts, shortcut
|
||||
|
||||
menuToggle: (e) ->
|
||||
Header.menu.toggle e, @, g
|
||||
|
||||
createNotification: (e) ->
|
||||
{type, content, lifetime, cb} = e.detail
|
||||
notif = new Notification type, content, lifetime
|
||||
@ -144,7 +248,7 @@ class Notification
|
||||
setTimeout @close, @timeout * $.SECOND if @timeout
|
||||
|
||||
close = ->
|
||||
$.rm @el if @el.parentNode
|
||||
$.rm @el
|
||||
|
||||
CatalogLinks =
|
||||
init: ->
|
||||
@ -193,7 +297,6 @@ CatalogLinks =
|
||||
else
|
||||
"//boards.4chan.org/#{board}/catalog"
|
||||
)
|
||||
|
||||
ready: ->
|
||||
if catalogLink = ($('.pages.cataloglink a', d.body) or $ '[href=".././catalog"]', d.body)
|
||||
if g.VIEW isnt 'thread'
|
||||
@ -967,7 +1070,7 @@ Menu =
|
||||
(post) ->
|
||||
a or= $.el 'a',
|
||||
className: 'menu-button'
|
||||
innerHTML: '[<span class=dropmarker></span>]'
|
||||
innerHTML: '[<span class=drop-marker></span>]'
|
||||
href: 'javascript:;'
|
||||
clone = a.cloneNode true
|
||||
clone.setAttribute 'data-postid', post.fullID
|
||||
@ -1008,7 +1111,7 @@ ReportLink =
|
||||
|
||||
DeleteLink =
|
||||
init: ->
|
||||
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Delete Link'] or !Conf['Quick Reply']
|
||||
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Delete Link']
|
||||
|
||||
div = $.el 'div',
|
||||
className: 'delete-link'
|
||||
@ -1060,20 +1163,22 @@ DeleteLink =
|
||||
else
|
||||
$.id('delPassword').value
|
||||
|
||||
fileOnly = $.hasClass @, 'delete-file'
|
||||
|
||||
form =
|
||||
mode: 'usrdel'
|
||||
onlyimgdel: $.hasClass @, 'delete-file'
|
||||
onlyimgdel: fileOnly
|
||||
pwd: pwd
|
||||
form[post.ID] = 'delete'
|
||||
|
||||
link = @
|
||||
$.ajax $.id('delform').action.replace("/#{g.BOARD}/", "/#{post.board}/"),
|
||||
onload: -> DeleteLink.load link, post, @response
|
||||
onload: -> DeleteLink.load link, post, fileOnly, @response
|
||||
onerror: -> DeleteLink.error link
|
||||
,
|
||||
cred: true
|
||||
form: $.formData form
|
||||
load: (link, post, html) ->
|
||||
load: (link, post, fileOnly, html) ->
|
||||
tmpDoc = d.implementation.createHTMLDocument ''
|
||||
tmpDoc.documentElement.innerHTML = html
|
||||
if tmpDoc.title is '4chan - Banned' # Ban/warn check
|
||||
@ -1084,7 +1189,7 @@ DeleteLink =
|
||||
else
|
||||
if tmpDoc.title is 'Updating index...'
|
||||
# We're 100% sure.
|
||||
(post.origin or post).kill()
|
||||
(post.origin or post).kill fileOnly
|
||||
s = 'Deleted'
|
||||
link.textContent = s
|
||||
error: (link) ->
|
||||
@ -1360,7 +1465,7 @@ Keybinds =
|
||||
location.href = url
|
||||
|
||||
hl: (delta, thread) ->
|
||||
if Conf['Bottom header']
|
||||
if Conf['Fixed Header'] and Conf['Bottom header']
|
||||
topMargin = 0
|
||||
else
|
||||
headRect = Header.bar.getBoundingClientRect()
|
||||
@ -1398,7 +1503,13 @@ Keybinds =
|
||||
|
||||
Nav =
|
||||
init: ->
|
||||
return if g.VIEW is 'index' and !Conf['Index Navigation'] or g.VIEW is 'thread' and !Conf['Reply Navigation']
|
||||
switch g.VIEW
|
||||
when 'index'
|
||||
return unless Conf['Index Navigation']
|
||||
when 'thread'
|
||||
return unless Conf['Reply Navigation']
|
||||
else # catalog
|
||||
return
|
||||
|
||||
span = $.el 'span',
|
||||
id: 'navlinks'
|
||||
@ -1470,11 +1581,13 @@ Redirect =
|
||||
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'
|
||||
"//archive.foolz.us/_/api/chan/post/?board=#{boardID}&num=#{postID}"
|
||||
"https://archive.foolz.us/_/api/chan/post/?board=#{boardID}&num=#{postID}"
|
||||
when 'u'
|
||||
"//nsfw.foolz.us/_/api/chan/post/?board=#{boardID}&num=#{postID}"
|
||||
"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:
|
||||
@ -1490,9 +1603,9 @@ Redirect =
|
||||
Redirect.path '//archive.thedarkcave.org', 'foolfuuka', data
|
||||
when 'ck', 'fa', 'lit', 's4s'
|
||||
Redirect.path '//fuuka.warosu.org', 'fuuka', data
|
||||
when 'diy', 'sci'
|
||||
when 'diy', 'g', 'sci'
|
||||
Redirect.path '//archive.installgentoo.net', 'fuuka', data
|
||||
when 'cgl', 'g', 'mu', 'w'
|
||||
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
|
||||
@ -1865,10 +1978,10 @@ Get =
|
||||
|
||||
# Get rid of the side arrows.
|
||||
{nodes} = clone
|
||||
nodes.root.innerHTML = null
|
||||
$.rmAll nodes.root
|
||||
$.add nodes.root, nodes.post
|
||||
|
||||
root.innerHTML = null
|
||||
$.rmAll root
|
||||
$.add root, nodes.root
|
||||
fetchedPost: (req, boardID, threadID, postID, root, context) ->
|
||||
# In case of multiple callbacks for the same request,
|
||||
@ -2662,41 +2775,21 @@ ImageExpand =
|
||||
init: ->
|
||||
return if g.VIEW is 'catalog' or !Conf['Image Expansion']
|
||||
|
||||
wrapper = $.el 'div',
|
||||
id: 'imgControls'
|
||||
innerHTML: """
|
||||
<a class='expand-all-shortcut' title='Expand All Images' href='javascript:;'></a>
|
||||
<a class='menu-button' href='javascript:;'></a>
|
||||
"""
|
||||
@EAI = wrapper.firstElementChild
|
||||
@EAI = $.el 'a',
|
||||
id: 'img-controls'
|
||||
className: 'expand-all-shortcut'
|
||||
title: 'Expand All Images'
|
||||
href: 'javascript:;'
|
||||
|
||||
$.on @EAI, 'click', ImageExpand.cb.toggleAll
|
||||
|
||||
@opmenu = new UI.Menu 'imageexpand'
|
||||
$.on $('.menu-button', wrapper), 'click', @menuToggle
|
||||
|
||||
for type, config of Config.imageExpansion
|
||||
label = $.el 'label',
|
||||
innerHTML: "<input type=checkbox name='#{type}'> #{type}"
|
||||
input = label.firstElementChild
|
||||
if ['Fit width', 'Fit height'].contains type
|
||||
$.on input, 'change', ImageExpand.cb.setFitness
|
||||
|
||||
if config
|
||||
label.title = config[1]
|
||||
input.checked = Conf[type]
|
||||
$.event 'change', null, input
|
||||
$.on input, 'change', $.cb.checked
|
||||
|
||||
$.event 'AddMenuEntry',
|
||||
type: 'imageexpand'
|
||||
el: label
|
||||
|
||||
$.asap (-> $.id 'delform'), ->
|
||||
$.prepend $.id('delform'), wrapper
|
||||
|
||||
Post::callbacks.push
|
||||
name: 'Image Expansion'
|
||||
cb: @node
|
||||
cb: @node
|
||||
|
||||
$.asap (-> $.id 'delform'), ->
|
||||
$.prepend $.id('delform'), ImageExpand.EAI
|
||||
|
||||
node: ->
|
||||
return unless @file?.isImage
|
||||
{thumb} = @file
|
||||
@ -2843,7 +2936,7 @@ ImageExpand =
|
||||
|
||||
el = $.el 'span',
|
||||
textContent: 'Image Expansion'
|
||||
className: 'image-expansion-link'
|
||||
className: 'image-expansion-link'
|
||||
|
||||
{createSubEntry} = ImageExpand.menu
|
||||
subEntries = []
|
||||
@ -3190,18 +3283,22 @@ Unread =
|
||||
threadID: @ID
|
||||
defaultValue: 0
|
||||
Unread.addPosts posts
|
||||
if (hash = location.hash.match /\d+/) and post = @posts[hash[0]]
|
||||
Header.scrollToPost post.nodes.root
|
||||
else if Unread.posts.length
|
||||
# Scroll to before the first unread post.
|
||||
$.x('preceding-sibling::div[contains(@class,"postContainer")][1]', Unread.posts[0].nodes.root).scrollIntoView false
|
||||
else if posts.length
|
||||
# Scroll to the last read post.
|
||||
Header.scrollToPost posts[posts.length - 1].nodes.root
|
||||
$.on d, 'ThreadUpdate', Unread.onUpdate
|
||||
$.on d, 'scroll visibilitychange', Unread.read
|
||||
$.on d, 'visibilitychange', Unread.setLine if Conf['Unread Line']
|
||||
|
||||
return unless Conf['Scroll to Last Read Post']
|
||||
# Let the header's onload callback handle it.
|
||||
return if (hash = location.hash.match /\d+/) and hash[0] of @posts
|
||||
if Unread.posts.length
|
||||
# Scroll to before the first unread post.
|
||||
while root = $.x 'preceding-sibling::div[contains(@class,"postContainer")][1]', Unread.posts[0].nodes.root
|
||||
break unless (Get.postFromRoot root).isHidden
|
||||
root.scrollIntoView false
|
||||
else if posts.length
|
||||
# Scroll to the last read post.
|
||||
Header.scrollToPost posts[posts.length - 1].nodes.root
|
||||
|
||||
sync: ->
|
||||
lastReadPost = Unread.db.get
|
||||
boardID: Unread.thread.board.ID
|
||||
@ -3287,7 +3384,7 @@ Unread =
|
||||
{root} = post.nodes
|
||||
if root isnt $ '.thread > .replyContainer', root.parentNode # not the first reply
|
||||
$.before root, Unread.hr
|
||||
else if Unread.hr.parentNode
|
||||
else
|
||||
$.rm Unread.hr
|
||||
|
||||
update: <% if (type === 'crx') { %>(dontrepeat) <% } %>->
|
||||
@ -3581,7 +3678,7 @@ ThreadUpdater =
|
||||
unless d.hidden
|
||||
# Lower the max refresh rate limit on visible tabs.
|
||||
j = Math.min j, 7
|
||||
ThreadUpdater.seconds =
|
||||
ThreadUpdater.seconds =
|
||||
if Conf['Optional Increase']
|
||||
Math.max i, [0, 5, 10, 15, 20, 30, 60, 90, 120, 240, 300][j]
|
||||
else
|
||||
@ -3781,7 +3878,7 @@ ThreadWatcher =
|
||||
$.add div, [x, $.tn(' '), link]
|
||||
nodes.push div
|
||||
|
||||
ThreadWatcher.dialog.innerHTML = ''
|
||||
$.rmAll ThreadWatcher.dialog
|
||||
$.add ThreadWatcher.dialog, nodes
|
||||
|
||||
watched = watched[g.BOARD] or {}
|
||||
@ -4035,7 +4132,7 @@ Linkify =
|
||||
|
||||
embedder: (a) ->
|
||||
return [a] unless Conf['Embedding']
|
||||
|
||||
|
||||
callbacks = ->
|
||||
a.textContent = switch @status
|
||||
when 200, 304
|
||||
@ -4066,16 +4163,17 @@ Linkify =
|
||||
$.on embed, 'click', Linkify.toggle
|
||||
|
||||
if Conf['Link Title'] and (service = type.title)
|
||||
titles = $.get 'CachedTitles', {}
|
||||
$.get 'CachedTitles', {}, (item) ->
|
||||
titles = item['CachedTitles']
|
||||
|
||||
if title = titles[match[1]]
|
||||
a.textContent = title[0]
|
||||
embed.setAttribute 'data-title', title[0]
|
||||
else
|
||||
try
|
||||
$.cache service.api.call(a), callbacks
|
||||
catch err
|
||||
a.innerHTML = "[#{key}] <span class=warning>Title Link Blocked</span> (are you using NoScript?)</a>"
|
||||
if title = titles[match[1]]
|
||||
a.textContent = title[0]
|
||||
embed.setAttribute 'data-title', title[0]
|
||||
else
|
||||
try
|
||||
$.cache service.api.call(a), callbacks
|
||||
catch err
|
||||
a.innerHTML = "[#{key}] <span class=warning>Title Link Blocked</span> (are you using NoScript?)</a>"
|
||||
|
||||
return [a, $.tn(' '), embed]
|
||||
return [a]
|
||||
|
||||
@ -432,7 +432,7 @@ Main =
|
||||
|
||||
# c.timeEnd 'All initializations'
|
||||
|
||||
$.on d, 'AddCallback', Main.addCallback
|
||||
$.on d, 'AddCallback', Main.addCallback
|
||||
$.ready Main.initReady
|
||||
|
||||
initReady: ->
|
||||
|
||||
107
src/qr.coffee
107
src/qr.coffee
@ -11,9 +11,9 @@ QR =
|
||||
href: 'javascript:;'
|
||||
$.on sc, 'click', ->
|
||||
if !QR.nodes or QR.nodes.el.hidden
|
||||
$.event 'CloseMenu'
|
||||
QR.open()
|
||||
QR.nodes.com.focus()
|
||||
QR.resetThreadSelector()
|
||||
else
|
||||
QR.close()
|
||||
$.toggleClass @, 'disabled'
|
||||
@ -21,8 +21,7 @@ QR =
|
||||
Header.addShortcut sc
|
||||
|
||||
if Conf['Hide Original Post Form']
|
||||
$.asap (-> doc), ->
|
||||
$.addClass doc, 'hide-original-post-form'
|
||||
$.asap (-> doc), -> $.addClass doc, 'hide-original-post-form'
|
||||
|
||||
$.on d, '4chanXInitFinished', @initReady
|
||||
|
||||
@ -81,6 +80,10 @@ QR =
|
||||
QR.status()
|
||||
if !Conf['Remember Spoiler'] and QR.nodes.spoiler.checked
|
||||
QR.nodes.spoiler.click()
|
||||
focusin: ->
|
||||
$.addClass QR.nodes.el, 'has-focus'
|
||||
focusout: ->
|
||||
$.rmClass QR.nodes.el, 'has-focus'
|
||||
hide: ->
|
||||
d.activeElement.blur()
|
||||
$.addClass QR.nodes.el, 'autohide'
|
||||
@ -213,7 +216,7 @@ QR =
|
||||
|
||||
now = Date.now()
|
||||
post = QR.posts[0]
|
||||
isReply = QR.nodes.thread.value isnt 'new'
|
||||
isReply = post.thread isnt 'new'
|
||||
isSage = /sage/i.test post.email
|
||||
hasFile = !!post.file
|
||||
seconds = null
|
||||
@ -272,19 +275,19 @@ QR =
|
||||
text += ">#{s}\n"
|
||||
|
||||
QR.open()
|
||||
ta = QR.nodes.com
|
||||
QR.nodes.thread.value = OP.ID unless ta.value
|
||||
{com, thread} = QR.nodes
|
||||
thread.value = OP.ID unless com.value
|
||||
|
||||
caretPos = ta.selectionStart
|
||||
caretPos = com.selectionStart
|
||||
# Replace selection for text.
|
||||
ta.value = ta.value[...caretPos] + text + ta.value[ta.selectionEnd..]
|
||||
com.value = com.value[...caretPos] + text + com.value[com.selectionEnd..]
|
||||
# Move the caret to the end of the new quote.
|
||||
range = caretPos + text.length
|
||||
ta.setSelectionRange range, range
|
||||
ta.focus()
|
||||
com.setSelectionRange range, range
|
||||
com.focus()
|
||||
|
||||
# Fire the 'input' event
|
||||
$.event 'input', null, ta
|
||||
QR.selected.save com
|
||||
QR.selected.save thread
|
||||
|
||||
characterCount: ->
|
||||
counter = QR.nodes.charCount
|
||||
@ -362,12 +365,6 @@ QR =
|
||||
post = new QR.post()
|
||||
post.setFile file
|
||||
$.addClass QR.nodes.el, 'dump'
|
||||
|
||||
resetThreadSelector: ->
|
||||
if g.VIEW is 'thread'
|
||||
QR.nodes.thread.value = g.THREADID
|
||||
else
|
||||
QR.nodes.thread.value = 'new'
|
||||
|
||||
posts: []
|
||||
|
||||
@ -386,8 +383,12 @@ QR =
|
||||
spoiler: $ 'input', el
|
||||
span: el.lastChild
|
||||
|
||||
@nodes.spoiler.checked = @spoiler
|
||||
|
||||
<% if (type === 'userscript') { %>
|
||||
# XXX Firefox lacks focusin/focusout support.
|
||||
for elm in $$ '*', el
|
||||
$.on elm, 'blur', QR.focusout
|
||||
$.on elm, 'focus', QR.focusin
|
||||
<% } %>
|
||||
$.on el, 'click', @select.bind @
|
||||
$.on @nodes.rm, 'click', (e) => e.stopPropagation(); @rm()
|
||||
$.on @nodes.label, 'click', (e) => e.stopPropagation()
|
||||
@ -399,9 +400,14 @@ QR =
|
||||
for event in ['dragStart', 'dragEnter', 'dragLeave', 'dragOver', 'dragEnd', 'drop']
|
||||
$.on el, event.toLowerCase(), @[event]
|
||||
|
||||
@thread = if g.VIEW is 'thread'
|
||||
g.THREADID
|
||||
else
|
||||
'new'
|
||||
|
||||
prev = QR.posts[QR.posts.length - 1]
|
||||
QR.posts.push @
|
||||
@spoiler = if prev and Conf['Remember Spoiler']
|
||||
@nodes.spoiler.checked = @spoiler = if prev and Conf['Remember Spoiler']
|
||||
prev.spoiler
|
||||
else
|
||||
false
|
||||
@ -435,7 +441,7 @@ QR =
|
||||
lock: (lock=true) ->
|
||||
@isLocked = lock
|
||||
return unless @ is QR.selected
|
||||
for name in ['name', 'email', 'sub', 'com', 'spoiler']
|
||||
for name in ['thread', 'name', 'email', 'sub', 'com', 'spoiler']
|
||||
QR.nodes[name].disabled = lock
|
||||
@nodes.rm.style.visibility =
|
||||
QR.nodes.fileRM.style.visibility = if lock then 'hidden' else ''
|
||||
@ -461,12 +467,15 @@ QR =
|
||||
|
||||
load: ->
|
||||
# Load this post's values.
|
||||
for name in ['name', 'email', 'sub', 'com']
|
||||
for name in ['thread', 'name', 'email', 'sub', 'com']
|
||||
QR.nodes[name].value = @[name] or null
|
||||
@showFileData()
|
||||
QR.characterCount()
|
||||
|
||||
save: (input) ->
|
||||
if input.type is 'checkbox'
|
||||
@spoiler = input.checked
|
||||
return
|
||||
{value} = input
|
||||
@[input.dataset.name] = value
|
||||
return if input.nodeName isnt 'TEXTAREA'
|
||||
@ -481,7 +490,7 @@ QR =
|
||||
return unless @ is QR.selected
|
||||
# Do this in case people use extensions
|
||||
# that do not trigger the `input` event.
|
||||
for name in ['name', 'email', 'sub', 'com']
|
||||
for name in ['thread', 'name', 'email', 'sub', 'com', 'spoiler']
|
||||
@save QR.nodes[name]
|
||||
return
|
||||
|
||||
@ -565,8 +574,8 @@ QR =
|
||||
@showFileData()
|
||||
return unless window.URL
|
||||
URL.revokeObjectURL @URL
|
||||
|
||||
showFileData: (hide) ->
|
||||
|
||||
showFileData: ->
|
||||
if @file
|
||||
QR.nodes.filename.textContent = @filename
|
||||
QR.nodes.filename.title = @filename
|
||||
@ -661,6 +670,12 @@ QR =
|
||||
# start with an uncached captcha
|
||||
@reload()
|
||||
|
||||
<% if (type === 'userscript') { %>
|
||||
# XXX Firefox lacks focusin/focusout support.
|
||||
$.on input, 'blur', QR.focusout
|
||||
$.on input, 'focus', QR.focusin
|
||||
<% } %>
|
||||
|
||||
$.addClass QR.nodes.el, 'has-captcha'
|
||||
$.after QR.nodes.dumpList.parentElement, [imgContainer, input]
|
||||
|
||||
@ -847,10 +862,17 @@ QR =
|
||||
$.add nodes.thread, $.el 'option',
|
||||
value: thread
|
||||
textContent: "Thread No.#{thread}"
|
||||
QR.resetThreadSelector()
|
||||
|
||||
$.on nodes.filename.parentNode, 'click keyup', QR.openFileInput
|
||||
|
||||
<% if (type === 'userscript') { %>
|
||||
# XXX Firefox lacks focusin/focusout support.
|
||||
for elm in $$ '*', QR.nodes.el
|
||||
$.on elm, 'blur', QR.focusout
|
||||
$.on elm, 'focus', QR.focusin
|
||||
<% } %>
|
||||
$.on QR.nodes.el, 'focusin', QR.focusin
|
||||
$.on QR.nodes.el, 'focusout', QR.focusout
|
||||
$.on nodes.autohide, 'change', QR.toggleHide
|
||||
$.on nodes.close, 'click', QR.close
|
||||
$.on nodes.dumpButton, 'click', -> nodes.el.classList.toggle 'dump'
|
||||
@ -861,13 +883,21 @@ QR =
|
||||
QR.selected.rmFile()
|
||||
$.on nodes.spoiler, 'change', -> QR.selected.nodes.spoiler.click()
|
||||
$.on nodes.fileInput, 'change', QR.fileInput
|
||||
|
||||
new QR.post true
|
||||
# save selected post's data
|
||||
for name in ['name', 'email', 'sub', 'com']
|
||||
$.on nodes[name], 'input', -> QR.selected.save @
|
||||
$.on nodes[name], 'focus', -> $.addClass nodes.el, 'focus'
|
||||
$.on nodes[name], 'blur', -> $.rmClass nodes.el, 'focus'
|
||||
$.on nodes[name], 'input', -> QR.selected.save @
|
||||
$.on nodes.thread, 'change', -> QR.selected.save @
|
||||
|
||||
<% if (type === 'userscript') { %>
|
||||
if Conf['Remember QR Size']
|
||||
$.get 'QR Size', '', (item) ->
|
||||
nodes.com.style.cssText = item['QR Size']
|
||||
$.on nodes.com, 'mouseup', (e) ->
|
||||
return if e.button isnt 0
|
||||
$.set 'QR Size', @style.cssText
|
||||
<% } %>
|
||||
|
||||
new QR.post true
|
||||
|
||||
QR.status()
|
||||
QR.cooldown.init()
|
||||
@ -897,7 +927,7 @@ QR =
|
||||
post.forceSave()
|
||||
if g.BOARD.ID is 'f'
|
||||
filetag = QR.nodes.flashTag.value
|
||||
threadID = QR.nodes.thread.value
|
||||
threadID = post.thread
|
||||
thread = g.BOARD.threads[threadID]
|
||||
|
||||
# prevent errors
|
||||
@ -985,6 +1015,11 @@ QR =
|
||||
QR.status()
|
||||
|
||||
response: ->
|
||||
<% if (type === 'userjs') { %>
|
||||
# The upload.onload callback is not called
|
||||
# or at least not in time with Opera.
|
||||
QR.req.upload.onload()
|
||||
<% } %>
|
||||
{req} = QR
|
||||
delete QR.req
|
||||
|
||||
@ -1026,6 +1061,12 @@ QR =
|
||||
# Too many frequent mistyped captchas will auto-ban you!
|
||||
# On connection error, the post most likely didn't go through.
|
||||
QR.cooldown.set delay: 2
|
||||
else if err.textContent and m = err.textContent.match /wait\s(\d+)\ssecond/i
|
||||
QR.cooldown.auto = if QR.captcha.isEnabled
|
||||
!!QR.captcha.captchas.length
|
||||
else
|
||||
true
|
||||
QR.cooldown.set delay: m[1]
|
||||
else # stop auto-posting
|
||||
QR.cooldown.auto = false
|
||||
QR.status()
|
||||
|
||||
@ -124,7 +124,7 @@ Settings =
|
||||
$.rmClass selected, 'tab-selected'
|
||||
$.addClass $(".tab-#{@hyphenatedTitle}", Settings.dialog), 'tab-selected'
|
||||
section = $ 'section', Settings.dialog
|
||||
section.innerHTML = null
|
||||
$.rmAll section
|
||||
section.className = "section-#{@hyphenatedTitle}"
|
||||
@open section, mode
|
||||
section.scrollTop = 0
|
||||
@ -188,6 +188,13 @@ Settings =
|
||||
$.delete ['hiddenThreads', 'hiddenPosts']
|
||||
$.after $('input[name="Stubs"]', section).parentNode.parentNode, div
|
||||
|
||||
div = $.el 'div',
|
||||
innerHTML: "<button>Reset Header</button><span class=description>: Unhide the navigation bar."
|
||||
unhide = $ 'button', div
|
||||
$.on unhide, 'click', ->
|
||||
Header.setBarPosition.call textContent: "sticky top"
|
||||
$.after $('input[name="Check for Updates"]', section).parentNode.parentNode, div
|
||||
|
||||
export: (now, data) ->
|
||||
unless typeof now is 'number'
|
||||
now = Date.now()
|
||||
@ -213,7 +220,7 @@ Settings =
|
||||
return
|
||||
# XXX Firefox won't let us download automatically.
|
||||
p = $ '.imp-exp-result', Settings.dialog
|
||||
p.innerHTML = null
|
||||
$.rmAll p
|
||||
$.add p, a
|
||||
|
||||
import: ->
|
||||
@ -340,7 +347,7 @@ Settings =
|
||||
selectFilter: ->
|
||||
div = @nextElementSibling
|
||||
if (name = @value) isnt 'guide'
|
||||
div.innerHTML = null
|
||||
$.rmAll div
|
||||
ta = $.el 'textarea',
|
||||
name: name
|
||||
className: 'field'
|
||||
@ -824,7 +831,7 @@ Settings =
|
||||
mascotHide = $.el "div",
|
||||
id: "mascot_hide"
|
||||
className: "reply"
|
||||
innerHTML: "Hide Categories <span class=dropmarker></span><div></div>"
|
||||
innerHTML: "Hide Categories <span class=drop-marker></span><div></div>"
|
||||
|
||||
keys = Object.keys Mascots
|
||||
keys.sort()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user