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

Conflicts:
	CHANGELOG.md
	Gruntfile.coffee
	LICENSE
	css/style.css
	html/Posting/QR.html
	img/changelog/3.2.0/0.png
	package.json
	src/General/Get.coffee
	src/General/Header.coffee
	src/General/Main.coffee
	src/General/Notice.coffee
	src/General/Notification.coffee
	src/General/Settings.coffee
	src/General/img/icon.gif
	src/General/lib/notification.class
	src/Monitoring/Favicon.coffee
	src/Posting/QuickReply.coffee
	src/Quotelinks/QuoteOP.coffee
This commit is contained in:
Zixaphir 2013-08-17 20:33:48 -07:00
commit d15f8ee728
51 changed files with 744 additions and 361 deletions

View File

@ -1,3 +1,9 @@
**MayhemYDG**:
![filename editing](img/changelog/3.8.0/0.gif)
- The QR now allows you to edit the filename on the fly:
### v1.2.32
*2013-08-16*

View File

@ -24,6 +24,7 @@ module.exports = (grunt) ->
'src/General/Build.coffee'
'src/General/Get.coffee'
'src/General/UI.coffee'
'src/General/Notice.coffee'
'src/Filtering/**/*'
'src/Quotelinks/**/*'
'src/Linkification/**/*'
@ -133,7 +134,7 @@ module.exports = (grunt) ->
tmpcrx: 'tmp-crx'
tmpuserscript: 'tmp-userscript'
require('matchdep').filterDev('grunt-*').forEach grunt.loadNpmTasks
require('load-grunt-tasks') grunt
grunt.registerTask 'default', [
'build'

View File

@ -1,6 +1,8 @@
// ==UserScript==
// @name 4chan X
// @version 1.2.32
// @minGMVer 1.13
// @minFFVer 22
// @namespace 4chan-X
// @description Cross-browser userscript for maximum lurking on 4chan.
// @license MIT; https://github.com/seaweedchan/4chan-x/blob/master/LICENSE
@ -16,4 +18,4 @@
// @updateURL https://github.com/seaweedchan/4chan-x/raw/stable/builds/4chan-X.meta.js
// @downloadURL https://github.com/seaweedchan/4chan-x/raw/stable/builds/4chan-X.user.js
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwAgMAAAAqbBEUAAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAHFJREFUKFOt0LENACEIBdBv4Qju4wgWanEj3D6OcIVMKaitYHEU/jwTCQj8W75kiVCSBvdQ5/AvfVHBin11BgdRq3ysBgfwBDRrj3MCIA+oAQaku/Q1cNctrAmyDl577tOThYt/Y1RBM4DgOHzM0HFTAyLukH/cmRnqAAAAAElFTkSuQmCC
// ==/UserScript==
// ==/UserScript==

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
img/changelog/3.2.0/0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
img/changelog/3.8.0/0.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 KiB

View File

@ -2,7 +2,6 @@
"name": "4chan-X",
"version": "1.2.32",
"description": "Cross-browser userscript for maximum lurking on 4chan.",
"meta": {
"name": "4chan X",
"repo": "https://github.com/seaweedchan/4chan-x/",
@ -21,6 +20,7 @@
}
},
"devDependencies": {
"font-awesome": "git://github.com/MayhemYDG/Font-Awesome.git#df4285951124f9ca1f3907438462e5ba9e464bcb",
"grunt": "~0.4.1",
"grunt-bump": "~0.0.11",
"grunt-concurrent": "~0.3.0",
@ -31,7 +31,7 @@
"grunt-contrib-copy": "~0.4.1",
"grunt-contrib-watch": "~0.5.0",
"grunt-shell": "~0.3.1",
"matchdep": "~0.1.2"
"load-grunt-tasks": "~0.1.0"
},
"repository": {
"type": "git",

View File

@ -33,7 +33,7 @@ Filter =
regexp = RegExp regexp[1], regexp[2]
catch err
# I warned you, bro.
new Notification 'warning', err.message, 60
new Notice 'warning', err.message, 60
continue
# Filter OPs along with their threads, replies only, or both.

View File

@ -156,11 +156,8 @@ Get =
# https://github.com/eksopl/asagi/blob/master/src/main/java/net/easymodo/asagi/Yotsuba.java#L109-138
bq.innerHTML = bq.innerHTML.replace ///
\n
| \[/?b\]
| \[/?spoiler\]
| \[/?code\]
| \[/?moot\]
| \[/?banned\]
|
\[/?[a-z]+(:lit)?\]
///g, Get.parseMarkup
comment = bq.innerHTML
@ -234,6 +231,8 @@ Get =
when '[/moot]'
'</div>'
when '[banned]'
'<b style="color: red;">'
'<strong style="color: red;">'
when '[/banned]'
'</b>'
'</strong>'
else
text.replace ':lit', ''

View File

@ -258,8 +258,7 @@ Header =
'automatically hide itself.'
else
'remain visible.'}"
new Notification 'info', message, 2
new Notice 'info', message, 2
setFooterVisibility: (hide) ->
Header.footerToggler.checked = hide
@ -321,5 +320,5 @@ Header =
createNotification: (e) ->
{type, content, lifetime, cb} = e.detail
notif = new Notification type, content, lifetime
notif = new Notice type, content, lifetime
cb notif if cb

