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 ### v1.2.32
*2013-08-16* *2013-08-16*

View File

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

View File

@ -1,6 +1,8 @@
// ==UserScript== // ==UserScript==
// @name 4chan X // @name 4chan X
// @version 1.2.32 // @version 1.2.32
// @minGMVer 1.13
// @minFFVer 22
// @namespace 4chan-X // @namespace 4chan-X
// @description Cross-browser userscript for maximum lurking on 4chan. // @description Cross-browser userscript for maximum lurking on 4chan.
// @license MIT; https://github.com/seaweedchan/4chan-x/blob/master/LICENSE // @license MIT; https://github.com/seaweedchan/4chan-x/blob/master/LICENSE

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

View File

@ -33,7 +33,7 @@ Filter =
regexp = RegExp regexp[1], regexp[2] regexp = RegExp regexp[1], regexp[2]
catch err catch err
# I warned you, bro. # I warned you, bro.
new Notification 'warning', err.message, 60 new Notice 'warning', err.message, 60
continue continue
# Filter OPs along with their threads, replies only, or both. # 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 # https://github.com/eksopl/asagi/blob/master/src/main/java/net/easymodo/asagi/Yotsuba.java#L109-138
bq.innerHTML = bq.innerHTML.replace /// bq.innerHTML = bq.innerHTML.replace ///
\n \n
| \[/?b\] |
| \[/?spoiler\] \[/?[a-z]+(:lit)?\]
| \[/?code\]
| \[/?moot\]
| \[/?banned\]
///g, Get.parseMarkup ///g, Get.parseMarkup
comment = bq.innerHTML comment = bq.innerHTML
@ -234,6 +231,8 @@ Get =
when '[/moot]' when '[/moot]'
'</div>' '</div>'
when '[banned]' when '[banned]'
'<b style="color: red;">' '<strong style="color: red;">'
when '[/banned]' when '[/banned]'
'</b>' '</strong>'
else
text.replace ':lit', ''

View File

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

View File

