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
@ -1,3 +1,9 @@
|
||||
**MayhemYDG**:
|
||||
|
||||

|
||||
|
||||
- The QR now allows you to edit the filename on the fly:
|
||||
|
||||
### v1.2.32
|
||||
*2013-08-16*
|
||||
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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==
|
||||
|
||||
BIN
img/changelog/3.2.0/0.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
img/changelog/3.8.0/0.gif
Normal file
|
After Width: | Height: | Size: 690 KiB |
@ -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",
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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', ''
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
@ -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
|
||||
@ -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) ->
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
|
Before Width: | Height: | Size: 346 B After Width: | Height: | Size: 335 B |
|
Before Width: | Height: | Size: 456 B After Width: | Height: | Size: 384 B |
|
Before Width: | Height: | Size: 323 B After Width: | Height: | Size: 309 B |
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 376 B |
|
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 309 B |
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 377 B |
|
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 189 B |
|
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 216 B |
|
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 216 B |
|
Before Width: | Height: | Size: 114 B After Width: | Height: | Size: 113 B |
|
Before Width: | Height: | Size: 172 B After Width: | Height: | Size: 133 B |
|
Before Width: | Height: | Size: 274 B After Width: | Height: | Size: 254 B |
|
Before Width: | Height: | Size: 172 B After Width: | Height: | Size: 133 B |
|
Before Width: | Height: | Size: 274 B After Width: | Height: | Size: 256 B |
|
Before Width: | Height: | Size: 172 B After Width: | Height: | Size: 133 B |
|
Before Width: | Height: | Size: 270 B After Width: | Height: | Size: 253 B |
|
Before Width: | Height: | Size: 273 B After Width: | Height: | Size: 254 B |
|
Before Width: | Height: | Size: 349 B After Width: | Height: | Size: 300 B |
|
Before Width: | Height: | Size: 281 B After Width: | Height: | Size: 263 B |
|
Before Width: | Height: | Size: 349 B After Width: | Height: | Size: 311 B |
|
Before Width: | Height: | Size: 280 B After Width: | Height: | Size: 262 B |
|
Before Width: | Height: | Size: 349 B After Width: | Height: | Size: 311 B |
@ -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
|
||||
|
||||
@ -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?()
|
||||
|
||||
@ -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==
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"}) %>'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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++]
|
||||
|
||||
@ -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
|
||||
|
||||