View File

@ -13,7 +13,7 @@ Main =
Conf[parent] = obj
return
flatten null, Config
for db in DataBoards
for db in DataBoard.keys
Conf[db] = boards: {}
Conf['selectedArchives'] = {}
Conf['CachedTitles'] = []
@ -21,7 +21,7 @@ Main =
$.extend Conf, items
<% if (type === 'crx') { %>
unless items
new Notification 'error', $.el 'span',
new Notice 'error', $.el 'span',
innerHTML: """
It seems like your <%= meta.name %> settings became corrupted due to a <a href="https://code.google.com/p/chromium/issues/detail?id=261623" target=_blank>Chrome bug</a>.<br>
Unfortunately, you'll have to <a href="https://github.com/MayhemYDG/4chan-x/wiki/FAQ#known-problems" target=_blank>fix it yourself</a>.
@ -236,7 +236,7 @@ Main =
try
localStorage.getItem '4chan-settings'
catch err
new Notification 'warning', 'Cookies need to be enabled on 4chan for <%= meta.name %> to properly function.', 30
new Notice 'warning', 'Cookies need to be enabled on 4chan for <%= meta.name %> to properly function.', 30
$.event '4chanXInitFinished'
@ -318,7 +318,7 @@ Main =
else if errors.length is 1
error = errors[0]
if error
new Notification 'error', Main.parseError(error), 15
new Notice 'error', Main.parseError(error), 15
return
div = $.el 'div',
@ -334,7 +334,7 @@ Main =
for error in errors
$.add logs, Main.parseError error
new Notification 'error', [div, logs], 30
new Notice 'error', [div, logs], 30
parseError: (data) ->
Main.logError data
@ -358,6 +358,13 @@ Main =
Main.thisPageIsLegit
css: """
@font-face {
font-family: 'FontAwesome';
src: url('data:application/font-woff;base64,<%= grunt.file.read('node_modules/font-awesome/font/fontawesome-webfont.woff', {encoding: 'base64'}) %>') format('woff');
font-weight: normal;
font-style: normal;
}
<%= grunt.file.read('node_modules/font-awesome/css/font-awesome.min.css').replace(/@font-face\{[^}]+\}/, '').replace(/\\/g, '\\\\') %>
<%= grunt.file.read('src/General/css/style.css') %>
<%= grunt.file.read('src/General/css/yotsuba.css') %>
<%= grunt.file.read('src/General/css/yotsuba-b.css') %>

