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:
commit
53cf95d8e1
378
4chan_x.user.js
378
4chan_x.user.js
File diff suppressed because one or more lines are too long
59
CHANGELOG.md
Normal file
59
CHANGELOG.md
Normal 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.
|
||||
@ -30,8 +30,7 @@ Open your console with:
|
||||
|
||||
### Release
|
||||
|
||||
- To patch, run `grunt patch` (`0.0.x` version bump).
|
||||
- To upgrade, run `grunt upgrade` (`0.x.0` version bump).
|
||||
- Update the version with `grunt patch`, `grunt minor` or `grunt major`.
|
||||
- Release with `grunt release`.
|
||||
|
||||
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
106
Gruntfile.js
Normal 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 + '.');
|
||||
});
|
||||
|
||||
};
|
||||
60
changelog
60
changelog
@ -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
|
||||
|
||||
2.39.0
|
||||
|
||||
@ -67,27 +67,24 @@ a[href="javascript:;"] {
|
||||
z-index: 999;
|
||||
}
|
||||
#notifications {
|
||||
z-index: 80;
|
||||
}
|
||||
#qp, #ihover {
|
||||
z-index: 70;
|
||||
}
|
||||
#menu {
|
||||
#qp, #ihover {
|
||||
z-index: 60;
|
||||
}
|
||||
#navlinks, #updater, #thread-stats {
|
||||
#menu {
|
||||
z-index: 50;
|
||||
}
|
||||
#header:hover {
|
||||
#navlinks, #updater, #thread-stats {
|
||||
z-index: 40;
|
||||
}
|
||||
#qr {
|
||||
z-index: 30;
|
||||
}
|
||||
#header {
|
||||
#watcher {
|
||||
z-index: 20;
|
||||
}
|
||||
#watcher {
|
||||
#header {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@ -109,18 +106,25 @@ a[href="javascript:;"] {
|
||||
}
|
||||
#header-bar {
|
||||
border-width: 0 0 1px;
|
||||
padding: 4px;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
padding: 3px 4px 4px;
|
||||
position: relative;
|
||||
-webkit-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) {
|
||||
box-shadow: none;
|
||||
margin-bottom: -1em;
|
||||
-webkit-transform: translateY(-100%);
|
||||
transform: translateY(-100%);
|
||||
-webkit-transition: all 2s 2s ease-in-out;
|
||||
transition: all 2s 2s ease-in-out;
|
||||
-webkit-transition: all .8s .6s cubic-bezier(.55, .055, .675, .19);
|
||||
transition: all .8s .6s cubic-bezier(.55, .055, .675, .19);
|
||||
}
|
||||
#toggle-header-bar {
|
||||
cursor: n-resize;
|
||||
@ -141,11 +145,20 @@ a[href="javascript:;"] {
|
||||
text-decoration: none;
|
||||
padding: 1px;
|
||||
}
|
||||
#shortcuts:empty {
|
||||
display: none;
|
||||
}
|
||||
.shortcut:not(:last-child)::after {
|
||||
content: " / ";
|
||||
}
|
||||
.brackets-wrap::before {
|
||||
content: " [";
|
||||
content: "\\00a0[";
|
||||
}
|
||||
.brackets-wrap::after {
|
||||
content: "] ";
|
||||
content: "]\\00a0";
|
||||
}
|
||||
.expand-all-shortcut {
|
||||
opacity: .35;
|
||||
}
|
||||
|
||||
/* Notifications */
|
||||
@ -281,6 +294,9 @@ a[href="javascript:;"] {
|
||||
.section-sauce textarea {
|
||||
height: 350px;
|
||||
}
|
||||
.section-rice .field[name="boardnav"] {
|
||||
width: 100%;
|
||||
}
|
||||
.section-rice textarea {
|
||||
height: 150px;
|
||||
}
|
||||
@ -736,7 +752,7 @@ a[href="javascript:;"] {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
.menu-button > span {
|
||||
.menu-button i {
|
||||
border-top: 6px solid;
|
||||
border-right: 4px solid transparent;
|
||||
border-left: 4px solid transparent;
|
||||
@ -761,11 +777,6 @@ a[href="javascript:;"] {
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
/* XXX firefox fix */
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
.entry.has-submenu {
|
||||
padding-right: 20px;
|
||||
|
||||
@ -16,8 +16,6 @@ $.extend $,
|
||||
MINUTE: 1000 * 60
|
||||
HOUR : 1000 * 60 * 60
|
||||
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()
|
||||
id: (id) ->
|
||||
d.getElementById id
|
||||
|
||||
@ -5,6 +5,10 @@ Config =
|
||||
false
|
||||
'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': [
|
||||
true
|
||||
'Redirect dead threads and images.'
|
||||
@ -133,6 +137,10 @@ Config =
|
||||
true
|
||||
'Show a different favicon when there are unread posts.'
|
||||
]
|
||||
'Unread Line': [
|
||||
true
|
||||
'Show a line to distinguish read posts from unread ones.'
|
||||
]
|
||||
'Thread Excerpt': [
|
||||
true
|
||||
'Show an excerpt of the thread in the tab title.'
|
||||
@ -167,6 +175,10 @@ Config =
|
||||
false
|
||||
'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': [
|
||||
false
|
||||
'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/
|
||||
"""
|
||||
|
||||
'Header auto-hide': false
|
||||
|
||||
'Header catalog links': false
|
||||
|
||||
boardnav: '[current-title / toggle-all]'
|
||||
|
||||
time: '%m/%d/%y(%a)%H:%M:%S'
|
||||
|
||||
backlink: '>>%id'
|
||||
@ -316,6 +334,10 @@ http://www.google.com/searchbyimage?image_url=%turl
|
||||
|
||||
hotkeys:
|
||||
# QR & Options
|
||||
'Toggle board list': [
|
||||
'Ctrl+b'
|
||||
'Toggle the full board list.'
|
||||
]
|
||||
'Open empty QR': [
|
||||
'q'
|
||||
'Open QR without post number inserted.'
|
||||
@ -340,6 +362,10 @@ http://www.google.com/searchbyimage?image_url=%turl
|
||||
'Alt+c'
|
||||
'Insert code tags.'
|
||||
]
|
||||
'Eqn tags': [
|
||||
'Alt+e'
|
||||
'Insert eqn tags.'
|
||||
]
|
||||
'Math tags': [
|
||||
'Alt+m'
|
||||
'Insert math tags.'
|
||||
|
||||
@ -1,85 +1,134 @@
|
||||
Header =
|
||||
init: ->
|
||||
headerEl = $.el 'div',
|
||||
id: 'header'
|
||||
id: 'header'
|
||||
innerHTML: """
|
||||
<div id=header-bar class=dialog>
|
||||
<span class=brackets-wrap><a class=menu-button href=javascript:;><span></span></a></span>
|
||||
<span class=brackets-wrap hidden>top secret</span>
|
||||
<span class=brackets-wrap id=board-list hidden>next-gen board-list</span>
|
||||
<span class='show-board-list-button brackets-wrap' title="Toggle the board list."><a href=javascript:;>+</a></span>
|
||||
<a class=board-name href="/#{g.BOARD}/#{if g.VIEW is 'catalog' then 'catalog' else ''}">
|
||||
<span class=board-path>/#{g.BOARD}/</span> - <span class=board-title>...</span>
|
||||
</a>
|
||||
<span class=board-list hidden></span>
|
||||
<span class='menu-button brackets-wrap'><a href=javascript:;><i></i></a></span>
|
||||
<span id=shortcuts class=brackets-wrap></span>
|
||||
<span id=board-list>
|
||||
<span id=custom-board-list></span>
|
||||
<span id=full-board-list hidden></span>
|
||||
</span>
|
||||
<div id=toggle-header-bar title="Toggle the header auto-hiding."></div>
|
||||
</div>
|
||||
<div id=notifications></div>
|
||||
""".replace />\s+</g, '><' # get rid of spaces between elements
|
||||
|
||||
@headerBar = $ '#header-bar', headerEl
|
||||
Header.setBarVisibility $.get 'autohideHeaderBar', false
|
||||
$.sync 'autohideHeaderBar', Header.setBarVisibility
|
||||
@bar = $ '#header-bar', headerEl
|
||||
@setBarVisibility Conf['Header auto-hide']
|
||||
$.sync 'Header auto-hide', @setBarVisibility
|
||||
|
||||
@menu = new UI.Menu 'header'
|
||||
$.on $('.menu-button', @headerBar), 'click', @menuToggle
|
||||
$.on $('.show-board-list-button', @headerBar), 'click', @toggleBoardList
|
||||
$.on $('#toggle-header-bar', @headerBar), 'click', @toggleBar
|
||||
$.on $('.menu-button', @bar), 'click', @menuToggle
|
||||
$.on $('#toggle-header-bar', @bar), 'click', @toggleBarVisibility
|
||||
|
||||
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
|
||||
$.sync 'Header catalog links', @setCatalogLinks
|
||||
$.event 'AddMenuEntry',
|
||||
type: 'header'
|
||||
el: catalogToggler
|
||||
order: 105
|
||||
order: 50
|
||||
|
||||
$.asap (-> d.body), ->
|
||||
if Main.isThisPageLegit()
|
||||
$.prepend d.body, headerEl
|
||||
$.asap (-> $.id 'boardNavDesktop'), @setBoardList
|
||||
return unless Main.isThisPageLegit()
|
||||
# Wait for #boardNavMobile instead of #boardNavDesktop,
|
||||
# it might be incomplete otherwise.
|
||||
$.asap (-> $.id 'boardNavMobile'), Header.setBoardList
|
||||
$.prepend d.body, headerEl
|
||||
|
||||
setBoardList: ->
|
||||
if nav = $.id 'boardNavDesktop'
|
||||
if a = $ "a[href*='/#{g.BOARD}/']", nav
|
||||
a.className = 'current'
|
||||
$('.board-title', Header.headerBar).textContent = a.title
|
||||
$.add $('.board-list', Header.headerBar), [nav.childNodes...]
|
||||
nav = $.id 'boardNavDesktop'
|
||||
if a = $ "a[href*='/#{g.BOARD}/']", nav
|
||||
a.className = 'current'
|
||||
fullBoardList = $ '#full-board-list', Header.bar
|
||||
$.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: ->
|
||||
node = @firstElementChild.firstChild
|
||||
if showBoardList = $.hasClass @, 'show-board-list-button'
|
||||
$.rmClass @, 'show-board-list-button'
|
||||
$.addClass @, 'hide-board-list-button'
|
||||
node.data = node.data.replace '+', '-'
|
||||
else
|
||||
$.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
|
||||
{bar} = Header
|
||||
custom = $ '#custom-board-list', bar
|
||||
full = $ '#full-board-list', bar
|
||||
showBoardList = !full.hidden
|
||||
custom.hidden = !showBoardList
|
||||
full.hidden = showBoardList
|
||||
|
||||
toggleCatalogLinks: ->
|
||||
useCatalog = @checked
|
||||
root = $ '.board-list', Header.headerBar
|
||||
as = $$ 'a[href*="boards.4chan.org"]', root
|
||||
as.push $ '.board-name', Header.headerBar
|
||||
setCatalogLinks: (useCatalog) ->
|
||||
as = $$ '#board-list a[href*="boards.4chan.org"]', Header.bar
|
||||
str = if useCatalog then 'catalog' else ''
|
||||
for a in as
|
||||
a.pathname = "/#{a.pathname.split('/')[1]}/#{if useCatalog then 'catalog' else ''}"
|
||||
a.pathname = "/#{a.pathname.split('/')[1]}/#{str}"
|
||||
return
|
||||
toggleCatalogLinks: ->
|
||||
Header.setCatalogLinks @checked
|
||||
$.set 'Header catalog links', @checked
|
||||
|
||||
setBarVisibility: (hide) ->
|
||||
(if hide then $.addClass else $.rmClass) Header.headerBar, 'autohide'
|
||||
toggleBar: ->
|
||||
hide = !$.hasClass Header.headerBar, 'autohide'
|
||||
(if hide then $.addClass else $.rmClass) Header.bar, 'autohide'
|
||||
toggleBarVisibility: ->
|
||||
hide = !$.hasClass Header.bar, '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 'autohideHeaderBar', hide
|
||||
$.set 'Header auto-hide', hide
|
||||
|
||||
addShortcut: (el) ->
|
||||
shortcut = $.el 'span',
|
||||
className: 'shortcut'
|
||||
$.add shortcut, el
|
||||
$.prepend $('#shortcuts', Header.bar), shortcut
|
||||
|
||||
menuToggle: (e) ->
|
||||
Header.menu.toggle e, @, g
|
||||
@ -169,7 +218,7 @@ Settings =
|
||||
<div class=sections-list></div>
|
||||
<div class=credits>
|
||||
<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=javascript:; class=close title=Close>×</a>
|
||||
</div>
|
||||
@ -299,7 +348,7 @@ Settings =
|
||||
window.location.reload()
|
||||
catch err
|
||||
output.textContent = 'Import failed due to an error.'
|
||||
$.log err.stack
|
||||
c.log err.stack
|
||||
reader.readAsText file
|
||||
loadSettings: (data) ->
|
||||
version = data.version.split '.'
|
||||
@ -356,9 +405,9 @@ Settings =
|
||||
data.Conf.sauces = data.Conf.sauces.replace /\$\d/g, (c) ->
|
||||
switch c
|
||||
when '$1'
|
||||
'%turl'
|
||||
'%TURL'
|
||||
when '$2'
|
||||
'%url'
|
||||
'%URL'
|
||||
when '$3'
|
||||
'%MD5'
|
||||
when '$4'
|
||||
@ -408,6 +457,7 @@ Settings =
|
||||
name: name
|
||||
className: 'field'
|
||||
value: $.get name, Conf[name]
|
||||
spellcheck: false
|
||||
$.on ta, 'change', $.cb.value
|
||||
$.add div, ta
|
||||
return
|
||||
@ -447,14 +497,14 @@ Settings =
|
||||
section.innerHTML = """
|
||||
<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>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:
|
||||
<li><code>%turl</code>: Thumbnail url.</li>
|
||||
<li><code>%url</code>: Full image url.</li>
|
||||
<li><code>%TURL</code>: Thumbnail URL.</li>
|
||||
<li><code>%URL</code>: Full image URL.</li>
|
||||
<li><code>%MD5</code>: MD5 hash.</li>
|
||||
<li><code>%board</code>: Current board.</li>
|
||||
</ul>
|
||||
<textarea name=sauces class=field></textarea>
|
||||
<textarea name=sauces class=field spellcheck=false></textarea>
|
||||
"""
|
||||
sauce = $ 'textarea', section
|
||||
sauce.value = $.get 'sauces', Conf['sauces']
|
||||
@ -462,9 +512,20 @@ Settings =
|
||||
|
||||
rice: (section) ->
|
||||
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>
|
||||
<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>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>
|
||||
@ -476,13 +537,13 @@ Settings =
|
||||
|
||||
<fieldset>
|
||||
<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>
|
||||
<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>divnk: <code>%l</code> (truncated), <code>%L</code> (untruncated), <code>%T</code> (Unix timestamp)</div>
|
||||
<div><input name=fileInfo class=field spellcheck=false>: <span class='fileText file-info-preview'></span></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>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>
|
||||
@ -503,10 +564,10 @@ Settings =
|
||||
<fieldset>
|
||||
<legend>Custom CSS <span class=warning #{if Conf['Custom CSS'] then 'hidden' else ''}>is disabled.</span></legend>
|
||||
<button id=apply-css>Apply CSS</button>
|
||||
<textarea name=usercss class=field></textarea>
|
||||
<textarea name=usercss class=field spellcheck=false></textarea>
|
||||
</fieldset>
|
||||
"""
|
||||
for name in ['time', 'backlink', 'fileInfo', 'favicon', 'usercss']
|
||||
for name in ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss']
|
||||
input = $ "[name=#{name}]", section
|
||||
input.value = $.get name, Conf[name]
|
||||
event = if name in ['favicon', 'usercss']
|
||||
@ -518,6 +579,8 @@ Settings =
|
||||
$.on input, event, Settings[name]
|
||||
Settings[name].call input
|
||||
$.on $.id('apply-css'), 'click', Settings.usercss
|
||||
boardnav: ->
|
||||
Header.generateBoardList @value
|
||||
time: ->
|
||||
funk = Time.createFunc @value
|
||||
@nextElementSibling.textContent = funk Time, new Date()
|
||||
@ -550,7 +613,7 @@ Settings =
|
||||
section.innerHTML = """
|
||||
<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>Press <kbd>Return</kbd> to disable a keybind.</div>
|
||||
<div>Press <kbd>Backspace</kbd> to disable a keybind.</div>
|
||||
<table><tbody>
|
||||
<tr><th>Actions</th><th>Keybinds</th></tr>
|
||||
</tbody></table>
|
||||
@ -562,6 +625,7 @@ Settings =
|
||||
input = $ 'input', tr
|
||||
input.name = key
|
||||
input.value = $.get key, Conf[key]
|
||||
input.spellcheck = false
|
||||
$.on input, 'keydown', Settings.keybind
|
||||
$.add tbody, tr
|
||||
return
|
||||
@ -1318,7 +1382,7 @@ Menu =
|
||||
(post) ->
|
||||
a or= $.el 'a',
|
||||
className: 'menu-button'
|
||||
innerHTML: '[<span></span>]'
|
||||
innerHTML: '[<i></i>]'
|
||||
href: 'javascript:;'
|
||||
clone = a.cloneNode true
|
||||
clone.setAttribute 'data-postid', post.fullID
|
||||
@ -1561,6 +1625,9 @@ Keybinds =
|
||||
thread = Get.postFromNode($('.op', threadRoot)).thread
|
||||
switch key
|
||||
# QR & Options
|
||||
when Conf['Toggle board list']
|
||||
if Conf['Custom Board Navigation']
|
||||
Header.toggleBoardList()
|
||||
when Conf['Open empty QR']
|
||||
Keybinds.qr threadRoot
|
||||
when Conf['Open QR']
|
||||
@ -1581,6 +1648,9 @@ Keybinds =
|
||||
when Conf['Code tags']
|
||||
return if target.nodeName isnt 'TEXTAREA'
|
||||
Keybinds.tags 'code', target
|
||||
when Conf['Eqn tags']
|
||||
return if target.nodeName isnt 'TEXTAREA'
|
||||
Keybinds.tags 'eqn', target
|
||||
when Conf['Math tags']
|
||||
return if target.nodeName isnt 'TEXTAREA'
|
||||
Keybinds.tags 'math', target
|
||||
@ -1626,7 +1696,7 @@ Keybinds =
|
||||
when Conf['Previous reply']
|
||||
Keybinds.hl -1, threadRoot
|
||||
when Conf['Hide']
|
||||
ThreadHiding.toggle thread
|
||||
ThreadHiding.toggle thread if g.VIEW is 'index'
|
||||
else
|
||||
return
|
||||
e.preventDefault()
|
||||
@ -1686,9 +1756,7 @@ Keybinds =
|
||||
|
||||
img: (thread, all) ->
|
||||
if all
|
||||
input = ImageExpand.expandAllInput
|
||||
input.checked = !input.checked
|
||||
ImageExpand.cb.all.call input
|
||||
ImageExpand.cb.toggleAll()
|
||||
else
|
||||
post = Get.postFromNode $('.post.highlight', thread) or $ '.op', thread
|
||||
ImageExpand.toggle post
|
||||
@ -1702,7 +1770,7 @@ Keybinds =
|
||||
location.href = url
|
||||
|
||||
hl: (delta, thread) ->
|
||||
headRect = Header.headerBar.getBoundingClientRect()
|
||||
headRect = Header.bar.getBoundingClientRect()
|
||||
topMargin = headRect.top + headRect.height
|
||||
if postEl = $ '.reply.highlight', thread
|
||||
$.rmClass postEl, 'highlight'
|
||||
@ -1762,7 +1830,7 @@ Nav =
|
||||
Nav.scroll +1
|
||||
|
||||
getThread: (full) ->
|
||||
headRect = Header.headerBar.getBoundingClientRect()
|
||||
headRect = Header.bar.getBoundingClientRect()
|
||||
topMargin = headRect.top + headRect.height
|
||||
threads = $$ '.thread:not([hidden])'
|
||||
for thread, i in threads
|
||||
@ -2981,11 +3049,11 @@ Sauce =
|
||||
name: 'Sauce'
|
||||
cb: @node
|
||||
createSauceLink: (link) ->
|
||||
link = link.replace /%(t?url|MD5|board)/g, (parameter) ->
|
||||
link = link.replace /%(T?URL|MD5|board)/g, (parameter) ->
|
||||
switch parameter
|
||||
when '%turl'
|
||||
when '%TURL'
|
||||
"' + post.file.thumbURL + '"
|
||||
when '%url'
|
||||
when '%URL'
|
||||
"' + post.file.URL + '"
|
||||
when '%MD5'
|
||||
"' + encodeURIComponent(post.file.MD5) + '"
|
||||
@ -3012,6 +3080,14 @@ ImageExpand =
|
||||
init: ->
|
||||
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
|
||||
name: 'Image Expansion'
|
||||
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
|
||||
e.preventDefault()
|
||||
ImageExpand.toggle Get.postFromNode @
|
||||
all: ->
|
||||
toggleAll: ->
|
||||
$.event 'CloseMenu'
|
||||
func = if ImageExpand.on = @checked
|
||||
ImageExpand.expand
|
||||
if ImageExpand.on = $.hasClass ImageExpand.EAI, 'expand-all-shortcut'
|
||||
ImageExpand.EAI.className = 'contract-all-shortcut'
|
||||
ImageExpand.EAI.title = 'Contract All Images'
|
||||
func = ImageExpand.expand
|
||||
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 post in [post].concat post.clones
|
||||
{file} = post
|
||||
@ -3063,7 +3143,7 @@ ImageExpand =
|
||||
# Scroll back to the thumbnail when contracting the image
|
||||
# to avoid being left miles away from the relevant post.
|
||||
postRect = post.nodes.root.getBoundingClientRect()
|
||||
headRect = Header.headerBar.getBoundingClientRect()
|
||||
headRect = Header.bar.getBoundingClientRect()
|
||||
top = postRect.top - headRect.top - headRect.height - 2
|
||||
root = if $.engine is 'webkit'
|
||||
d.body
|
||||
@ -3150,26 +3230,21 @@ ImageExpand =
|
||||
|
||||
{createSubEntry} = ImageExpand.menu
|
||||
subEntries = []
|
||||
subEntries.push createSubEntry 'Expand all'
|
||||
for key, conf of Config.imageExpansion
|
||||
subEntries.push createSubEntry key, conf
|
||||
|
||||
$.event 'AddMenuEntry',
|
||||
type: 'header'
|
||||
el: el
|
||||
order: 20
|
||||
order: 80
|
||||
subEntries: subEntries
|
||||
|
||||
createSubEntry: (type, config) ->
|
||||
label = $.el 'label',
|
||||
innerHTML: "<input type=checkbox name='#{type}'> #{type}"
|
||||
input = label.firstElementChild
|
||||
switch type
|
||||
when 'Expand all'
|
||||
$.on input, 'change', ImageExpand.cb.all
|
||||
ImageExpand.expandAllInput = input
|
||||
when 'Fit width', 'Fit height'
|
||||
$.on input, 'change', ImageExpand.cb.setFitness
|
||||
if type in ['Fit width', 'Fit height']
|
||||
$.on input, 'change', ImageExpand.cb.setFitness
|
||||
if config
|
||||
label.title = config[1]
|
||||
input.checked = Conf[type]
|
||||
@ -3458,14 +3533,15 @@ Unread =
|
||||
for ID, post of @posts
|
||||
posts.push post if post.isReply
|
||||
Unread.addPosts posts
|
||||
if Unread.hr.parentNode
|
||||
Unread.hr.scrollIntoView false
|
||||
else if posts.length and !Unread.posts.length
|
||||
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.
|
||||
posts[posts.length - 1].nodes.root.scrollIntoView()
|
||||
$.on d, 'ThreadUpdate', Unread.onUpdate
|
||||
$.on d, 'scroll visibilitychange', Unread.read
|
||||
$.on d, 'visibilitychange', Unread.setLine
|
||||
$.on d, 'visibilitychange', Unread.setLine if Conf['Unread Line']
|
||||
|
||||
addPosts: (newPosts) ->
|
||||
if Conf['Quick Reply']
|
||||
@ -3477,8 +3553,9 @@ Unread =
|
||||
continue
|
||||
Unread.posts.push post
|
||||
Unread.addPostQuotingYou post, yourPosts if yourPosts
|
||||
# Force line on visible threads if there were no unread posts previously.
|
||||
Unread.setLine Unread.posts[0] in newPosts
|
||||
if Conf['Unread Line']
|
||||
# Force line on visible threads if there were no unread posts previously.
|
||||
Unread.setLine Unread.posts[0] in newPosts
|
||||
Unread.read()
|
||||
Unread.update()
|
||||
|
||||
@ -3659,7 +3736,7 @@ ThreadUpdater =
|
||||
#{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><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
|
||||
@ -3690,7 +3767,7 @@ ThreadUpdater =
|
||||
when 'Interval'
|
||||
$.on input, 'change', ThreadUpdater.cb.interval
|
||||
ThreadUpdater.cb.interval.call input
|
||||
when 'Update Now'
|
||||
when 'Update'
|
||||
$.on input, 'click', ThreadUpdater.update
|
||||
|
||||
$.on window, 'online offline', ThreadUpdater.cb.online
|
||||
@ -3868,9 +3945,13 @@ ThreadUpdater =
|
||||
deletedFiles = []
|
||||
# Check for deleted posts/files.
|
||||
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
|
||||
unless ID in index
|
||||
if post.isDead and ID in index
|
||||
post.resurrect()
|
||||
else unless ID in index
|
||||
post.kill()
|
||||
deletedPosts.push post
|
||||
else if post.file and !post.file.isDead and ID not in files
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
return unless /^(boards|images|sys)\.4chan\.org$/.test location.hostname
|
||||
|
||||
Conf = {}
|
||||
c = console
|
||||
d = document
|
||||
doc = null
|
||||
g =
|
||||
|
||||
@ -185,6 +185,28 @@ class Post
|
||||
$.add quotelink, $.tn '\u00A0(Dead)'
|
||||
$.addClass quotelink, 'deadlink'
|
||||
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) ->
|
||||
new Clone @, context
|
||||
rmClone: (index) ->
|
||||
@ -303,17 +325,17 @@ Main =
|
||||
return
|
||||
|
||||
initFeature = (name, module) ->
|
||||
# console.time "#{name} initialization"
|
||||
# c.time "#{name} initialization"
|
||||
try
|
||||
module.init()
|
||||
catch err
|
||||
Main.handleErrors
|
||||
message: "\"#{name}\" initialization crashed."
|
||||
error: err
|
||||
finally
|
||||
# console.timeEnd "#{name} initialization"
|
||||
# finally
|
||||
# c.timeEnd "#{name} initialization"
|
||||
|
||||
# console.time 'All initializations'
|
||||
# c.time 'All initializations'
|
||||
initFeature 'Polyfill', Polyfill
|
||||
initFeature 'Header', Header
|
||||
initFeature 'Settings', Settings
|
||||
@ -360,7 +382,7 @@ Main =
|
||||
initFeature 'Thread Watcher', ThreadWatcher
|
||||
initFeature 'Index Navigation', Nav
|
||||
initFeature 'Keybinds', Keybinds
|
||||
# console.timeEnd 'All initializations'
|
||||
# c.timeEnd 'All initializations'
|
||||
|
||||
$.on d, 'AddCallback', Main.addCallback
|
||||
$.on d, '4chanMainInit', Main.initStyle
|
||||
@ -443,7 +465,7 @@ Main =
|
||||
# get the nodes' length only once
|
||||
len = nodes.length
|
||||
for callback in klass::callbacks
|
||||
# console.profile callback.name
|
||||
# c.profile callback.name
|
||||
for i in [0...len]
|
||||
node = nodes[i]
|
||||
try
|
||||
@ -454,7 +476,7 @@ Main =
|
||||
errors.push
|
||||
message: "\"#{callback.name}\" crashed on #{klass.name} No.#{node} (/#{node.board}/)."
|
||||
error: err
|
||||
# console.profileEnd callback.name
|
||||
# c.profileEnd callback.name
|
||||
Main.handleErrors errors if errors
|
||||
|
||||
addCallback: (e) ->
|
||||
@ -494,7 +516,7 @@ Main =
|
||||
|
||||
parseError: (data) ->
|
||||
{message, error} = data
|
||||
$.log message, error.stack
|
||||
c.log message, error.stack
|
||||
message = $.el 'div',
|
||||
textContent: message
|
||||
error = $.el 'div',
|
||||
|
||||
@ -18,19 +18,17 @@ QR =
|
||||
if Conf['Hide Original Post Form']
|
||||
$.addClass doc, 'hide-original-post-form'
|
||||
|
||||
link = $.el 'a',
|
||||
sc = $.el 'a',
|
||||
className: 'qr-shortcut'
|
||||
textContent: 'Quick Reply'
|
||||
textContent: 'QR'
|
||||
title: 'Quick Reply'
|
||||
href: 'javascript:;'
|
||||
$.on link, 'click', ->
|
||||
$.on sc, 'click', ->
|
||||
$.event 'CloseMenu'
|
||||
QR.open()
|
||||
QR.resetThreadSelector()
|
||||
QR.nodes.com.focus()
|
||||
$.event 'AddMenuEntry',
|
||||
type: 'header'
|
||||
el: link
|
||||
order: 10
|
||||
Header.addShortcut sc
|
||||
|
||||
if $.engine is 'webkit'
|
||||
$.on d, 'paste', QR.paste
|
||||
@ -50,7 +48,7 @@ QR =
|
||||
|
||||
persist: ->
|
||||
QR.open()
|
||||
QR.hide() if Conf['Auto Hide QR']
|
||||
QR.hide() if Conf['Auto-Hide QR']
|
||||
open: ->
|
||||
if QR.nodes
|
||||
QR.nodes.el.hidden = false
|
||||
@ -494,22 +492,22 @@ QR =
|
||||
else
|
||||
height = s / width * height
|
||||
width = s
|
||||
c = $.el 'canvas'
|
||||
c.height = img.height = height
|
||||
c.width = img.width = width
|
||||
c.getContext('2d').drawImage img, 0, 0, width, height
|
||||
cv = $.el 'canvas'
|
||||
cv.height = img.height = height
|
||||
cv.width = img.width = width
|
||||
cv.getContext('2d').drawImage img, 0, 0, width, height
|
||||
unless window.URL
|
||||
@nodes.el.style.backgroundImage = "url(#{c.toDataURL()})"
|
||||
@nodes.el.style.backgroundImage = "url(#{cv.toDataURL()})"
|
||||
delete @URL
|
||||
return
|
||||
URL.revokeObjectURL fileURL
|
||||
applyBlob = (blob) =>
|
||||
@URL = URL.createObjectURL blob
|
||||
@nodes.el.style.backgroundImage = "url(#{@URL})"
|
||||
if c.toBlob
|
||||
c.toBlob applyBlob
|
||||
if cv.toBlob
|
||||
cv.toBlob applyBlob
|
||||
return
|
||||
data = atob c.toDataURL().split(',')[1]
|
||||
data = atob cv.toDataURL().split(',')[1]
|
||||
|
||||
# DataUrl to Binary code from Aeosynth's 4chan X repo
|
||||
l = data.length
|
||||
@ -848,7 +846,7 @@ QR =
|
||||
|
||||
# Enable auto-posting if we have stuff to post, disable it otherwise.
|
||||
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()
|
||||
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.
|
||||
@ -985,9 +983,14 @@ QR =
|
||||
isReply: !!threadID
|
||||
|
||||
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
|
||||
$.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
|
||||
QR.close()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user