Merge branch 'v3'

Conflicts:
	CHANGELOG.md
	Gruntfile.coffee
	LICENSE
	builds/appchan-x.user.js
	builds/crx/manifest.json
	builds/crx/script.js
	package.json
	src/General/Config.coffee
	src/General/Header.coffee
	src/General/Main.coffee
	src/General/Settings.coffee
	src/General/html/Features/QuickReply.html
	src/General/img/favicons/xat-/unreadDead.png
	src/General/img/favicons/xat-/unreadDeadY.png
	src/General/img/favicons/xat-/unreadNSFW.png
	src/General/img/favicons/xat-/unreadNSFWY.png
	src/General/img/favicons/xat-/unreadSFW.png
	src/General/img/favicons/xat-/unreadSFWY.png
	src/General/meta/banner.js
	src/General/meta/metadata.js
	src/Images/ImageExpand.coffee
	src/Monitoring/Favicon.coffee
	src/Monitoring/ThreadWatcher.coffee
	src/Posting/QuickReply.coffee
This commit is contained in:
Zixaphir 2013-08-18 11:17:07 -07:00
commit 72e3fc2585
55 changed files with 2088 additions and 522 deletions

View File

@ -1,3 +1,16 @@
**MayhemYDG**:
- **New feature**: `Desktop Notifications`
- Enabled by default, but you will have to grant your browser permissions to display them or disable them altogether:<br>
![authorize or disable](img/changelog/3.9.0/0.png)
- Clicking on a notification will bring up the relevant tab. (Does not work on Firefox unfortunately, [see bug 874050](https://bugzilla.mozilla.org/show_bug.cgi?id=874050).)
- Notifications will appear when someone quotes you, clicking such notification will also scroll the thread to the relevant post.
- Notifications will appear for posting errors instead of alert popups.
- Opera does *not* support desktop notifications yet.
![filename editing](img/changelog/3.8.0/0.gif)
- The QR now allows you to edit the filename on the fly:
**seaweedchan**:
- Optimizations for the banner and board title code

View File

@ -135,7 +135,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

@ -78,15 +78,6 @@
*
* license: https://github.com/4chan/4chan-JS/blob/master/LICENSE
*
* Linkify: (http://userscripts.org/scripts/show/1352)
* Copyright (c) 2011, Anthony Lieuallen
* All rights reserved.
* Originally written by Anthony Lieuallen of http://arantius.com/
* Licensed for unlimited modification and redistribution as long as
* this notice is kept intact.
*
* license: http://userscripts.org/scripts/review/1352
*
* jsColor: (http://jscolor.com/)
* Copyright (c) Jan Odvarko, http://odvarko.cz
*

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==

View File

@ -1,6 +1,8 @@
// ==UserScript==
// @name appchan x
// @version 2.3.3
// @minGMVer 1.13
// @minFFVer 22
// @namespace zixaphir
// @description The most comprehensive 4chan userscript.
// @license MIT; https://github.com/zixaphir/appchan-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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -19,6 +19,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",
@ -29,7 +30,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

@ -13,6 +13,10 @@ Config =
true
'Add button to hide 4chan announcements.'
]
'Desktop Notifications': [
true
'Enables desktop notifications across various <%= meta.name %> features.'
]
'404 Redirect': [
true
'Redirect dead threads and images.'
@ -826,6 +830,7 @@ http://iqdb.org/?url=%TURL
'Bottom Header': false
'Header catalog links': false
'Bottom Board List': true
'Shortcut Icons': false
'Custom Board Navigation': true
boardnav: """

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

@ -36,6 +36,7 @@ Header =
$.sync 'Fixed Header', Header.setBarFixed
$.sync 'Bottom Header', Header.setBarPosition
$.event 'AddMenuEntry',
type: 'header'
el: $.el 'span',
@ -60,6 +61,7 @@ Header =
$.prepend d.body, @bar
$.add d.body, Header.hover
@setBarPosition Conf['Bottom Header']
@
bar: $.el 'div',
id: 'header-bar'
@ -200,6 +202,21 @@ Header =
Conf['Fixed Header'] = @checked
$.set 'Fixed Header', @checked
setShortcutIcons: (show) ->
Header.shortcutToggler.checked = show
if show
$.addClass doc, 'shortcut-icons'
else
$.rmClass doc, 'shortcut-icons'
toggleShortcutIcons: ->
$.event 'CloseMenu'
Header.setShortcutIcons @checked
Conf['Shortcut Icons'] = @checked
$.set 'Shortcut Icons', @checked
setBarVisibility: (hide) ->
Header.headerToggler.checked = hide
$.event 'CloseMenu'
@ -220,8 +237,7 @@ Header =
'automatically hide itself.'
else
'remain visible.'}"
new Notification 'info', message, 2
new Notice 'info', message, 2
setCustomNav: (show) ->
Header.customNavToggler.checked = show
@ -265,5 +281,33 @@ 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
areNotificationsEnabled: false
enableDesktopNotifications: ->
return unless window.Notification and Conf['Desktop Notifications']
switch Notification.permission
when 'granted'
Header.areNotificationsEnabled = true
return
when 'denied'
# requestPermission doesn't work if status is 'denied',
# but it'll still work if status is 'default'.
return
el = $.el 'span',
innerHTML: """
Desktop notification permissions are not granted:<br>
<button>Authorize</button> or <button>Disable</button>
"""
[authorize, disable] = $$ 'button', el
$.on authorize, 'click', ->
Notification.requestPermission (status) ->
Header.areNotificationsEnabled = status is 'granted'
return if status is 'default'
notice.close()
$.on disable, 'click', ->
$.set 'Desktop Notifications', false
notice.close()
notice = new Notice 'info', el

View File

@ -14,8 +14,7 @@ Main =
flatten null, Config
# Unflattened Config.
for db in DataBoards
for db in DataBoard.keys
Conf[db] = boards: {}
$.extend Conf,
@ -33,7 +32,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>.
@ -234,7 +233,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'
@ -316,7 +315,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',
@ -332,7 +331,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

View File

@ -2,7 +2,7 @@ Settings =
init: ->
# Appchan X settings link
el = $.el 'a',
className: 'settings-link'
className: 'settings-link icon icon-wrench'
href: 'javascript:;'
textContent: 'Settings'
$.on el, 'click', Settings.open
@ -25,7 +25,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
@ -171,7 +171,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

@ -19,8 +19,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: ->
@ -29,7 +28,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) ->
@ -111,7 +110,7 @@ UI = do ->
$.add entry.el, submenu
return
close = ->
close: =>
$.rm currentMenu
$.rmClass lastToggledButton, 'active'
currentMenu = null
@ -124,7 +123,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
@ -182,7 +181,7 @@ UI = do ->
style.left = left
style.right = right
addEntry: (e) ->
addEntry: (e) =>
entry = e.detail
return if entry.type isnt @type
@parseEntry entry

1167
src/General/css/font-awesome.css vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1501,6 +1501,19 @@ input:not([type=radio]) {
.has-file #qr-filename {
display: block;
}
#qr-filename {
border: medium none;
vertical-align: top;
padding: 0;
margin: 0;
height: auto;
background: transparent none;
overflow: hidden;
text-overflow: ellipsis;
}
#qr-filename:not(.edit) {
pointer-events: none;
}
.has-file #qr-filerm {
display: inline-block;
}

View File

@ -206,8 +206,7 @@ a {
content: "]\\00a0";
}
.dead-thread,
.disabled,
.expand-all-shortcut {
.disabled {
opacity: .45;
}
#shortcuts {
@ -773,21 +772,34 @@ input.field.tripped:not(:hover):not(:focus) {
height: 24px;
}
/* Fake File Input */
input#qr-filename {
border: none !important;
width: 80%;
padding: 0px 4px;
position: relative;
bottom: 1px;
background: none !important;
}
input#qr-filename:not(.edit) {
pointer-events: none;
}
#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;
padding: 1px 4px;
}
#qr-filename-container {
-moz-box-sizing: border-box;
@ -1026,7 +1038,6 @@ a:only-of-type > .remove {
.boardSubtitle[contenteditable="true"] {
cursor: text !important;
}
/* Link Title Favicons */
.linkify.YouTube {
background: transparent url('data:image/png;base64,<%= grunt.file.read("src/General/img/links/youtube.png", {encoding: "base64"}) %>') center left no-repeat!important;

View File

@ -9,9 +9,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>
@ -24,7 +24,7 @@
<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-extras-container>
<label id=qr-spoiler-label>
<input type=checkbox id=qr-file-spoiler title='Spoiler image' tabindex=70>
@ -43,4 +43,4 @@
<datalist id="list-name"></datalist>
<datalist id="list-email"></datalist>
<datalist id="list-sub"></datalist>

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

View File

@ -41,10 +41,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

@ -3,4 +3,4 @@
<%= grunt.file.read('src/General/lib/post.class') %>
<%= grunt.file.read('src/General/lib/clone.class') %>
<%= grunt.file.read('src/General/lib/databoard.class') %>
<%= grunt.file.read('src/General/lib/notification.class') %>
<%= grunt.file.read('src/General/lib/notice.class') %>

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,4 +1,4 @@
class Notification
class Notice
constructor: (type, content, @timeout) ->
@el = $.el 'div',
innerHTML: '<a href=javascript:; class=close title=Close>✖</a><div class=message></div>'
@ -25,4 +25,5 @@ class Notification
setTimeout @close, @timeout * $.SECOND if @timeout
close: =>
$.rm @el
$.off d, 'visibilitychange', @add
$.rm @el

View File

@ -1,9 +1,21 @@
Polyfill =
init: ->
<% if (type === 'crx') { %>
Polyfill.toBlob()
Polyfill.visibility()
@notificationPermission()
@toBlob()
@visibility()
<% } %>
notificationPermission: ->
return if !window.Notification or 'permission' of Notification
Object.defineProperty Notification, 'permission',
get: ->
switch webkitNotifications.checkPermission()
when 0
'granted'
when 1
'default'
when 2
'denied'
toBlob: ->
HTMLCanvasElement::toBlob or= (cb) ->
data = atob @toDataURL()[22..]

View File

@ -78,15 +78,6 @@
*
* license: https://github.com/4chan/4chan-JS/blob/master/LICENSE
*
* Linkify: (http://userscripts.org/scripts/show/1352)
* Copyright (c) 2011, Anthony Lieuallen
* All rights reserved.
* Originally written by Anthony Lieuallen of http://arantius.com/
* Licensed for unlimited modification and redistribution as long as
* this notice is kept intact.
*
* license: http://userscripts.org/scripts/review/1352
*
* jsColor: (http://jscolor.com/)
* Copyright (c) Jan Odvarko, http://odvarko.cz
*

View File

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

View File

@ -4,9 +4,9 @@ ImageExpand =
@EAI = $.el 'a',
id: 'img-controls'
className: 'expand-all-shortcut'
title: 'Expand All Images'
href: 'javascript:;'
className: 'expand-all-shortcut icon icon-resize-full'
title: 'Expand All Images'
href: 'javascript:;'
$.on @EAI, 'click', ImageExpand.cb.toggleAll
@ -27,7 +27,7 @@ ImageExpand =
ImageExpand.contract @
ImageExpand.expand @
return
if ImageExpand.on and !@isHidden
if ImageExpand.on and !@isHidden and (Conf['Expand spoilers'] or !@file.isSpoiler)
ImageExpand.expand @
cb:
toggle: (e) ->
@ -37,11 +37,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 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 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
@ -55,40 +54,40 @@ Linkify =
while result = test.exec data
{index} = result
endNode = node
word = result[0]
# End of node, not necessarily end of space-delimited string
if (length = index + result[0].length) is data.length
if (length = index + word.length) is data.length
test.lastIndex = 0
while (saved = snapshot.snapshotItem i++)
break if saved.nodeName is 'BR'
if saved.nodeName is 'BR'
break
endNode = saved
{length} = saved.data
endNode = saved
{data} = saved
word += data
{length} = data
if end = space.exec saved.data
if end = space.exec data
# Set our snapshot and regex to start on this node at this position when the loop resumes
test.lastIndex = length = end.index
i--
break
test.lastIndex = 0 if length is endNode.data.length
range = Linkify.makeRange node, endNode, index, length
links.push range if link = Linkify.regString.exec range.toString()
break
if Linkify.regString.exec word
links.push Linkify.makeRange node, endNode, index, length
else
if link = Linkify.regString.exec result[0]
range = Linkify.makeRange node, node, index, length
links.push range
break unless test.lastIndex and node is endNode
for range in links.reverse()
@nodes.links.push Linkify.makeLink range, @
for link in links.reverse()
@nodes.links.push Linkify.makeLink link, @
return unless Conf['Embedding'] or Conf['Link Title']
items = @nodes.links
{links} = @nodes
i = 0
while range = items[i++]
if data = Linkify.services range
while link = links[i++]
if data = Linkify.services link
Linkify.embed data if Conf['Embedding']
Linkify.title data if Conf['Link Title']
@ -126,12 +125,9 @@ Linkify =
if i
range.setEnd range.endContainer, range.endOffset - i
# This is the only piece of code left based on Anthony Lieuallen's Linkify
text =
if text.contains ':'
text
else (
if text.contains '@'
unless /(https?|mailto|git|magnet|ftp|irc):/.test text
text = (
if /@/.test text
'mailto:'
else
'http://'

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

@ -9,43 +9,49 @@ Favicon =
Favicon.switch()
switch: ->
unreadDead = Favicon.unreadDeadY = Favicon.unreadSFW = Favicon.unreadSFWY = Favicon.unreadNSFW = Favicon.unreadNSFWY = 'data:image/png;base64,'
switch Conf['favicon']
when 'ferongr'
Favicon.unreadDead += '<%= grunt.file.read("src/General/img/favicons/ferongr/unreadDead.png", {encoding: "base64"}) %>'
Favicon.unreadDeadY += '<%= grunt.file.read("src/General/img/favicons/ferongr/unreadDeadY.png", {encoding: "base64"}) %>'
Favicon.unreadSFW += '<%= grunt.file.read("src/General/img/favicons/ferongr/unreadSFW.png", {encoding: "base64"}) %>'
Favicon.unreadSFWY += '<%= grunt.file.read("src/General/img/favicons/ferongr/unreadSFWY.png", {encoding: "base64"}) %>'
Favicon.unreadNSFW += '<%= grunt.file.read("src/General/img/favicons/ferongr/unreadNSFW.png", {encoding: "base64"}) %>'
Favicon.unreadNSFWY += '<%= grunt.file.read("src/General/img/favicons/ferongr/unreadNSFWY.png", {encoding: "base64"}) %>'
when 'xat-'
Favicon.unreadDead += '<%= grunt.file.read("src/General/img/favicons/xat-/unreadDead.png", {encoding: "base64"}) %>'
Favicon.unreadDeadY += '<%= grunt.file.read("src/General/img/favicons/xat-/unreadDeadY.png", {encoding: "base64"}) %>'
Favicon.unreadSFW += '<%= grunt.file.read("src/General/img/favicons/xat-/unreadSFW.png", {encoding: "base64"}) %>'
Favicon.unreadSFWY += '<%= grunt.file.read("src/General/img/favicons/xat-/unreadSFWY.png", {encoding: "base64"}) %>'
Favicon.unreadNSFW += '<%= grunt.file.read("src/General/img/favicons/xat-/unreadNSFW.png", {encoding: "base64"}) %>'
Favicon.unreadNSFWY += '<%= grunt.file.read("src/General/img/favicons/xat-/unreadNSFWY.png", {encoding: "base64"}) %>'
when 'Mayhem'
Favicon.unreadDead += '<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadDead.png", {encoding: "base64"}) %>'
Favicon.unreadDeadY += '<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadDeadY.png", {encoding: "base64"}) %>'
Favicon.unreadSFW += '<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadSFW.png", {encoding: "base64"}) %>'
Favicon.unreadSFWY += '<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadSFWY.png", {encoding: "base64"}) %>'
Favicon.unreadNSFW += '<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadNSFW.png", {encoding: "base64"}) %>'
Favicon.unreadNSFWY += '<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadNSFWY.png", {encoding: "base64"}) %>'
when '4chanJS'
Favicon.unreadDead += '<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadDead.png", {encoding: "base64"}) %>'
Favicon.unreadDeadY += '<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadDeadY.png", {encoding: "base64"}) %>'
Favicon.unreadSFW += '<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadSFW.png", {encoding: "base64"}) %>'
Favicon.unreadSFWY += '<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadSFWY.png", {encoding: "base64"}) %>'
Favicon.unreadNSFW += '<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadNSFW.png", {encoding: "base64"}) %>'
Favicon.unreadNSFWY += '<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadNSFWY.png", {encoding: "base64"}) %>'
when 'Original'
Favicon.unreadDead += '<%= grunt.file.read("src/General/img/favicons/Original/unreadDead.png", {encoding: "base64"}) %>'
Favicon.unreadDeadY += '<%= grunt.file.read("src/General/img/favicons/Original/unreadDeadY.png", {encoding: "base64"}) %>'
Favicon.unreadSFW += '<%= grunt.file.read("src/General/img/favicons/Original/unreadSFW.png", {encoding: "base64"}) %>'
Favicon.unreadSFWY += '<%= grunt.file.read("src/General/img/favicons/Original/unreadSFWY.png", {encoding: "base64"}) %>'
Favicon.unreadNSFW += '<%= grunt.file.read("src/General/img/favicons/Original/unreadNSFW.png", {encoding: "base64"}) %>'
Favicon.unreadNSFWY += '<%= grunt.file.read("src/General/img/favicons/Original/unreadNSFWY.png", {encoding: "base64"}) %>'
t = 'data:image/png;base64,'
f = Favicon
[f.unreadDead, funreadDeadY, f.unreadSFW, f.unreadSFWY, f.unreadNSFW, f.unreadNSFWY] = switch Conf['favicon']
when 'ferongr' then [
t + '<%= grunt.file.read("src/General/img/favicons/ferongr/unreadDead.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/ferongr/unreadDeadY.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/ferongr/unreadSFW.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/ferongr/unreadSFWY.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/ferongr/unreadNSFW.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/ferongr/unreadNSFWY.png", {encoding: "base64"}) %>'
]
when 'xat-' then [
t + '<%= grunt.file.read("src/General/img/favicons/xat-/unreadDead.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/xat-/unreadDeadY.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/xat-/unreadSFW.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/xat-/unreadSFWY.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/xat-/unreadNSFW.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/xat-/unreadNSFWY.png", {encoding: "base64"}) %>'
]
when 'Mayhem' then [
t + '<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadDead.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadDeadY.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadSFW.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadSFWY.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadNSFW.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadNSFWY.png", {encoding: "base64"}) %>'
]
when '4chanJS' then [
t + '<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadDead.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadDeadY.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadSFW.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadSFWY.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadNSFW.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadNSFWY.png", {encoding: "base64"}) %>'
]
when 'Original' then [
t + '<%= grunt.file.read("src/General/img/favicons/Original/unreadDead.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/Original/unreadDeadY.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/Original/unreadSFW.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/Original/unreadSFWY.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/Original/unreadNSFW.png", {encoding: "base64"}) %>'
t + '<%= grunt.file.read("src/General/img/favicons/Original/unreadNSFWY.png", {encoding: "base64"}) %>'
]
if Favicon.SFW
Favicon.unread = Favicon.unreadSFW
Favicon.unreadY = Favicon.unreadSFWY
@ -54,3 +60,4 @@ Favicon =
Favicon.unreadY = Favicon.unreadNSFWY
dead: 'data:image/png;base64,<%= grunt.file.read("src/General/img/favicons/dead.png", {encoding: "base64"}) %>'
logo: 'data:image/png;base64,<%= grunt.file.read("src/General/img/icon128.png", {encoding: "base64"}) %>'

View File

@ -220,14 +220,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 Header.areNotificationsEnabled
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

@ -17,7 +17,7 @@ QR =
return unless Conf['Header Shortcut'] or Conf['Page Shortcut']
sc = $.el 'a',
className: "qr-shortcut #{unless Conf['Persistent QR'] then 'disabled' else ''}"
className: "qr-shortcut icon icon-comment #{unless Conf['Persistent QR'] then 'disabled' else ''}"
textContent: 'QR'
title: 'Quick Reply'
href: 'javascript:;'
@ -94,7 +94,7 @@ QR =
d.activeElement.blur()
$.rmClass QR.nodes.el, 'dump'
unless Conf['Captcha Warning Notifications']
$.rmClass QR.captcha.nodes.input, 'error'
$.rmClass QR.captcha.nodes.input, 'error' if QR.captcha.isEnabled
if Conf['QR Shortcut']
$.toggleClass $('.qr-shortcut'), 'disabled'
new QR.post true
@ -107,8 +107,13 @@ QR =
$.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'
@ -135,15 +140,33 @@ 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 Header.areNotificationsEnabled
notif = new Notification el.textContent,
body: el.textContent
icon: Favicon.logo
notif.onclick = -> window.focus()
<% if (type === 'crx') { %>
# Firefox automatically closes notifications
# so we can't control the onclose properly.
notif.onclose = -> notice.close()
setTimeout ->
notif.onclose = null
notif.close()
, 5 * $.SECOND
<% } %>
notifications: []
cleanNotifications: ->
@ -441,10 +464,15 @@ QR =
QR.fileInput files
openFileInput: (e) ->
e.preventDefault()
return if e.keyCode and not [32, 13].contains e.keyCode
if e.shiftKey and not e.keyCode
e.stopPropagation()
if e.shiftKey and e.type is 'click'
return QR.selected.rmFile()
if e.ctrlKey and e.type is 'click'
$.addClass QR.nodes.filename, 'edit'
QR.nodes.filename.focus()
return
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) ->
@ -505,8 +533,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) =>
@ -570,18 +598,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()
@ -598,7 +625,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
QR.tripcodeHider.call QR.nodes['name']
@ -621,18 +648,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|swf)$/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()
@ -676,18 +712,27 @@ QR =
img.src = fileURL
rmFile: ->
return if @isLocked
delete @file
delete @filename
delete @filesize
@nodes.el.title = null
QR.nodes.fileContainer.title = ''
@nodes.el.style.backgroundImage = null
@nodes.label.hidden = true if QR.spoiler
@showFileData()
URL.revokeObjectURL @URL
updateFilename: ->
long = "#{@filename} (#{@filesize})\nCtrl+click to edit filename. Shift+click to clear."
@nodes.el.title = long
return unless @ is QR.selected
QR.nodes.fileContainer.title = long
showFileData: ->
if @file
QR.nodes.filename.textContent = @filename
QR.nodes.filename.title = @filename
@updateFilename()
QR.nodes.filename.value = @filename
QR.nodes.spoiler.checked = @spoiler
$.addClass QR.nodes.fileSubmit, 'has-file'
else
@ -860,28 +905,29 @@ QR =
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'
fileRM: '#qr-filerm'
fileExtras: '#qr-extras-container'
spoiler: '#qr-file-spoiler'
spoilerPar: '#qr-spoiler-label'
status: '[type=submit]'
fileInput: '[type=file]'
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'
fileContainer: '#qr-filename-container'
fileRM: '#qr-filerm'
fileExtras: '#qr-extras-container'
spoiler: '#qr-file-spoiler'
spoilerPar: '#qr-spoiler-label'
status: '[type=submit]'
fileInput: '[type=file]'
}
check =
@ -939,15 +985,17 @@ QR =
$.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'
$.on nodes.addPost, 'click', -> new QR.post true
$.on nodes.form, 'submit', QR.submit
$.on nodes.fileRM, 'click', -> QR.selected.rmFile()
$.on nodes.fileExtras, 'click', (e) -> e.stopPropagation()
$.on nodes.filename, 'blur', -> $.rmClass @, 'edit'
$.on nodes.fileRM, 'click', -> QR.selected.rmFile()
$.on nodes.fileExtras, 'click', (e) -> e.stopPropagation()
$.on nodes.spoiler, 'change', -> QR.selected.nodes.spoiler.click()
$.on nodes.fileInput, 'change', QR.fileInput
@ -958,7 +1006,7 @@ QR =
$.on nodes[name], 'mouseover', QR.mouseover
# 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 @
@ -1169,7 +1217,7 @@ QR =
QR.cleanNotifications()
if Conf['Posting Success Notifications']
QR.notifications.push new Notification 'success', h1.textContent, 5
QR.notifications.push new Notice 'success', h1.textContent, 5
QR.persona.set post
@ -1221,7 +1269,8 @@ 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()
mouseover: (e) ->

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

@ -26,6 +26,7 @@ QuoteYou =
for quotelink in @nodes.quotelinks when QR.db.get Get.postDataFromLink quotelink
$.add quotelink, $.tn '\u00A0(You)'
$.addClass quotelink, 'you'
$.addClass @nodes.root, 'quotesYou'
return
@ -36,7 +37,7 @@ QuoteYou =
unless QuoteYou.lastRead
unless post = QuoteYou.lastRead = $ '.quotesYou'
new Notification 'warning', 'No posts are currently quoting you, loser.', 20
new Notice 'warning', 'No posts are currently quoting you, loser.', 20
return
return if QuoteYou.cb.scroll post
else

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