29
src/General/Notice.coffee Normal file
View File

@ -0,0 +1,29 @@
class Notice
constructor: (type, content, @timeout) ->
@el = $.el 'div',
innerHTML: '<a href=javascript:; class=close title=Close>×</a><div class=message></div>'
@el.style.opacity = 0
@setType type
$.on @el.firstElementChild, 'click', @close
if typeof content is 'string'
content = $.tn content
$.add @el.lastElementChild, content
$.ready @add
setType: (type) ->
@el.className = "notification #{type}"
add: =>
if d.hidden
$.on d, 'visibilitychange', @add
return
$.off d, 'visibilitychange', @add
$.add $.id('notifications'), @el
@el.clientHeight # force reflow
@el.style.opacity = 1
setTimeout @close, @timeout * $.SECOND if @timeout
close: =>
$.off d, 'visibilitychange', @add
$.rm @el

View File

@ -17,7 +17,7 @@ Settings =
el = $.el 'span',
innerHTML: "<%= meta.name %> has been updated to <a href='#{changelog}' target=_blank>version #{g.VERSION}</a>."
if Conf['Show Updated Notifications']
new Notification 'info', el, 30
new Notice 'info', el, 30
else
$.on d, '4chanXInitFinished', Settings.open
$.set 'previousversion', g.VERSION
@ -151,7 +151,7 @@ Settings =
data =
version: g.VERSION
date: now
for db in DataBoards
for db in DataBoard.keys
Conf[db] = boards: {}
# Make sure to export the most recent data.
$.get Conf, (Conf) ->

View File

@ -23,8 +23,7 @@ UI = do ->
constructor: (@type) ->
# Doc here: https://github.com/MayhemYDG/4chan-x/wiki/Menu-API
$.on d, 'AddMenuEntry', @addEntry.bind @
@close = close.bind @
$.on d, 'AddMenuEntry', @addEntry
@entries = []
makeMenu: ->
@ -33,7 +32,7 @@ UI = do ->
id: 'menu'
tabIndex: 0
$.on menu, 'click', (e) -> e.stopPropagation()
$.on menu, 'keydown', @keybinds.bind @
$.on menu, 'keydown', @keybinds
menu
toggle: (e, button, data) ->
@ -114,7 +113,7 @@ UI = do ->
$.add entry.el, submenu
return
close = ->
close: =>
$.rm currentMenu
$.rmClass lastToggledButton, 'active'
currentMenu = null
@ -127,7 +126,7 @@ UI = do ->
+(first.style.order or first.style.webkitOrder) - +(second.style.order or second.style.webkitOrder)
entries[entries.indexOf(entry) + direction]
keybinds: (e) ->
keybinds: (e) =>
entry = $ '.focused', currentMenu
while subEntry = $ '.focused', entry
entry = subEntry
@ -185,7 +184,7 @@ UI = do ->
style.left = left
style.right = right
addEntry: (e) ->
addEntry: (e) =>
entry = e.detail
return if entry.type isnt @type
@parseEntry entry

View File

@ -773,18 +773,24 @@ input.field.tripped:not(:hover):not(:focus) {
height: 24px;
}
/* Fake File Input */
input#qr-filename {
border: none !important;
width: 65%;
padding: 0px 4px;
}
#qr-filename,
#qr-filesize,
.has-file #qr-no-file {
display: none;
}
#qr-no-file,
.has-file #qr-filename {
.has-file #qr-filename,
.has-file #qr-filesize {
display: inline-block;
padding: 0px 4px;
margin-bottom: 2px;
margin: 0 0 2px;
overflow: hidden;
text-overflow: ellipsis;
max-width: 88%;
vertical-align: top;
}
#qr-no-file {
color: #AAA;

View File

