Merge branch 'v3' of git://github.com/MayhemYDG/4chan-x into v3

Conflicts:
	4chan_x.user.js
	Gruntfile.js
	src/config.coffee
	src/qr.coffee
This commit is contained in:
Zixaphir 2013-03-15 13:05:15 -07:00
commit 53cf95d8e1
12 changed files with 704 additions and 330 deletions

File diff suppressed because one or more lines are too long

59
CHANGELOG.md Normal file
View File

@ -0,0 +1,59 @@
# 3.0.0
**Major rewrite of 4chan X.**
Header:
- Easily access features and the boards list directly from the Header.
- The board list can be customized.
- The Header can be automatically hidden.
Egocentrism:
- `(You)` will be added to quotes linking to your posts.
- The Unread tab icon will indicate new unread posts quoting you with an exclamation mark.
- Delete links in the post menu will only appear for your posts.
Quick Reply changes:
- Opening text files will insert their content in the comment field.
- Pasting files/images (e.g. from another website) in Chrome will open them in the QR.
- Cooldown start time is now more accurate, which means shorter cooldown period and faster auto-posting.
- Cooldown remaining time will adjust to your upload speed and file size for faster auto-posting.
- Clicking the submit button while uploading will abort the upload and won't start re-uploading automatically anymore.
- Closing the QR while uploading will abort the upload and won't close the QR anymore.
- Creating threads outside of the index is now possible.
- Selection-to-quote also applies to selected text inside the post, not just inside the comment.
- Added thumbnailing support for Opera.
Image Expansion changes:
- The toggle and settings are now located in the Header's shortcuts and menu.
- There is now a setting to allow expanding spoilers.
- Expanding OP images won't squish replies anymore.
Thread Updater changes:
- The Thread Updater will now notify of sticky/closed status change and update the icons.
- The Thread Updater will pause when offline, and resume when online.
- Added a setting to always auto-scroll to the bottom instead of the first new post.
Unread posts changes:
- Added a line to distinguish read posts from unread ones.
- Read posts won't be marked as unread after reloading a thread.
- The page will scroll to the last read post after reloading a thread.
- Visible posts will not be taken into account towards the unread count.
Thread Stats changes:
- Post and file count will now adjust with deleted posts.
- The post count will now become red past the bump limit.
- The file count will not become red anymore inside sticky threads.
Thread/Post Hiding changes:
- Added Thread & Post Hiding in the Menu, with individual settings.
- Thread & Post Hiding Buttons can now be disabled in the settings.
- Recursive Hiding will be automatically applied when manually showing/hiding a post.
Other:
- Added touch and multi-touch support for dragging windows.
- Added [eqn] and [math] tags keybind.
- Fix Chrome's install warning saying that 4chan X would execute on all domains.
- Fix Quote Backlinks and Quote Highlighting not affecting inlined quotes.
- Fix unreadable inlined posts with the Tomorrow theme.
- Fix user ID highlighting on fetched posts.
- More fixes and improvements.

View File

@ -30,8 +30,7 @@ Open your console with:
### Release ### Release
- To patch, run `grunt patch` (`0.0.x` version bump). - Update the version with `grunt patch`, `grunt minor` or `grunt major`.
- To upgrade, run `grunt upgrade` (`0.x.0` version bump).
- Release with `grunt release`. - Release with `grunt release`.
Note: this is only used to release new 4chan X versions, and is **not** needed or wanted in pull requests. Note: this is only used to release new 4chan X versions, and is **not** needed or wanted in pull requests.

106
Gruntfile.js Normal file
View File

@ -0,0 +1,106 @@
module.exports = function(grunt) {
var pkg = grunt.file.readJSON('package.json');
// Project configuration.
grunt.initConfig({
pkg: pkg,
concat: {
coffee: {
options: { process: { data: pkg } },
src: [
'src/config.coffee',
'src/globals.coffee',
'lib/ui.coffee',
'lib/$.coffee',
'lib/polyfill.coffee',
'src/features.coffee',
'src/qr.coffee',
'src/report.coffee',
'src/main.coffee'
],
dest: 'tmp/script.coffee'
},
script: {
options: { process: { data: pkg } },
src: [
'src/metadata.js',
'src/banner.js',
'tmp/script.js'
],
dest: '<%= pkg.meta.files.userjs %>'
},
metadata: {
options: { process: { data: pkg } },
src: 'src/metadata.js',
dest: '<%= pkg.meta.files.metajs %>'
}
},
coffee: {
script: {
src: 'tmp/script.coffee',
dest: 'tmp/script.js'
}
},
exec: {
commit: {
command: function() {
var release = pkg.meta.name + ' v' + pkg.version;
return [
'git checkout ' + pkg.meta.mainBranch,
'git commit -am "Release ' + release + '."',
'git tag -a ' + pkg.version + ' -m "' + release + '."',
'git tag -af stable-v3 -m "' + release + '."'
].join(' && ');
},
stdout: true
},
push: {
command: 'git push origin --all && git push origin --tags',
stdout: true
}
},
watch: {
all: {
options: {
interrupt: true
},
files: [
'Gruntfile.js',
'package.json',
'lib/**/*.coffee',
'src/**/*.coffee',
'src/**/*.js',
'css/**/*.css',
'img/*'
],
tasks: 'default'
}
},
clean: {
tmp: 'tmp'
}
});
grunt.loadNpmTasks('grunt-bump');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-coffee');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-exec');
grunt.registerTask('default', ['concat:coffee', 'coffee:script', 'concat:script', 'concat:metadata', 'clean']);
grunt.registerTask('release', ['default', 'exec:commit', 'exec:push']);
grunt.registerTask('patch', ['bump', 'updcl:3']);
grunt.registerTask('minor', ['bump:minor', 'updcl:2']);
grunt.registerTask('major', ['bump:major', 'updcl:1']);
grunt.registerTask('updcl', 'Update the changelog', function(i) {
// Update the `pkg` object with the new version.
pkg = grunt.file.readJSON('package.json');
// i is the number of #s for markdown.
var version = new Array(+i + 1).join('#') + ' ' + pkg.version + ' *(' + grunt.template.today('yyyy-mm-dd') + ')*';
grunt.file.write('CHANGELOG.md', version + '\n' + grunt.file.read('CHANGELOG.md'));
grunt.log.ok('Changelog updated for v' + pkg.version + '.');
});
};

View File