@ -13,7 +13,7 @@ Main =
Conf[parent] = obj Conf[parent] = obj
return return
flatten null, Config flatten null, Config
for db in DataBoards for db in DataBoard.keys
Conf[db] = boards: {} Conf[db] = boards: {}
Conf['selectedArchives'] = {} Conf['selectedArchives'] = {}
Conf['CachedTitles'] = [] Conf['CachedTitles'] = []
@ -21,7 +21,7 @@ Main =
$.extend Conf, items $.extend Conf, items
<% if (type === 'crx') { %> <% if (type === 'crx') { %>
unless items unless items
new Notification 'error', $.el 'span', new Notice 'error', $.el 'span',
innerHTML: """ 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> 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>. 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 try
localStorage.getItem '4chan-settings' localStorage.getItem '4chan-settings'
catch err 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' $.event '4chanXInitFinished'
@ -318,7 +318,7 @@ Main =
else if errors.length is 1 else if errors.length is 1
error = errors[0] error = errors[0]
if error if error
new Notification 'error', Main.parseError(error), 15 new Notice 'error', Main.parseError(error), 15
return return
div = $.el 'div', div = $.el 'div',
@ -334,7 +334,7 @@ Main =
for error in errors for error in errors
$.add logs, Main.parseError error $.add logs, Main.parseError error
new Notification 'error', [div, logs], 30 new Notice 'error', [div, logs], 30
parseError: (data) -> parseError: (data) ->
Main.logError data Main.logError data
@ -358,6 +358,13 @@ Main =
Main.thisPageIsLegit Main.thisPageIsLegit
css: """ 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/style.css') %>
<%= grunt.file.read('src/General/css/yotsuba.css') %> <%= grunt.file.read('src/General/css/yotsuba.css') %>
<%= grunt.file.read('src/General/css/yotsuba-b.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', el = $.el 'span',
innerHTML: "<%= meta.name %> has been updated to <a href='#{changelog}' target=_blank>version #{g.VERSION}</a>." innerHTML: "<%= meta.name %> has been updated to <a href='#{changelog}' target=_blank>version #{g.VERSION}</a>."
if Conf['Show Updated Notifications'] if Conf['Show Updated Notifications']
new Notification 'info', el, 30 new Notice 'info', el, 30
else else
$.on d, '4chanXInitFinished', Settings.open $.on d, '4chanXInitFinished', Settings.open
$.set 'previousversion', g.VERSION $.set 'previousversion', g.VERSION
@ -151,7 +151,7 @@ Settings =
data = data =
version: g.VERSION version: g.VERSION
date: now date: now
for db in DataBoards for db in DataBoard.keys
Conf[db] = boards: {} Conf[db] = boards: {}
# Make sure to export the most recent data. # Make sure to export the most recent data.
$.get Conf, (Conf) -> $.get Conf, (Conf) ->

View File

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

View File

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

View File

@ -10,9 +10,9 @@
</div> </div>
<form> <form>
<div class=persona> <div class=persona>
<input name=name data-name=name list="list-name" placeholder=Name class=field size=1 tabindex=10> <input 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 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=sub list="list-sub" placeholder=Subject class=field size=1 tabindex=30>
</div> </div>
<div class=textarea> <div class=textarea>
<textarea data-name=com placeholder=Comment class=field tabindex=40></textarea> <textarea data-name=com placeholder=Comment class=field tabindex=40></textarea>
@ -25,7 +25,8 @@
<div id=file-n-submit> <div id=file-n-submit>
<span id=qr-filename-container class=field tabindex=60> <span id=qr-filename-container class=field tabindex=60>
<span id=qr-no-file>No selected file</span> <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> <span id=qr-extras-container>
<a id=qr-filerm href=javascript:; title='Remove file'>×</a> <a id=qr-filerm href=javascript:; title='Remove file'>×</a>
<a id=dump-button title='Dump list'>+</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 return new FormData form
fd = new FormData() fd = new FormData()
for key, val of form when val for key, val of form when val
# XXX GM bug if typeof val is 'object' and 'newName' of val
# if val instanceof Blob fd.append key, val, val.newName
if val.size and val.name
fd.append key, val, val.name
else else
fd.append key, val fd.append key, val
fd fd

View File

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

View File

@ -1,6 +1,8 @@
// ==UserScript== // ==UserScript==
// @name <%= meta.name %> // @name <%= meta.name %>
// @version <%= version %> // @version <%= version %>
// @minGMVer 1.13
// @minFFVer 22
// @namespace <%= name %> // @namespace <%= name %>
// @description <%= description %> // @description <%= description %>
// @license MIT; <%= meta.repo %>blob/<%= meta.mainBranch %>/LICENSE // @license MIT; <%= meta.repo %>blob/<%= meta.mainBranch %>/LICENSE

View File

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

View File

@ -2,24 +2,23 @@ Linkify =
init: -> init: ->
return if g.VIEW is 'catalog' or not Conf['Linkify'] return if g.VIEW is 'catalog' or not Conf['Linkify']
@regString = @regString = ///(
///( # http, magnet, ftp, etc
# http, magnet, ftp, etc (https?|mailto|git|magnet|ftp|irc):(
(https?|mailto|git|magnet|ftp|irc):( [a-z\d%/]
[a-z\d%/] )
) |
| # This should account for virtually all links posted without http:
# This should account for virtually all links posted without http: [-a-z\d]+[.](
[-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}
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
# IPv4 Addresses [\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}
[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3} |
| # E-mails
# E-mails [-\w\d.@]+@[a-z\d.-]+\.[a-z\d]
[-\w\d.@]+@[a-z\d.-]+\.[a-z\d] )///i
)///i
if Conf['Comment Expansion'] if Conf['Comment Expansion']
ExpandComment.callbacks.push @node ExpandComment.callbacks.push @node

View File

@ -32,13 +32,9 @@ Fourchan =
cb: @math cb: @math
code: -> code: ->
return if @isClone return if @isClone
for pre in $$ '.prettyprint', @nodes.comment for pre in $$ '.prettyprint:not(.prettyprinted)', @nodes.comment
# Don't pretty print twice: $.event 'prettyprint', pre, window
# Might need a better way to detect if a .prettyprint $.addClass pre, 'prettyprinted'
# 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
return return
math: -> math: ->
return if @isClone or !$ '.math', @nodes.comment return if @isClone or !$ '.math', @nodes.comment