@ -10,9 +10,9 @@
</div>
<form>
<div class=persona>
<input name=name data-name=name list="list-name" placeholder=Name class=field size=1 tabindex=10>
<input name=email data-name=email list="list-email" placeholder=E-mail class=field size=1 tabindex=20>
<input name=sub data-name=sub list="list-sub" placeholder=Subject class=field size=1 tabindex=30>
<input data-name=name list="list-name" placeholder=Name class=field size=1 tabindex=10>
<input data-name=email list="list-email" placeholder=E-mail class=field size=1 tabindex=20>
<input data-name=sub list="list-sub" placeholder=Subject class=field size=1 tabindex=30>
</div>
<div class=textarea>
<textarea data-name=com placeholder=Comment class=field tabindex=40></textarea>
@ -25,7 +25,8 @@
<div id=file-n-submit>
<span id=qr-filename-container class=field tabindex=60>
<span id=qr-no-file>No selected file</span>
<span id=qr-filename></span>
<input id="qr-filename" data-name="filename" spellcheck="false">
<span id=qr-filesize></span>
<span id=qr-extras-container>
<a id=qr-filerm href=javascript:; title='Remove file'>×</a>
<a id=dump-button title='Dump list'>+</a>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 B

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 B

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 323 B

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 B

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 377 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 B

After

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 B

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 B

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 B

After

Width:  |  Height:  |  Size: 113 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 B

After

Width:  |  Height:  |  Size: 133 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 B

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 B

After

Width:  |  Height:  |  Size: 133 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 B

After

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 B

After

Width:  |  Height:  |  Size: 133 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 B

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 B

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 349 B

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 281 B

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 349 B

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 B

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 349 B

After

Width:  |  Height:  |  Size: 311 B

View File

@ -44,10 +44,8 @@ $.formData = (form) ->
return new FormData form
fd = new FormData()
for key, val of form when val
# XXX GM bug
# if val instanceof Blob
if val.size and val.name
fd.append key, val, val.name
if typeof val is 'object' and 'newName' of val
fd.append key, val, val.newName
else
fd.append key, val
fd

View File

@ -1,9 +1,9 @@
DataBoards = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads']
class DataBoard
@keys = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads']
constructor: (@key, sync, dontClean) ->
@data = Conf[key]
$.sync key, @onSync.bind @
$.sync key, @onSync
@clean() unless dontClean
return unless sync
# Chrome also fires the onChanged callback on the current tab,
@ -88,6 +88,6 @@ class DataBoard
@deleteIfEmpty {boardID}
@save()
onSync: (data) ->
onSync: (data) =>
@data = data or boards: {}
@sync?()

View File

@ -1,6 +1,8 @@
// ==UserScript==
// @name <%= meta.name %>
// @version <%= version %>
// @minGMVer 1.13
// @minFFVer 22
// @namespace <%= name %>
// @description <%= description %>
// @license MIT; <%= meta.repo %>blob/<%= meta.mainBranch %>/LICENSE
@ -17,4 +19,4 @@
// @updateURL <%= meta.repo %>raw/stable/builds/<%= meta.files.metajs %>
// @downloadURL <%= meta.repo %>raw/stable/builds/<%= meta.files.userjs %>
// @icon data:image/png;base64,<%= grunt.file.read('src/General/img/icon48.png', {encoding: 'base64'}) %>
// ==/UserScript==
// ==/UserScript==

View File

@ -3,8 +3,7 @@ ImageExpand =
return if g.VIEW is 'catalog' or !Conf['Image Expansion']
@EAI = $.el 'a',
className: 'expand-all-shortcut'
textContent: 'EAI'
className: 'expand-all-shortcut icon-resize-full'
title: 'Expand All Images'
href: 'javascript:;'
$.on @EAI, 'click', ImageExpand.cb.toggleAll
@ -33,11 +32,11 @@ ImageExpand =
toggleAll: ->
$.event 'CloseMenu'
if ImageExpand.on = $.hasClass ImageExpand.EAI, 'expand-all-shortcut'
ImageExpand.EAI.className = 'contract-all-shortcut'
ImageExpand.EAI.className = 'contract-all-shortcut icon-resize-small'
ImageExpand.EAI.title = 'Contract All Images'
func = ImageExpand.expand
else
ImageExpand.EAI.className = 'expand-all-shortcut'
ImageExpand.EAI.className = 'expand-all-shortcut icon-resize-full'
ImageExpand.EAI.title = 'Expand All Images'
func = ImageExpand.contract
for ID, post of g.posts