@ -1,63 +1,3 @@
beta
- Mayhem
Major rewrite of 4chan X.
New features:
Header:
Access the list of boards directly from the Header.
From the Header's menu, access to:
Settings
Quick Reply shortcut
Image Expansion
Can be auto-hidden.
Egocentrism:
"(You)" will be added to quotes linking to your posts.
The Unread tab icon will indicate new unread posts quoting you with an exclamation mark.
Delete links in the post menu will only appear for your posts.
QR changes:
Opening text files will insert their content in the comment field.
Pasting files/images (e.g. from another website) in Chrome will open them in the QR.
Cooldown start time is now more accurate, which means shorter cooldown period and faster auto-posting.
Cooldown remaining time will adjust to your upload speed and file size for faster auto-posting.
Clicking the submit button while uploading will abort the upload and won't start re-uploading automatically anymore.
Closing the QR while uploading will abort the upload and won't close the QR anymore.
Creating threads outside of the index is now possible.
Selection-to-quote also applies to selected text inside the post, not just inside the comment.
Added thumbnailing support for Opera.
Image Expansion changes:
Expanding OP images won't squish replies anymore.
There is now a setting to allow expanding spoilers.
Thread Updater changes:
The Thread Updater will now notify of sticky/closed status change and update the icons.
The Thread Updater will pause when offline, and resume when online.
Added an option to always auto-scroll to the bottom.
Unread posts changes:
A line will distinguish read post from unread ones.
Read posts won't be marked as unread after reloading a thread.
Visible posts will not be taken into account towards the unread count.
Thread Stats changes:
Post and file count will now adjust with deleted posts.
The post count will now go red past the bump limit.
The file count will not go red anymore when the thread is a sticky.
Thread/Post Hiding changes:
Added Thread & Post Hiding in the Menu, with individual settings.
Thread & Post Hiding Buttons can now be disabled in the settings.
Recursive Hiding will be automatically applied when manually hiding a post.
Added touch and multi-touch support for dragging windows.
Added [math] tags keybind.
Fix Chrome's install warning saying that 4chan X would execute on all domains.
Fix Quote Backlinks not affecting inlined quotes.
Fix Quote Highlighting not affecting inlined quotes.
Fix unreadable inlined posts with the Tomorrow theme.
Fix user ID highlighting on fetched posts.
master master
2.39.0 2.39.0

View File