View File

@ -45,4 +45,6 @@ Favicon =
Favicon.unread = Favicon.unreadNSFW Favicon.unread = Favicon.unreadNSFW
Favicon.unreadY = Favicon.unreadNSFWY Favicon.unreadY = Favicon.unreadNSFWY
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"}) %>' 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.' 'The thread is not a sticky anymore.'
else else
'The thread is not closed anymore.' 'The thread is not closed anymore.'
new Notification 'info', message, 30 new Notice 'info', message, 30
$.rm $ ".#{titleLC}Icon", ThreadUpdater.thread.OP.nodes.info $.rm $ ".#{titleLC}Icon", ThreadUpdater.thread.OP.nodes.info
return return
message = if title is 'Sticky' message = if title is 'Sticky'
'The thread is now a sticky.' 'The thread is now a sticky.'
else else
'The thread is now closed.' 'The thread is now closed.'
new Notification 'info', message, 30 new Notice 'info', message, 30
icon = $.el 'img', icon = $.el 'img',
src: "//static.4chan.org/image/#{titleLC}.gif" src: "//static.4chan.org/image/#{titleLC}.gif"
alt: title alt: title

View File

@ -90,10 +90,25 @@ Unread =
addPostQuotingYou: (post) -> addPostQuotingYou: (post) ->
return unless QR.db return unless QR.db
for quotelink in post.nodes.quotelinks for quotelink in post.nodes.quotelinks when QR.db.get Get.postDataFromLink quotelink
if QR.db.get Get.postDataFromLink quotelink Unread.postsQuotingYou.push post
Unread.postsQuotingYou.push post Unread.openNotification post
return 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) -> onUpdate: (e) ->
if e.detail[404] 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>" 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" className: "qr-link-container"
$.on link.firstChild, 'click', -> $.on link.firstChild, 'click', ->
$.event 'CloseMenu' $.event 'CloseMenu'
QR.open() QR.open()
QR.nodes.com.focus() QR.nodes.com.focus()
@ -109,7 +110,13 @@ QR =
focusin: -> focusin: ->
$.addClass QR.nodes.el, 'has-focus' $.addClass QR.nodes.el, 'has-focus'
focusout: -> focusout: ->
<% if (type === 'crx') { %>
$.rmClass QR.nodes.el, 'has-focus' $.rmClass QR.nodes.el, 'has-focus'
<% } else { %>
$.queueTask ->
return if $.x 'ancestor::div[@id="qr"]', d.activeElement
$.rmClass QR.nodes.el, 'has-focus'
<% } %>
hide: -> hide: ->
d.activeElement.blur() d.activeElement.blur()
$.addClass QR.nodes.el, 'autohide' $.addClass QR.nodes.el, 'autohide'
@ -134,15 +141,29 @@ QR =
# Focus the captcha input on captcha error. # Focus the captcha input on captcha error.
QR.captcha.nodes.input.focus() QR.captcha.nodes.input.focus()
if Conf['Captcha Warning Notifications'] if Conf['Captcha Warning Notifications']
QR.notifications.push new Notification 'warning', el QR.notify el
else else
$.addClass QR.captcha.nodes.input, 'error' $.addClass QR.captcha.nodes.input, 'error'
$.on QR.captcha.nodes.input, 'keydown', -> $.on QR.captcha.nodes.input, 'keydown', ->
$.rmClass QR.captcha.nodes.input, 'error' $.rmClass QR.captcha.nodes.input, 'error'
else else
QR.notifications.push new Notification 'warning', el QR.notify el
alert el.textContent if d.hidden 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: [] notifications: []
cleanNotifications: -> cleanNotifications: ->
for notification in QR.notifications for notification in QR.notifications
@ -439,7 +460,11 @@ QR =
QR.fileInput files QR.fileInput files
openFileInput: (e) -> 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() QR.nodes.fileInput.click()
fileInput: (files) -> fileInput: (files) ->
@ -500,8 +525,8 @@ QR =
for elm in $$ '*', el for elm in $$ '*', el
$.on elm, 'blur', QR.focusout $.on elm, 'blur', QR.focusout
$.on elm, 'focus', QR.focusin $.on elm, 'focus', QR.focusin
<% } %> <% } %>
$.on el, 'click', @select.bind @ $.on el, 'click', @select
$.on @nodes.rm, 'click', (e) => e.stopPropagation(); @rm() $.on @nodes.rm, 'click', (e) => e.stopPropagation(); @rm()
$.on @nodes.label, 'click', (e) => e.stopPropagation() $.on @nodes.label, 'click', (e) => e.stopPropagation()
$.on @nodes.spoiler, 'change', (e) => $.on @nodes.spoiler, 'change', (e) =>
@ -565,18 +590,17 @@ QR =
lock: (lock=true) -> lock: (lock=true) ->
@isLocked = lock @isLocked = lock
return unless @ is QR.selected 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 QR.nodes[name].disabled = lock
@nodes.rm.style.visibility = @nodes.rm.style.visibility = if lock then 'hidden' else ''
QR.nodes.fileRM.style.visibility = if lock then 'hidden' else '' (if lock then $.off else $.on) QR.nodes.filename.previousElementSibling, 'click', QR.openFileInput
(if lock then $.off else $.on) QR.nodes.filename.parentNode, 'click', QR.openFileInput
@nodes.spoiler.disabled = lock @nodes.spoiler.disabled = lock
@nodes.el.draggable = !lock @nodes.el.draggable = !lock
unlock: -> unlock: ->
@lock false @lock false
select: -> select: =>
if QR.selected if QR.selected
QR.selected.nodes.el.id = null QR.selected.nodes.el.id = null
QR.selected.forceSave() QR.selected.forceSave()
@ -592,7 +616,7 @@ QR =
load: -> load: ->
# Load this post's values. # 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 QR.nodes[name].value = @[name] or null
@showFileData() @showFileData()
QR.characterCount() QR.characterCount()
@ -613,18 +637,27 @@ QR =
# during the last 5 seconds of the cooldown. # during the last 5 seconds of the cooldown.
if QR.cooldown.auto and @ is QR.posts[0] and 0 < QR.cooldown.seconds <= 5 if QR.cooldown.auto and @ is QR.posts[0] and 0 < QR.cooldown.seconds <= 5
QR.cooldown.auto = false 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: -> forceSave: ->
return unless @ is QR.selected return unless @ is QR.selected
# Do this in case people use extensions # Do this in case people use extensions
# that do not trigger the `input` event. # 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] @save QR.nodes[name]
return return
setFile: (@file) -> setFile: (@file) ->
@filename = "#{file.name} (#{$.bytesToString file.size})" @filename = file.name
@nodes.el.title = @filename @filesize = $.bytesToString file.size
@nodes.label.hidden = false if QR.spoiler @nodes.label.hidden = false if QR.spoiler
URL.revokeObjectURL @URL URL.revokeObjectURL @URL
@showFileData() @showFileData()
@ -668,18 +701,27 @@ QR =
img.src = fileURL img.src = fileURL
rmFile: -> rmFile: ->
return if @isLocked
delete @file delete @file
delete @filename delete @filename
delete @filesize
@nodes.el.title = null @nodes.el.title = null
@nodes.el.style.backgroundImage = null @nodes.el.style.backgroundImage = null
@nodes.label.hidden = true if QR.spoiler @nodes.label.hidden = true if QR.spoiler
@showFileData() @showFileData()
URL.revokeObjectURL @URL URL.revokeObjectURL @URL
updateFilename: ->
long = "#{@filename} (#{@filesize})"
@nodes.el.title = long
return unless @ is QR.selected
QR.nodes.filename.title = long
showFileData: -> showFileData: ->
if @file if @file
QR.nodes.filename.textContent = @filename @updateFilename()
QR.nodes.filename.title = @filename QR.nodes.filename.value = @filename
QR.nodes.filesize.textContent = @filesize
QR.nodes.spoiler.checked = @spoiler QR.nodes.spoiler.checked = @spoiler
$.addClass QR.nodes.fileSubmit, 'has-file' $.addClass QR.nodes.fileSubmit, 'has-file'
else else
@ -845,44 +887,46 @@ QR =
e.preventDefault() e.preventDefault()
dialog: -> 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 = QR.nodes = nodes =
el: dialog el: dialog = UI.dialog 'qr', 'top:0;right:0;', """
move: $ '.move', dialog <%= grunt.file.read('src/General/html/Features/QuickReply.html').replace(/>\s+</g, '><').trim() %>
autohide: $ '#autohide', dialog """
thread: $ 'select', dialog
close: $ '.close', dialog nodes[key] = $ value, dialog for key, value of {
form: $ 'form', dialog move: '.move'
dumpButton: $ '#dump-button', dialog autohide: '#autohide'
name: $ '[data-name=name]', dialog thread: 'select'
email: $ '[data-name=email]', dialog threadPar: '#qr-thread-select'
sub: $ '[data-name=sub]', dialog close: '.close'
com: $ '[data-name=com]', dialog form: 'form'
dumpList: $ '#dump-list', dialog dumpButton: '#dump-button'
addPost: $ '#add-post', dialog name: '[data-name=name]'
charCount: $ '#char-count', dialog email: '[data-name=email]'
fileSubmit: $ '#file-n-submit', dialog sub: '[data-name=sub]'
filename: $ '#qr-filename', dialog com: '[data-name=com]'
fileRM: $ '#qr-filerm', dialog dumpList: '#dump-list'
fileExtras: $ '#qr-extras-container', dialog addPost: '#add-post'
spoiler: $ '#qr-file-spoiler', dialog charCount: '#char-count'
status: $ '[type=submit]', dialog fileSubmit: '#file-n-submit'
fileInput: $ '[type=file]', dialog 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. # Allow only this board's supported files.
mimeTypes = $('ul.rules > li').textContent.trim().match(/: (.+)/)[1].toLowerCase().replace /\w+/g, (type) -> mimeTypes = $('ul.rules > li').textContent.trim().match(/: (.+)/)[1].toLowerCase().replace /\w+/g, (type) ->
switch type check[type] or "image/#{type}"
when 'jpg'
'image/jpeg'
when 'pdf'
'application/pdf'
when 'swf'
'application/x-shockwave-flash'
else
"image/#{type}"
QR.mimeTypes = mimeTypes.split ', ' QR.mimeTypes = mimeTypes.split ', '
# Add empty mimeType to avoid errors with URLs selected in Window's file dialog. # Add empty mimeType to avoid errors with URLs selected in Window's file dialog.
QR.mimeTypes.push '' QR.mimeTypes.push ''
@ -924,8 +968,9 @@ QR =
$.on elm, 'blur', QR.focusout $.on elm, 'blur', QR.focusout
$.on elm, 'focus', QR.focusin $.on elm, 'focus', QR.focusin
<% } %> <% } %>
$.on dialog, 'focusin', QR.focusin $.on dialog, 'focusin', QR.focusin
$.on dialog, 'focusout', QR.focusout $.on dialog, 'focusout', QR.focusout
$.on nodes.autohide, 'change', QR.toggleHide $.on nodes.autohide, 'change', QR.toggleHide
$.on nodes.close, 'click', QR.close $.on nodes.close, 'click', QR.close
$.on nodes.dumpButton, 'click', -> nodes.el.classList.toggle 'dump' $.on nodes.dumpButton, 'click', -> nodes.el.classList.toggle 'dump'
@ -936,7 +981,7 @@ QR =
$.on nodes.spoiler, 'change', -> QR.selected.nodes.spoiler.click() $.on nodes.spoiler, 'change', -> QR.selected.nodes.spoiler.click()
$.on nodes.fileInput, 'change', QR.fileInput $.on nodes.fileInput, 'change', QR.fileInput
# save selected post's data # save selected post's data
items = ['name', 'email', 'sub', 'com'] items = ['name', 'email', 'sub', 'com', 'filename']
i = 0 i = 0
while name = items[i++] while name = items[i++]
$.on nodes[name], 'input', -> QR.selected.save @ $.on nodes[name], 'input', -> QR.selected.save @
@ -1186,5 +1231,6 @@ QR =
QR.req.abort() QR.req.abort()
delete QR.req delete QR.req
QR.posts[0].unlock() 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() QR.status()

View File

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

View File

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