View File

@ -2,24 +2,23 @@ Linkify =
init: ->
return if g.VIEW is 'catalog' or not Conf['Linkify']
@regString =
///(
# http, magnet, ftp, etc
(https?|mailto|git|magnet|ftp|irc):(
[a-z\d%/]
)
|
# This should account for virtually all links posted without http:
[-a-z\d]+[.](
aero|asia|biz|cat|com|coop|info|int|jobs|mobi|museum|name|net|org|post|pro|tel|travel|xxx|edu|gov|mil|[a-z]{2}
)(/|(?!.))
|
# IPv4 Addresses
[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}
|
# E-mails
[-\w\d.@]+@[a-z\d.-]+\.[a-z\d]
)///i
@regString = ///(
# http, magnet, ftp, etc
(https?|mailto|git|magnet|ftp|irc):(
[a-z\d%/]
)
|
# This should account for virtually all links posted without http:
[-a-z\d]+[.](
aero|asia|biz|cat|com|coop|info|int|jobs|mobi|museum|name|net|org|post|pro|tel|travel|xxx|edu|gov|mil|[a-z]{2}
)(/|(?!.))
|
# IPv4 Addresses
[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}
|
# E-mails
[-\w\d.@]+@[a-z\d.-]+\.[a-z\d]
)///i
if Conf['Comment Expansion']
ExpandComment.callbacks.push @node

View File

@ -32,13 +32,9 @@ Fourchan =
cb: @math
code: ->
return if @isClone
for pre in $$ '.prettyprint', @nodes.comment
# Don't pretty print twice:
# Might need a better way to detect if a .prettyprint
# is already pretty-printed. We can't just look for spans
# since 4chan inserts its quotes and whatnot inside.
unless $ '.pln', pre
$.event 'prettyprint', pre, window
for pre in $$ '.prettyprint:not(.prettyprinted)', @nodes.comment
$.event 'prettyprint', pre, window
$.addClass pre, 'prettyprinted'
return
math: ->
return if @isClone or !$ '.math', @nodes.comment

View File

@ -44,5 +44,7 @@ Favicon =
else
Favicon.unread = Favicon.unreadNSFW
Favicon.unreadY = Favicon.unreadNSFWY
dead: 'data:image/gif;base64,<%= grunt.file.read("src/General/img/favicons/dead.gif", {encoding: "base64"}) %>'
empty: 'data:image/gif;base64,<%= grunt.file.read("src/General/img/favicons/empty.gif", {encoding: "base64"}) %>'
dead: 'data:image/gif;base64,<%= grunt.file.read("src/General/img/favicons/dead.gif", {encoding: "base64"}) %>'
logo: 'data:image/png;base64,<%= grunt.file.read("src/General/img/icon128.png", {encoding: "base64"}) %>'

View File

@ -219,14 +219,14 @@ ThreadUpdater =
'The thread is not a sticky anymore.'
else
'The thread is not closed anymore.'
new Notification 'info', message, 30
new Notice 'info', message, 30
$.rm $ ".#{titleLC}Icon", ThreadUpdater.thread.OP.nodes.info
return
message = if title is 'Sticky'
'The thread is now a sticky.'
else
'The thread is now closed.'
new Notification 'info', message, 30
new Notice 'info', message, 30
icon = $.el 'img',
src: "//static.4chan.org/image/#{titleLC}.gif"
alt: title

View File