@ -67,27 +67,24 @@ a[href="javascript:;"] {
z-index: 999; z-index: 999;
} }
#notifications { #notifications {
z-index: 80;
}
#qp, #ihover {
z-index: 70; z-index: 70;
} }
#menu { #qp, #ihover {
z-index: 60; z-index: 60;
} }
#navlinks, #updater, #thread-stats { #menu {
z-index: 50; z-index: 50;
} }
#header:hover { #navlinks, #updater, #thread-stats {
z-index: 40; z-index: 40;
} }
#qr { #qr {
z-index: 30; z-index: 30;
} }
#header { #watcher {
z-index: 20; z-index: 20;
} }
#watcher { #header {
z-index: 10; z-index: 10;
} }
@ -109,18 +106,25 @@ a[href="javascript:;"] {
} }
#header-bar { #header-bar {
border-width: 0 0 1px; border-width: 0 0 1px;
padding: 4px; display: -webkit-flex;
display: flex;
padding: 3px 4px 4px;
position: relative; position: relative;
-webkit-transition: all .1s ease-in-out; -webkit-transition: all .1s ease-in-out;
transition: all .1s ease-in-out; transition: all .1s ease-in-out;
} }
#board-list {
-webkit-flex: 1;
flex: 1;
text-align: center;
}
#header-bar.autohide:not(:hover) { #header-bar.autohide:not(:hover) {
box-shadow: none; box-shadow: none;
margin-bottom: -1em; margin-bottom: -1em;
-webkit-transform: translateY(-100%); -webkit-transform: translateY(-100%);
transform: translateY(-100%); transform: translateY(-100%);
-webkit-transition: all 2s 2s ease-in-out; -webkit-transition: all .8s .6s cubic-bezier(.55, .055, .675, .19);
transition: all 2s 2s ease-in-out; transition: all .8s .6s cubic-bezier(.55, .055, .675, .19);
} }
#toggle-header-bar { #toggle-header-bar {
cursor: n-resize; cursor: n-resize;
@ -141,11 +145,20 @@ a[href="javascript:;"] {
text-decoration: none; text-decoration: none;
padding: 1px; padding: 1px;
} }
#shortcuts:empty {
display: none;
}
.shortcut:not(:last-child)::after {
content: " / ";
}
.brackets-wrap::before { .brackets-wrap::before {
content: " ["; content: "\\00a0[";
} }
.brackets-wrap::after { .brackets-wrap::after {
content: "] "; content: "]\\00a0";
}
.expand-all-shortcut {
opacity: .35;
} }
/* Notifications */ /* Notifications */
@ -281,6 +294,9 @@ a[href="javascript:;"] {
.section-sauce textarea { .section-sauce textarea {
height: 350px; height: 350px;
} }
.section-rice .field[name="boardnav"] {
width: 100%;
}
.section-rice textarea { .section-rice textarea {
height: 150px; height: 150px;
} }
@ -736,7 +752,7 @@ a[href="javascript:;"] {
display: inline-block; display: inline-block;
position: relative; position: relative;
} }
.menu-button > span { .menu-button i {
border-top: 6px solid; border-top: 6px solid;
border-right: 4px solid transparent; border-right: 4px solid transparent;
border-left: 4px solid transparent; border-left: 4px solid transparent;
@ -761,11 +777,6 @@ a[href="javascript:;"] {
position: relative; position: relative;
text-decoration: none; text-decoration: none;
white-space: nowrap; white-space: nowrap;
/* XXX firefox fix */
top: 0;
right: 0;
bottom: 0;
left: 0;
} }
.entry.has-submenu { .entry.has-submenu {
padding-right: 20px; padding-right: 20px;

View File

@ -16,8 +16,6 @@ $.extend $,
MINUTE: 1000 * 60 MINUTE: 1000 * 60
HOUR : 1000 * 60 * 60 HOUR : 1000 * 60 * 60
DAY : 1000 * 60 * 60 * 24 DAY : 1000 * 60 * 60 * 24
# XXX http://code.google.com/p/phantomjs/issues/detail?id=522
log: console.log.bind console
engine: /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase() engine: /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase()
id: (id) -> id: (id) ->
d.getElementById id d.getElementById id

View File

@ -5,6 +5,10 @@ Config =
false false
'Compatibility between <%= meta.name %> and 4chan\'s inline extension is NOT guaranteed.' 'Compatibility between <%= meta.name %> and 4chan\'s inline extension is NOT guaranteed.'
] ]
'Custom Board Navigation': [
true
'Disable this to always display the full board list.'
]
'404 Redirect': [ '404 Redirect': [
true true
'Redirect dead threads and images.' 'Redirect dead threads and images.'
@ -133,6 +137,10 @@ Config =
true true
'Show a different favicon when there are unread posts.' 'Show a different favicon when there are unread posts.'
] ]
'Unread Line': [
true
'Show a line to distinguish read posts from unread ones.'
]
'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.'
@ -167,6 +175,10 @@ Config =
false false
'Automatically hide the quick reply when posting.' 'Automatically hide the quick reply when posting.'
] ]
'Open Post in New Tab': [
true
'Open new threads or replies to a thread from the index in a new tab.'
]
'Remember Subject': [ 'Remember Subject': [
false false
'Remember the subject field, instead of resetting after posting.' 'Remember the subject field, instead of resetting after posting.'
@ -304,6 +316,12 @@ http://www.google.com/searchbyimage?image_url=%turl
#//archive.installgentoo.net/%board/image/%MD5;text:View same on installgentoo /%board/ #//archive.installgentoo.net/%board/image/%MD5;text:View same on installgentoo /%board/
""" """
'Header auto-hide': false
'Header catalog links': false
boardnav: '[current-title / toggle-all]'
time: '%m/%d/%y(%a)%H:%M:%S' time: '%m/%d/%y(%a)%H:%M:%S'
backlink: '>>%id' backlink: '>>%id'
@ -316,6 +334,10 @@ http://www.google.com/searchbyimage?image_url=%turl
hotkeys: hotkeys:
# QR & Options # QR & Options
'Toggle board list': [
'Ctrl+b'
'Toggle the full board list.'
]
'Open empty QR': [ 'Open empty QR': [
'q' 'q'
'Open QR without post number inserted.' 'Open QR without post number inserted.'
@ -340,6 +362,10 @@ http://www.google.com/searchbyimage?image_url=%turl
'Alt+c' 'Alt+c'
'Insert code tags.' 'Insert code tags.'
] ]
'Eqn tags': [
'Alt+e'
'Insert eqn tags.'
]
'Math tags': [ 'Math tags': [
'Alt+m' 'Alt+m'
'Insert math tags.' 'Insert math tags.'

View File

@ -1,85 +1,134 @@
Header = Header =
init: -> init: ->
headerEl = $.el 'div', headerEl = $.el 'div',
id: 'header' id: 'header'
innerHTML: """ innerHTML: """
<div id=header-bar class=dialog> <div id=header-bar class=dialog>
<span class=brackets-wrap><a class=menu-button href=javascript:;><span></span></a></span> <span class='menu-button brackets-wrap'><a href=javascript:;><i></i></a></span>
<span class=brackets-wrap hidden>top secret</span> <span id=shortcuts class=brackets-wrap></span>
<span class=brackets-wrap id=board-list hidden>next-gen board-list</span> <span id=board-list>
<span class='show-board-list-button brackets-wrap' title="Toggle the board list."><a href=javascript:;>+</a></span> <span id=custom-board-list></span>
<a class=board-name href="/#{g.BOARD}/#{if g.VIEW is 'catalog' then 'catalog' else ''}"> <span id=full-board-list hidden></span>
<span class=board-path>/#{g.BOARD}/</span> - <span class=board-title>...</span> </span>
</a>
<span class=board-list hidden></span>
<div id=toggle-header-bar title="Toggle the header auto-hiding."></div> <div id=toggle-header-bar title="Toggle the header auto-hiding."></div>
</div> </div>
<div id=notifications></div> <div id=notifications></div>
""".replace />\s+</g, '><' # get rid of spaces between elements """.replace />\s+</g, '><' # get rid of spaces between elements
@headerBar = $ '#header-bar', headerEl @bar = $ '#header-bar', headerEl
Header.setBarVisibility $.get 'autohideHeaderBar', false @setBarVisibility Conf['Header auto-hide']
$.sync 'autohideHeaderBar', Header.setBarVisibility $.sync 'Header auto-hide', @setBarVisibility
@menu = new UI.Menu 'header' @menu = new UI.Menu 'header'
$.on $('.menu-button', @headerBar), 'click', @menuToggle $.on $('.menu-button', @bar), 'click', @menuToggle
$.on $('.show-board-list-button', @headerBar), 'click', @toggleBoardList $.on $('#toggle-header-bar', @bar), 'click', @toggleBarVisibility
$.on $('#toggle-header-bar', @headerBar), 'click', @toggleBar
catalogToggler = $.el 'label', catalogToggler = $.el 'label',
innerHTML: "<input type=checkbox #{if g.VIEW is 'catalog' then 'checked' else ''}> Use catalog board links" innerHTML: "<input type=checkbox #{if Conf['Header catalog links'] then 'checked' else ''}> Use catalog board links"
$.on catalogToggler.firstElementChild, 'change', @toggleCatalogLinks $.on catalogToggler.firstElementChild, 'change', @toggleCatalogLinks
$.sync 'Header catalog links', @setCatalogLinks
$.event 'AddMenuEntry', $.event 'AddMenuEntry',
type: 'header' type: 'header'
el: catalogToggler el: catalogToggler
order: 105 order: 50
$.asap (-> d.body), -> $.asap (-> d.body), ->
if Main.isThisPageLegit() return unless Main.isThisPageLegit()
$.prepend d.body, headerEl # Wait for #boardNavMobile instead of #boardNavDesktop,
$.asap (-> $.id 'boardNavDesktop'), @setBoardList # it might be incomplete otherwise.
$.asap (-> $.id 'boardNavMobile'), Header.setBoardList
$.prepend d.body, headerEl
setBoardList: -> setBoardList: ->
if nav = $.id 'boardNavDesktop' nav = $.id 'boardNavDesktop'
if a = $ "a[href*='/#{g.BOARD}/']", nav if a = $ "a[href*='/#{g.BOARD}/']", nav
a.className = 'current' a.className = 'current'
$('.board-title', Header.headerBar).textContent = a.title fullBoardList = $ '#full-board-list', Header.bar
$.add $('.board-list', Header.headerBar), [nav.childNodes...] $.add fullBoardList, [nav.childNodes...]
if Conf['Custom Board Navigation']
Header.generateBoardList Conf['boardnav']
$.sync 'boardnav', Header.generateBoardList
btn = $.el 'span',
className: 'hide-board-list-button brackets-wrap'
innerHTML: '<a href=javascript:;> - </a>'
$.on btn, 'click', Header.toggleBoardList
$.prepend fullBoardList, btn
else
$.rm $ '#custom-board-list', Header.bar
fullBoardList.hidden = false
Header.setCatalogLinks Conf['Header catalog links']
generateBoardList: (text) ->
list = $ '#custom-board-list', Header.bar
list.innerHTML = null
return unless text
as = $$('#full-board-list a', Header.bar)[0...-2] # ignore the Settings and Home links
nodes = text.match(/[\w@]+(-(all|title|full|text:"[^"]+"))?|[^\w@]+/g).map (t) ->
if /^[^\w@]/.test t
return $.tn t
if t is 'toggle-all'
a = $.el 'a',
className: 'show-board-list-button'
textContent: '+'
href: 'javascript:;'
$.on a, 'click', Header.toggleBoardList
return a
board = if /^current/.test t
g.BOARD.ID
else
t.match(/^[^-]+/)[0]
for a in as
if a.textContent is board
a = a.cloneNode true
if /-title$/.test t
a.textContent = a.title
else if /-full$/.test t
a.textContent = "/#{board}/ - #{a.title}"
else if m = t.match /-text:"(.+)"$/
a.textContent = m[1]
else if board is '@'
$.addClass a, 'navSmall'
return a
$.tn t
$.add list, nodes
toggleBoardList: -> toggleBoardList: ->
node = @firstElementChild.firstChild {bar} = Header
if showBoardList = $.hasClass @, 'show-board-list-button' custom = $ '#custom-board-list', bar
$.rmClass @, 'show-board-list-button' full = $ '#full-board-list', bar
$.addClass @, 'hide-board-list-button' showBoardList = !full.hidden
node.data = node.data.replace '+', '-' custom.hidden = !showBoardList
else full.hidden = showBoardList
$.rmClass @, 'hide-board-list-button'
$.addClass @, 'show-board-list-button'
node.data = node.data.replace '-', '+'
{headerBar} = Header
$('.board-name', headerBar).hidden = showBoardList
$('.board-list', headerBar).hidden = !showBoardList
toggleCatalogLinks: -> setCatalogLinks: (useCatalog) ->
useCatalog = @checked as = $$ '#board-list a[href*="boards.4chan.org"]', Header.bar
root = $ '.board-list', Header.headerBar str = if useCatalog then 'catalog' else ''
as = $$ 'a[href*="boards.4chan.org"]', root
as.push $ '.board-name', Header.headerBar
for a in as for a in as
a.pathname = "/#{a.pathname.split('/')[1]}/#{if useCatalog then 'catalog' else ''}" a.pathname = "/#{a.pathname.split('/')[1]}/#{str}"
return return
toggleCatalogLinks: ->
Header.setCatalogLinks @checked
$.set 'Header catalog links', @checked
setBarVisibility: (hide) -> setBarVisibility: (hide) ->
(if hide then $.addClass else $.rmClass) Header.headerBar, 'autohide' (if hide then $.addClass else $.rmClass) Header.bar, 'autohide'
toggleBar: -> toggleBarVisibility: ->
hide = !$.hasClass Header.headerBar, 'autohide' hide = !$.hasClass Header.bar, 'autohide'
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 'autohideHeaderBar', hide $.set 'Header auto-hide', hide
addShortcut: (el) ->
shortcut = $.el 'span',
className: 'shortcut'
$.add shortcut, el
$.prepend $('#shortcuts', Header.bar), shortcut
menuToggle: (e) -> menuToggle: (e) ->
Header.menu.toggle e, @, g Header.menu.toggle e, @, g
@ -169,7 +218,7 @@ Settings =
<div class=sections-list></div> <div class=sections-list></div>
<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' target=_blank>#{g.VERSION}</a> | <a href='<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md##{g.VERSION.replace(/\./g, '')}' 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' target=_blank>Issues</a> |
<a href=javascript:; class=close title=Close>×</a> <a href=javascript:; class=close title=Close>×</a>
</div> </div>
@ -299,7 +348,7 @@ Settings =
window.location.reload() window.location.reload()
catch err catch err
output.textContent = 'Import failed due to an error.' output.textContent = 'Import failed due to an error.'
$.log err.stack c.log err.stack
reader.readAsText file reader.readAsText file
loadSettings: (data) -> loadSettings: (data) ->
version = data.version.split '.' version = data.version.split '.'
@ -356,9 +405,9 @@ Settings =
data.Conf.sauces = data.Conf.sauces.replace /\$\d/g, (c) -> data.Conf.sauces = data.Conf.sauces.replace /\$\d/g, (c) ->
switch c switch c
when '$1' when '$1'
'%turl' '%TURL'
when '$2' when '$2'
'%url' '%URL'
when '$3' when '$3'
'%MD5' '%MD5'
when '$4' when '$4'
@ -408,6 +457,7 @@ Settings =
name: name name: name
className: 'field' className: 'field'
value: $.get name, Conf[name] value: $.get name, Conf[name]
spellcheck: false
$.on ta, 'change', $.cb.value $.on ta, 'change', $.cb.value
$.add div, ta $.add div, ta
return return
@ -447,14 +497,14 @@ Settings =
section.innerHTML = """ section.innerHTML = """
<div class=warning #{if Conf['Sauce'] then 'hidden' else ''}><code>Sauce</code> is disabled.</div> <div class=warning #{if Conf['Sauce'] then 'hidden' else ''}><code>Sauce</code> is disabled.</div>
<div>Lines starting with a <code>#</code> will be ignored.</div> <div>Lines starting with a <code>#</code> will be ignored.</div>
<div>You can specify a display text by appending <code>;text:[text]</code> to the url.</div> <div>You can specify a display text by appending <code>;text:[text]</code> to the URL.</div>
<ul>These parameters will be replaced by their corresponding values: <ul>These parameters will be replaced by their corresponding values:
<li><code>%turl</code>: Thumbnail url.</li> <li><code>%TURL</code>: Thumbnail URL.</li>
<li><code>%url</code>: Full image url.</li> <li><code>%URL</code>: Full image URL.</li>
<li><code>%MD5</code>: MD5 hash.</li> <li><code>%MD5</code>: MD5 hash.</li>
<li><code>%board</code>: Current board.</li> <li><code>%board</code>: Current board.</li>
</ul> </ul>
<textarea name=sauces class=field></textarea> <textarea name=sauces class=field spellcheck=false></textarea>
""" """
sauce = $ 'textarea', section sauce = $ 'textarea', section
sauce.value = $.get 'sauces', Conf['sauces'] sauce.value = $.get 'sauces', Conf['sauces']
@ -462,9 +512,20 @@ Settings =
rice: (section) -> rice: (section) ->
section.innerHTML = """ section.innerHTML = """
<fieldset>
<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>In the following, <code>board</code> can translate to a board ID (<code>a</code>, <code>b</code>, etc...), the current board (<code>current</code>), or the Status/Twitter link (<code>status</code>, <code>@</code>).</div>
<div>Board link: <code>board</code></div>
<div>Title link: <code>board-title</code></div>
<div>Full text link: <code>board-full</code></div>
<div>Custom text link: <code>board-text:"VIP Board"</code></div>
<div>Full board list toggle: <code>toggle-all</code></div>
</fieldset>
<fieldset> <fieldset>
<legend>Time Formatting <span class=warning #{if Conf['Time Formatting'] then 'hidden' else ''}>is disabled.</span></legend> <legend>Time Formatting <span class=warning #{if Conf['Time Formatting'] then 'hidden' else ''}>is disabled.</span></legend>
<div><input name=time class=field>: <span class=time-preview></span></div> <div><input name=time class=field spellcheck=false>: <span class=time-preview></span></div>
<div>Supported <a href=//en.wikipedia.org/wiki/Date_%28Unix%29#Formatting>format specifiers</a>:</div> <div>Supported <a href=//en.wikipedia.org/wiki/Date_%28Unix%29#Formatting>format specifiers</a>:</div>
<div>Day: <code>%a</code>, <code>%A</code>, <code>%d</code>, <code>%e</code></div> <div>Day: <code>%a</code>, <code>%A</code>, <code>%d</code>, <code>%e</code></div>
<div>Month: <code>%m</code>, <code>%b</code>, <code>%B</code></div> <div>Month: <code>%m</code>, <code>%b</code>, <code>%B</code></div>
@ -476,13 +537,13 @@ Settings =
<fieldset> <fieldset>
<legend>Quote Backlinks formatting <span class=warning #{if Conf['Quote Backlinks'] then 'hidden' else ''}>is disabled.</span></legend> <legend>Quote Backlinks formatting <span class=warning #{if Conf['Quote Backlinks'] then 'hidden' else ''}>is disabled.</span></legend>
<div><input name=backlink class=field>: <span class=backlink-preview></span></div> <div><input name=backlink class=field spellcheck=false>: <span class=backlink-preview></span></div>
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>File Info Formatting <span class=warning #{if Conf['File Info Formatting'] then 'hidden' else ''}>is disabled.</span></legend> <legend>File Info Formatting <span class=warning #{if Conf['File Info Formatting'] then 'hidden' else ''}>is disabled.</span></legend>
<div><input name=fileInfo class=field>: <span class='fileText file-info-preview'></span></div> <div><input name=fileInfo class=field spellcheck=false>: <span class='fileText file-info-preview'></span></div>
<div>divnk: <code>%l</code> (truncated), <code>%L</code> (untruncated), <code>%T</code> (Unix timestamp)</div> <div>Link: <code>%l</code> (truncated), <code>%L</code> (untruncated), <code>%T</code> (Unix timestamp)</div>
<div>Original file name: <code>%n</code> (truncated), <code>%N</code> (untruncated), <code>%t</code> (Unix timestamp)</div> <div>Original file name: <code>%n</code> (truncated), <code>%N</code> (untruncated), <code>%t</code> (Unix timestamp)</div>
<div>Spoiler indicator: <code>%p</code></div> <div>Spoiler indicator: <code>%p</code></div>
<div>Size: <code>%B</code> (Bytes), <code>%K</code> (KB), <code>%M</code> (MB), <code>%s</code> (4chan default)</div> <div>Size: <code>%B</code> (Bytes), <code>%K</code> (KB), <code>%M</code> (MB), <code>%s</code> (4chan default)</div>
@ -503,10 +564,10 @@ Settings =
<fieldset> <fieldset>
<legend>Custom CSS <span class=warning #{if Conf['Custom CSS'] then 'hidden' else ''}>is disabled.</span></legend> <legend>Custom CSS <span class=warning #{if Conf['Custom CSS'] then 'hidden' else ''}>is disabled.</span></legend>
<button id=apply-css>Apply CSS</button> <button id=apply-css>Apply CSS</button>
<textarea name=usercss class=field></textarea> <textarea name=usercss class=field spellcheck=false></textarea>
</fieldset> </fieldset>
""" """
for name in ['time', 'backlink', 'fileInfo', 'favicon', 'usercss'] for name in ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss']
input = $ "[name=#{name}]", section input = $ "[name=#{name}]", section
input.value = $.get name, Conf[name] input.value = $.get name, Conf[name]
event = if name in ['favicon', 'usercss'] event = if name in ['favicon', 'usercss']
@ -518,6 +579,8 @@ Settings =
$.on input, event, Settings[name] $.on input, event, Settings[name]
Settings[name].call input Settings[name].call input
$.on $.id('apply-css'), 'click', Settings.usercss $.on $.id('apply-css'), 'click', Settings.usercss
boardnav: ->
Header.generateBoardList @value
time: -> time: ->
funk = Time.createFunc @value funk = Time.createFunc @value
@nextElementSibling.textContent = funk Time, new Date() @nextElementSibling.textContent = funk Time, new Date()
@ -550,7 +613,7 @@ Settings =
section.innerHTML = """ section.innerHTML = """
<div class=warning #{if Conf['Keybinds'] then 'hidden' else ''}><code>Keybinds</code> are disabled.</div> <div class=warning #{if Conf['Keybinds'] then 'hidden' else ''}><code>Keybinds</code> are disabled.</div>
<div>Allowed keys: <kbd>a-z</kbd>, <kbd>0-9</kbd>, <kbd>Ctrl</kbd>, <kbd>Shift</kbd>, <kbd>Alt</kbd>, <kbd>Meta</kbd>, <kbd>Enter</kbd>, <kbd>Esc</kbd>, <kbd>Up</kbd>, <kbd>Down</kbd>, <kbd>Right</kbd>, <kbd>Left</kbd>.</div> <div>Allowed keys: <kbd>a-z</kbd>, <kbd>0-9</kbd>, <kbd>Ctrl</kbd>, <kbd>Shift</kbd>, <kbd>Alt</kbd>, <kbd>Meta</kbd>, <kbd>Enter</kbd>, <kbd>Esc</kbd>, <kbd>Up</kbd>, <kbd>Down</kbd>, <kbd>Right</kbd>, <kbd>Left</kbd>.</div>
<div>Press <kbd>Return</kbd> to disable a keybind.</div> <div>Press <kbd>Backspace</kbd> to disable a keybind.</div>
<table><tbody> <table><tbody>
<tr><th>Actions</th><th>Keybinds</th></tr> <tr><th>Actions</th><th>Keybinds</th></tr>
</tbody></table> </tbody></table>
@ -562,6 +625,7 @@ Settings =
input = $ 'input', tr input = $ 'input', tr
input.name = key input.name = key
input.value = $.get key, Conf[key] input.value = $.get key, Conf[key]
input.spellcheck = false
$.on input, 'keydown', Settings.keybind $.on input, 'keydown', Settings.keybind
$.add tbody, tr $.add tbody, tr
return return
@ -1318,7 +1382,7 @@ Menu =
(post) -> (post) ->
a or= $.el 'a', a or= $.el 'a',
className: 'menu-button' className: 'menu-button'
innerHTML: '[<span></span>]' innerHTML: '[<i></i>]'
href: 'javascript:;' href: 'javascript:;'
clone = a.cloneNode true clone = a.cloneNode true
clone.setAttribute 'data-postid', post.fullID clone.setAttribute 'data-postid', post.fullID
@ -1561,6 +1625,9 @@ Keybinds =
thread = Get.postFromNode($('.op', threadRoot)).thread thread = Get.postFromNode($('.op', threadRoot)).thread
switch key switch key
# QR & Options # QR & Options
when Conf['Toggle board list']
if Conf['Custom Board Navigation']
Header.toggleBoardList()
when Conf['Open empty QR'] when Conf['Open empty QR']
Keybinds.qr threadRoot Keybinds.qr threadRoot
when Conf['Open QR'] when Conf['Open QR']
@ -1581,6 +1648,9 @@ Keybinds =
when Conf['Code tags'] when Conf['Code tags']
return if target.nodeName isnt 'TEXTAREA' return if target.nodeName isnt 'TEXTAREA'
Keybinds.tags 'code', target Keybinds.tags 'code', target
when Conf['Eqn tags']
return if target.nodeName isnt 'TEXTAREA'
Keybinds.tags 'eqn', target
when Conf['Math tags'] when Conf['Math tags']
return if target.nodeName isnt 'TEXTAREA' return if target.nodeName isnt 'TEXTAREA'
Keybinds.tags 'math', target Keybinds.tags 'math', target
@ -1626,7 +1696,7 @@ Keybinds =
when Conf['Previous reply'] when Conf['Previous reply']
Keybinds.hl -1, threadRoot Keybinds.hl -1, threadRoot
when Conf['Hide'] when Conf['Hide']
ThreadHiding.toggle thread ThreadHiding.toggle thread if g.VIEW is 'index'
else else
return return
e.preventDefault() e.preventDefault()
@ -1686,9 +1756,7 @@ Keybinds =
img: (thread, all) -> img: (thread, all) ->
if all if all
input = ImageExpand.expandAllInput ImageExpand.cb.toggleAll()
input.checked = !input.checked
ImageExpand.cb.all.call input
else else
post = Get.postFromNode $('.post.highlight', thread) or $ '.op', thread post = Get.postFromNode $('.post.highlight', thread) or $ '.op', thread
ImageExpand.toggle post ImageExpand.toggle post
@ -1702,7 +1770,7 @@ Keybinds =
location.href = url location.href = url
hl: (delta, thread) -> hl: (delta, thread) ->
headRect = Header.headerBar.getBoundingClientRect() headRect = Header.bar.getBoundingClientRect()
topMargin = headRect.top + headRect.height topMargin = headRect.top + headRect.height
if postEl = $ '.reply.highlight', thread if postEl = $ '.reply.highlight', thread
$.rmClass postEl, 'highlight' $.rmClass postEl, 'highlight'
@ -1762,7 +1830,7 @@ Nav =
Nav.scroll +1 Nav.scroll +1
getThread: (full) -> getThread: (full) ->
headRect = Header.headerBar.getBoundingClientRect() headRect = Header.bar.getBoundingClientRect()
topMargin = headRect.top + headRect.height topMargin = headRect.top + headRect.height
threads = $$ '.thread:not([hidden])' threads = $$ '.thread:not([hidden])'
for thread, i in threads for thread, i in threads
@ -2981,11 +3049,11 @@ Sauce =
name: 'Sauce' name: 'Sauce'
cb: @node cb: @node
createSauceLink: (link) -> createSauceLink: (link) ->
link = link.replace /%(t?url|MD5|board)/g, (parameter) -> link = link.replace /%(T?URL|MD5|board)/g, (parameter) ->
switch parameter switch parameter
when '%turl' when '%TURL'
"' + post.file.thumbURL + '" "' + post.file.thumbURL + '"
when '%url' when '%URL'
"' + post.file.URL + '" "' + post.file.URL + '"
when '%MD5' when '%MD5'
"' + encodeURIComponent(post.file.MD5) + '" "' + encodeURIComponent(post.file.MD5) + '"
@ -3012,6 +3080,14 @@ ImageExpand =
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Image Expansion'] return if g.VIEW is 'catalog' or !Conf['Image Expansion']
@EAI = $.el 'a',
className: 'expand-all-shortcut'
textContent: 'EAI'
title: 'Expand All Images'
href: 'javascript:;'
$.on @EAI, 'click', ImageExpand.cb.toggleAll
Header.addShortcut @EAI
Post::callbacks.push Post::callbacks.push
name: 'Image Expansion' name: 'Image Expansion'
cb: @node cb: @node
@ -3025,12 +3101,16 @@ ImageExpand =
return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0 return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0
e.preventDefault() e.preventDefault()
ImageExpand.toggle Get.postFromNode @ ImageExpand.toggle Get.postFromNode @
all: -> toggleAll: ->
$.event 'CloseMenu' $.event 'CloseMenu'
func = if ImageExpand.on = @checked if ImageExpand.on = $.hasClass ImageExpand.EAI, 'expand-all-shortcut'
ImageExpand.expand ImageExpand.EAI.className = 'contract-all-shortcut'
ImageExpand.EAI.title = 'Contract All Images'
func = ImageExpand.expand
else else
ImageExpand.contract ImageExpand.EAI.className = 'expand-all-shortcut'
ImageExpand.EAI.title = 'Expand All Images'
func = ImageExpand.contract
for ID, post of g.posts for ID, post of g.posts
for post in [post].concat post.clones for post in [post].concat post.clones
{file} = post {file} = post
@ -3063,7 +3143,7 @@ ImageExpand =
# Scroll back to the thumbnail when contracting the image # Scroll back to the thumbnail when contracting the image
# to avoid being left miles away from the relevant post. # to avoid being left miles away from the relevant post.
postRect = post.nodes.root.getBoundingClientRect() postRect = post.nodes.root.getBoundingClientRect()
headRect = Header.headerBar.getBoundingClientRect() headRect = Header.bar.getBoundingClientRect()
top = postRect.top - headRect.top - headRect.height - 2 top = postRect.top - headRect.top - headRect.height - 2
root = if $.engine is 'webkit' root = if $.engine is 'webkit'
d.body d.body
@ -3150,26 +3230,21 @@ ImageExpand =
{createSubEntry} = ImageExpand.menu {createSubEntry} = ImageExpand.menu
subEntries = [] subEntries = []
subEntries.push createSubEntry 'Expand all'
for key, conf of Config.imageExpansion for key, conf of Config.imageExpansion
subEntries.push createSubEntry key, conf subEntries.push createSubEntry key, conf
$.event 'AddMenuEntry', $.event 'AddMenuEntry',
type: 'header' type: 'header'
el: el el: el
order: 20 order: 80
subEntries: subEntries subEntries: subEntries
createSubEntry: (type, config) -> createSubEntry: (type, config) ->
label = $.el 'label', label = $.el 'label',
innerHTML: "<input type=checkbox name='#{type}'> #{type}" innerHTML: "<input type=checkbox name='#{type}'> #{type}"
input = label.firstElementChild input = label.firstElementChild
switch type if type in ['Fit width', 'Fit height']
when 'Expand all' $.on input, 'change', ImageExpand.cb.setFitness
$.on input, 'change', ImageExpand.cb.all
ImageExpand.expandAllInput = input
when 'Fit width', 'Fit height'
$.on input, 'change', ImageExpand.cb.setFitness
if config if config
label.title = config[1] label.title = config[1]
input.checked = Conf[type] input.checked = Conf[type]
@ -3458,14 +3533,15 @@ Unread =
for ID, post of @posts for ID, post of @posts
posts.push post if post.isReply posts.push post if post.isReply
Unread.addPosts posts Unread.addPosts posts
if Unread.hr.parentNode if Unread.posts.length
Unread.hr.scrollIntoView false # Scroll to before the first unread post.
else if posts.length and !Unread.posts.length $.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. # Scroll to the last read post.
posts[posts.length - 1].nodes.root.scrollIntoView() posts[posts.length - 1].nodes.root.scrollIntoView()
$.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 $.on d, 'visibilitychange', Unread.setLine if Conf['Unread Line']
addPosts: (newPosts) -> addPosts: (newPosts) ->
if Conf['Quick Reply'] if Conf['Quick Reply']
@ -3477,8 +3553,9 @@ Unread =
continue continue
Unread.posts.push post Unread.posts.push post
Unread.addPostQuotingYou post, yourPosts if yourPosts Unread.addPostQuotingYou post, yourPosts if yourPosts
# Force line on visible threads if there were no unread posts previously. if Conf['Unread Line']
Unread.setLine Unread.posts[0] in newPosts # Force line on visible threads if there were no unread posts previously.
Unread.setLine Unread.posts[0] in newPosts
Unread.read() Unread.read()
Unread.update() Unread.update()
@ -3659,7 +3736,7 @@ ThreadUpdater =
#{html} #{html}
<div><label title='Controls whether *this* thread automatically updates or not'><input type=checkbox name='Auto Update This' #{checked}> Auto Update This</label></div> <div><label title='Controls whether *this* thread automatically updates or not'><input type=checkbox name='Auto Update This' #{checked}> Auto Update This</label></div>
<div><label><input type=number name=Interval class=field min=5 value=#{Conf['Interval']}> Refresh rate (s)</label></div> <div><label><input type=number name=Interval class=field min=5 value=#{Conf['Interval']}> Refresh rate (s)</label></div>
<div><input value='Update Now' type=button name='Update Now'></div> <div><input value='Update' type=button name='Update'></div>
""" """
@dialog = UI.dialog 'updater', 'bottom: 0; right: 0;', html @dialog = UI.dialog 'updater', 'bottom: 0; right: 0;', html
@ -3690,7 +3767,7 @@ ThreadUpdater =
when 'Interval' when 'Interval'
$.on input, 'change', ThreadUpdater.cb.interval $.on input, 'change', ThreadUpdater.cb.interval
ThreadUpdater.cb.interval.call input ThreadUpdater.cb.interval.call input
when 'Update Now' when 'Update'
$.on input, 'click', ThreadUpdater.update $.on input, 'click', ThreadUpdater.update
$.on window, 'online offline', ThreadUpdater.cb.online $.on window, 'online offline', ThreadUpdater.cb.online
@ -3868,9 +3945,13 @@ ThreadUpdater =
deletedFiles = [] deletedFiles = []
# Check for deleted posts/files. # Check for deleted posts/files.
for ID, post of ThreadUpdater.thread.posts for ID, post of ThreadUpdater.thread.posts
continue if post.isDead # XXX tmp fix for 4chan's racing condition
# giving us false-positive dead posts.
# continue if post.isDead
ID = +ID ID = +ID
unless ID in index if post.isDead and ID in index
post.resurrect()
else unless ID in index
post.kill() post.kill()
deletedPosts.push post deletedPosts.push post
else if post.file and !post.file.isDead and ID not in files else if post.file and !post.file.isDead and ID not in files

View File

@ -3,6 +3,7 @@
return unless /^(boards|images|sys)\.4chan\.org$/.test location.hostname return unless /^(boards|images|sys)\.4chan\.org$/.test location.hostname
Conf = {} Conf = {}
c = console
d = document d = document
doc = null doc = null
g = g =

View File

@ -185,6 +185,28 @@ class Post
$.add quotelink, $.tn '\u00A0(Dead)' $.add quotelink, $.tn '\u00A0(Dead)'
$.addClass quotelink, 'deadlink' $.addClass quotelink, 'deadlink'
return return
# XXX tmp fix for 4chan's racing condition
# giving us false-positive dead posts.
resurrect: ->
delete @isDead
delete @timeOfDeath
$.rmClass @nodes.root, 'deleted-post'
strong = $ 'strong.warning', @nodes.info
# no false-positive files
if @file and @file.isDead
strong.textContent = '[File deleted]'
else
$.rm strong
return if @isClone
for clone in @clones
clone.resurrect()
for quotelink in Get.allQuotelinksLinkingTo @
if $.hasClass quotelink, 'deadlink'
quotelink.textContent = quotelink.textContent.replace '\u00A0(Dead)', ''
$.rmClass quotelink, 'deadlink'
return
addClone: (context) -> addClone: (context) ->
new Clone @, context new Clone @, context
rmClone: (index) -> rmClone: (index) ->
@ -303,17 +325,17 @@ Main =
return return
initFeature = (name, module) -> initFeature = (name, module) ->
# console.time "#{name} initialization" # c.time "#{name} initialization"
try try
module.init() module.init()
catch err catch err
Main.handleErrors Main.handleErrors
message: "\"#{name}\" initialization crashed." message: "\"#{name}\" initialization crashed."
error: err error: err
finally # finally
# console.timeEnd "#{name} initialization" # c.timeEnd "#{name} initialization"
# console.time 'All initializations' # c.time 'All initializations'
initFeature 'Polyfill', Polyfill initFeature 'Polyfill', Polyfill
initFeature 'Header', Header initFeature 'Header', Header
initFeature 'Settings', Settings initFeature 'Settings', Settings
@ -360,7 +382,7 @@ Main =
initFeature 'Thread Watcher', ThreadWatcher initFeature 'Thread Watcher', ThreadWatcher
initFeature 'Index Navigation', Nav initFeature 'Index Navigation', Nav
initFeature 'Keybinds', Keybinds initFeature 'Keybinds', Keybinds
# console.timeEnd 'All initializations' # c.timeEnd 'All initializations'
$.on d, 'AddCallback', Main.addCallback $.on d, 'AddCallback', Main.addCallback
$.on d, '4chanMainInit', Main.initStyle $.on d, '4chanMainInit', Main.initStyle
@ -443,7 +465,7 @@ Main =
# get the nodes' length only once # get the nodes' length only once
len = nodes.length len = nodes.length
for callback in klass::callbacks for callback in klass::callbacks
# console.profile callback.name # c.profile callback.name
for i in [0...len] for i in [0...len]
node = nodes[i] node = nodes[i]
try try
@ -454,7 +476,7 @@ Main =
errors.push errors.push
message: "\"#{callback.name}\" crashed on #{klass.name} No.#{node} (/#{node.board}/)." message: "\"#{callback.name}\" crashed on #{klass.name} No.#{node} (/#{node.board}/)."
error: err error: err
# console.profileEnd callback.name # c.profileEnd callback.name
Main.handleErrors errors if errors Main.handleErrors errors if errors
addCallback: (e) -> addCallback: (e) ->
@ -494,7 +516,7 @@ Main =
parseError: (data) -> parseError: (data) ->
{message, error} = data {message, error} = data
$.log message, error.stack c.log message, error.stack
message = $.el 'div', message = $.el 'div',
textContent: message textContent: message
error = $.el 'div', error = $.el 'div',

View File

@ -18,19 +18,17 @@ QR =
if Conf['Hide Original Post Form'] if Conf['Hide Original Post Form']
$.addClass doc, 'hide-original-post-form' $.addClass doc, 'hide-original-post-form'
link = $.el 'a', sc = $.el 'a',
className: 'qr-shortcut' className: 'qr-shortcut'
textContent: 'Quick Reply' textContent: 'QR'
title: 'Quick Reply'
href: 'javascript:;' href: 'javascript:;'
$.on link, 'click', -> $.on sc, 'click', ->
$.event 'CloseMenu' $.event 'CloseMenu'
QR.open() QR.open()
QR.resetThreadSelector() QR.resetThreadSelector()
QR.nodes.com.focus() QR.nodes.com.focus()
$.event 'AddMenuEntry', Header.addShortcut sc
type: 'header'
el: link
order: 10
if $.engine is 'webkit' if $.engine is 'webkit'
$.on d, 'paste', QR.paste $.on d, 'paste', QR.paste
@ -50,7 +48,7 @@ QR =
persist: -> persist: ->
QR.open() QR.open()
QR.hide() if Conf['Auto Hide QR'] QR.hide() if Conf['Auto-Hide QR']
open: -> open: ->
if QR.nodes if QR.nodes
QR.nodes.el.hidden = false QR.nodes.el.hidden = false
@ -494,22 +492,22 @@ QR =
else else
height = s / width * height height = s / width * height
width = s width = s
c = $.el 'canvas' cv = $.el 'canvas'
c.height = img.height = height cv.height = img.height = height
c.width = img.width = width cv.width = img.width = width
c.getContext('2d').drawImage img, 0, 0, width, height cv.getContext('2d').drawImage img, 0, 0, width, height
unless window.URL unless window.URL
@nodes.el.style.backgroundImage = "url(#{c.toDataURL()})" @nodes.el.style.backgroundImage = "url(#{cv.toDataURL()})"
delete @URL delete @URL
return return
URL.revokeObjectURL fileURL URL.revokeObjectURL fileURL
applyBlob = (blob) => applyBlob = (blob) =>
@URL = URL.createObjectURL blob @URL = URL.createObjectURL blob
@nodes.el.style.backgroundImage = "url(#{@URL})" @nodes.el.style.backgroundImage = "url(#{@URL})"
if c.toBlob if cv.toBlob
c.toBlob applyBlob cv.toBlob applyBlob
return return
data = atob c.toDataURL().split(',')[1] data = atob cv.toDataURL().split(',')[1]
# DataUrl to Binary code from Aeosynth's 4chan X repo # DataUrl to Binary code from Aeosynth's 4chan X repo
l = data.length l = data.length
@ -848,7 +846,7 @@ QR =
# Enable auto-posting if we have stuff to post, disable it otherwise. # Enable auto-posting if we have stuff to post, disable it otherwise.
QR.cooldown.auto = QR.posts.length > 1 QR.cooldown.auto = QR.posts.length > 1
if Conf['Auto Hide QR'] and !QR.cooldown.auto if Conf['Auto-Hide QR'] and !QR.cooldown.auto
QR.hide() QR.hide()
if !QR.cooldown.auto and $.x 'ancestor::div[@id="qr"]', d.activeElement if !QR.cooldown.auto and $.x 'ancestor::div[@id="qr"]', d.activeElement
# Unfocus the focused element if it is one within the QR and we're not auto-posting. # Unfocus the focused element if it is one within the QR and we're not auto-posting.
@ -985,9 +983,14 @@ QR =
isReply: !!threadID isReply: !!threadID
if threadID is postID # new thread if threadID is postID # new thread
$.open "/#{g.BOARD}/res/#{threadID}" URL = "/#{g.BOARD}/res/#{threadID}"
else if g.VIEW is 'index' and !QR.cooldown.auto # posting from the index else if g.VIEW is 'index' and !QR.cooldown.auto # posting from the index
$.open "/#{g.BOARD}/res/#{threadID}#p#{postID}" URL = "/#{g.BOARD}/res/#{threadID}#p#{postID}"
if URL
if Conf['Open Post in New Tab']
$.open "/#{g.BOARD}/res/#{threadID}"
else
window.location = "/#{g.BOARD}/res/#{threadID}"
unless Conf['Persistent QR'] or QR.cooldown.auto unless Conf['Persistent QR'] or QR.cooldown.auto
QR.close() QR.close()