@ -90,10 +90,25 @@ Unread =
addPostQuotingYou: (post) ->
return unless QR.db
for quotelink in post.nodes.quotelinks
if QR.db.get Get.postDataFromLink quotelink
Unread.postsQuotingYou.push post
return
for quotelink in post.nodes.quotelinks when QR.db.get Get.postDataFromLink quotelink
Unread.postsQuotingYou.push post
Unread.openNotification post
return
openNotification: (post) ->
return unless d.hidden
name = if Conf['Anonymize']
'Anonymous'
else
$('.nameBlock', post.nodes.info).textContent.trim()
notif = new Notification "#{name} replied to you.",
body: post.info.comment
icon: Favicon.logo
notif.onclick = ->
Header.scrollToPost post.nodes.root
window.focus()
setTimeout ->
notif.close()
, 5 * $.SECOND
onUpdate: (e) ->
if e.detail[404]

View File

@ -45,6 +45,7 @@ QR =
innerHTML: "<a href=javascript:; class='qr-link'>#{if g.VIEW is 'thread' then 'Reply to Thread' else 'Start a Thread'}</a>"
className: "qr-link-container"
$.on link.firstChild, 'click', ->
$.event 'CloseMenu'
QR.open()
QR.nodes.com.focus()
@ -109,7 +110,13 @@ QR =
focusin: ->
$.addClass QR.nodes.el, 'has-focus'
focusout: ->
<% if (type === 'crx') { %>
$.rmClass QR.nodes.el, 'has-focus'
<% } else { %>
$.queueTask ->
return if $.x 'ancestor::div[@id="qr"]', d.activeElement
$.rmClass QR.nodes.el, 'has-focus'
<% } %>
hide: ->
d.activeElement.blur()
$.addClass QR.nodes.el, 'autohide'
@ -134,15 +141,29 @@ QR =
# Focus the captcha input on captcha error.
QR.captcha.nodes.input.focus()
if Conf['Captcha Warning Notifications']
QR.notifications.push new Notification 'warning', el
QR.notify el
else
$.addClass QR.captcha.nodes.input, 'error'
$.on QR.captcha.nodes.input, 'keydown', ->
$.rmClass QR.captcha.nodes.input, 'error'
else
QR.notifications.push new Notification 'warning', el
QR.notify el
alert el.textContent if d.hidden
notify: (el) ->
notice = new Notice 'warning', el
QR.notifications.push notice
return unless d.hidden
notif = new Notification 'Quick reply warning',
body: el.textContent
icon: Favicon.logo
notif.onclick = -> window.focus()
notif.onclose = -> notice.close()
setTimeout ->
notif.onclose = null
notif.close()
, 5 * $.SECOND
notifications: []
cleanNotifications: ->
for notification in QR.notifications
@ -439,7 +460,11 @@ QR =
QR.fileInput files
openFileInput: (e) ->
return if e.keyCode and e.keyCode isnt 32
e.stopPropagation()
if e.shiftKey and e.type is 'click'
return QR.selected.rmFile()
return if e.target.nodeName is 'INPUT' or (e.keyCode and not [32, 13].contains e.keyCode) or e.ctrlKey
e.preventDefault()
QR.nodes.fileInput.click()
fileInput: (files) ->
@ -500,8 +525,8 @@ QR =
for elm in $$ '*', el
$.on elm, 'blur', QR.focusout
$.on elm, 'focus', QR.focusin
<% } %>
$.on el, 'click', @select.bind @
<% } %>
$.on el, 'click', @select
$.on @nodes.rm, 'click', (e) => e.stopPropagation(); @rm()
$.on @nodes.label, 'click', (e) => e.stopPropagation()
$.on @nodes.spoiler, 'change', (e) =>
@ -565,18 +590,17 @@ QR =
lock: (lock=true) ->
@isLocked = lock
return unless @ is QR.selected
for name in ['thread', 'name', 'email', 'sub', 'com', 'spoiler']
for name in ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler']
QR.nodes[name].disabled = lock
@nodes.rm.style.visibility =
QR.nodes.fileRM.style.visibility = if lock then 'hidden' else ''
(if lock then $.off else $.on) QR.nodes.filename.parentNode, 'click', QR.openFileInput
@nodes.rm.style.visibility = if lock then 'hidden' else ''
(if lock then $.off else $.on) QR.nodes.filename.previousElementSibling, 'click', QR.openFileInput
@nodes.spoiler.disabled = lock
@nodes.el.draggable = !lock
unlock: ->
@lock false
select: ->
select: =>
if QR.selected
QR.selected.nodes.el.id = null
QR.selected.forceSave()
@ -592,7 +616,7 @@ QR =
load: ->
# Load this post's values.
for name in ['thread', 'name', 'email', 'sub', 'com']
for name in ['thread', 'name', 'email', 'sub', 'com', 'filename']
QR.nodes[name].value = @[name] or null
@showFileData()
QR.characterCount()
@ -613,18 +637,27 @@ QR =
# during the last 5 seconds of the cooldown.
if QR.cooldown.auto and @ is QR.posts[0] and 0 < QR.cooldown.seconds <= 5
QR.cooldown.auto = false
when 'filename'
return unless @file
@file.newName = @filename.replace /[/\\]/g, '-'
unless /\.(jpe?g|png|gif|pdf|sfw)$/i.test @filename
# 4chan will truncate the filename if it has no extension,
# but it will always replace the extension by the correct one,
# so we suffix it with '.jpg' when needed.
@file.newName += '.jpg'
@updateFilename()
forceSave: ->
return unless @ is QR.selected
# Do this in case people use extensions
# that do not trigger the `input` event.
for name in ['thread', 'name', 'email', 'sub', 'com', 'spoiler']
for name in ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler']
@save QR.nodes[name]
return
setFile: (@file) ->
@filename = "#{file.name} (#{$.bytesToString file.size})"
@nodes.el.title = @filename
@filename = file.name
@filesize = $.bytesToString file.size
@nodes.label.hidden = false if QR.spoiler
URL.revokeObjectURL @URL
@showFileData()
@ -668,18 +701,27 @@ QR =
img.src = fileURL
rmFile: ->
return if @isLocked
delete @file
delete @filename
delete @filesize
@nodes.el.title = null
@nodes.el.style.backgroundImage = null
@nodes.label.hidden = true if QR.spoiler
@showFileData()
URL.revokeObjectURL @URL
updateFilename: ->
long = "#{@filename} (#{@filesize})"
@nodes.el.title = long
return unless @ is QR.selected
QR.nodes.filename.title = long
showFileData: ->
if @file
QR.nodes.filename.textContent = @filename
QR.nodes.filename.title = @filename
@updateFilename()
QR.nodes.filename.value = @filename
QR.nodes.filesize.textContent = @filesize
QR.nodes.spoiler.checked = @spoiler
$.addClass QR.nodes.fileSubmit, 'has-file'
else
@ -845,44 +887,46 @@ QR =
e.preventDefault()
dialog: ->
dialog = UI.dialog 'qr', 'top:0;right:0;', """
<%= grunt.file.read('src/General/html/Features/QuickReply.html').replace(/>\s+</g, '><').trim() %>
"""
QR.nodes = nodes =
el: dialog
move: $ '.move', dialog
autohide: $ '#autohide', dialog
thread: $ 'select', dialog
close: $ '.close', dialog
form: $ 'form', dialog
dumpButton: $ '#dump-button', dialog
name: $ '[data-name=name]', dialog
email: $ '[data-name=email]', dialog
sub: $ '[data-name=sub]', dialog
com: $ '[data-name=com]', dialog
dumpList: $ '#dump-list', dialog
addPost: $ '#add-post', dialog
charCount: $ '#char-count', dialog
fileSubmit: $ '#file-n-submit', dialog
filename: $ '#qr-filename', dialog
fileRM: $ '#qr-filerm', dialog
fileExtras: $ '#qr-extras-container', dialog
spoiler: $ '#qr-file-spoiler', dialog
status: $ '[type=submit]', dialog
fileInput: $ '[type=file]', dialog
el: dialog = UI.dialog 'qr', 'top:0;right:0;', """
<%= grunt.file.read('src/General/html/Features/QuickReply.html').replace(/>\s+</g, '><').trim() %>
"""
nodes[key] = $ value, dialog for key, value of {
move: '.move'
autohide: '#autohide'
thread: 'select'
threadPar: '#qr-thread-select'
close: '.close'
form: 'form'
dumpButton: '#dump-button'
name: '[data-name=name]'
email: '[data-name=email]'
sub: '[data-name=sub]'
com: '[data-name=com]'
dumpList: '#dump-list'
addPost: '#add-post'
charCount: '#char-count'
fileSubmit: '#file-n-submit'
filename: '#qr-filename'
filesize: '#qr-filesize'
fileRM: '#qr-filerm'
fileExtras: '#qr-extras-container'
spoiler: '#qr-file-spoiler'
spoilerPar: '#qr-spoiler-label'
status: '[type=submit]'
fileInput: '[type=file]'
}
check =
jpg: 'image/jpeg'
pdf: 'application/pdf'
swf: 'application/x-shockwave-flash'
# Allow only this board's supported files.
mimeTypes = $('ul.rules > li').textContent.trim().match(/: (.+)/)[1].toLowerCase().replace /\w+/g, (type) ->
switch type
when 'jpg'
'image/jpeg'
when 'pdf'
'application/pdf'
when 'swf'
'application/x-shockwave-flash'
else
"image/#{type}"
check[type] or "image/#{type}"
QR.mimeTypes = mimeTypes.split ', '
# Add empty mimeType to avoid errors with URLs selected in Window's file dialog.
QR.mimeTypes.push ''
@ -924,8 +968,9 @@ QR =
$.on elm, 'blur', QR.focusout
$.on elm, 'focus', QR.focusin
<% } %>
$.on dialog, 'focusin', QR.focusin
$.on dialog, 'focusout', QR.focusout
$.on dialog, 'focusin', QR.focusin
$.on dialog, 'focusout', QR.focusout
$.on nodes.autohide, 'change', QR.toggleHide
$.on nodes.close, 'click', QR.close
$.on nodes.dumpButton, 'click', -> nodes.el.classList.toggle 'dump'
@ -936,7 +981,7 @@ QR =
$.on nodes.spoiler, 'change', -> QR.selected.nodes.spoiler.click()
$.on nodes.fileInput, 'change', QR.fileInput
# save selected post's data
items = ['name', 'email', 'sub', 'com']
items = ['name', 'email', 'sub', 'com', 'filename']
i = 0
while name = items[i++]
$.on nodes[name], 'input', -> QR.selected.save @
@ -1186,5 +1231,6 @@ QR =
QR.req.abort()
delete QR.req
QR.posts[0].unlock()
QR.notifications.push new Notification 'info', 'QR upload aborted.', 5
QR.cooldown.auto = false
QR.notifications.push new Notice 'info', 'QR upload aborted.', 5
QR.status()

View File

@ -26,6 +26,7 @@ QuoteOP =
{fullID} = (if @isClone then @context else @).thread
# add (OP) to quotes quoting this context's OP.
return unless quotes.contains fullID
i = 0
while quotelink = quotelinks[i++]

View File

@ -18,7 +18,7 @@ Quotify =
return
parseDeadlink: (deadlink) ->
if deadlink.parentNode.className is 'prettyprint'
if $.hasClass deadlink.parentNode, 'prettyprint'
# Don't quotify deadlinks inside code tags,
# un-`span` them.
# This won't be necessary once 4chan