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

Conflicts:
	lib/$.coffee
	lib/ui.coffee
	src/config.coffee
	src/features.coffee
	src/main.coffee
	src/qr.coffee

Also added a proper Chrome build. I'mma have to save my pem file...
This commit is contained in:
Zixaphir 2013-04-15 23:11:10 -07:00
commit 0e103f0526
17 changed files with 815 additions and 389 deletions

View File

@ -1,3 +1,20 @@
### 3.1.3 - *2013-04-16*
- Fix Chrome freezing when switching from the `Filter` tab to another tab in the settings.
### 3.1.2 - *2013-04-16*
- Fix error with successful posting.
### 3.1.1 - *2013-04-16*
- Styling adjustments for the announcement toggler.
## 3.1.0 - *2013-04-16*
- **New feature**: `Announcement Hiding`, enabled by default.
- Fix support for www.4chan.org/frames on Chrome.
- Fix quote features not working on dead quotelinks in inlined posts.
- Fix resurrecting dead quotelinks on HTTP.
### 3.0.6 - *2013-04-14*

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
// ==UserScript==
// @name 4chan X
// @version 3.0.6
// @version 3.1.3
// @namespace 4chan-X
// @description Cross-browser extension for productive lurking on 4chan.
// @copyright 2009-2011 James Campos <james.r.campos@gmail.com>

File diff suppressed because one or more lines are too long

BIN
builds/crx.crx Normal file

Binary file not shown.

View File

@ -1,6 +1,6 @@
{
"name": "4chan X",
"version": "3.0.6",
"version": "3.1.3",
"manifest_version": 2,
"description": "Cross-browser extension for productive lurking on 4chan.",
"icons": {
@ -11,6 +11,7 @@
"content_scripts": [{
"js": ["script.js"],
"matches": ["*://api.4chan.org/*","*://boards.4chan.org/*","*://images.4chan.org/*","*://sys.4chan.org/*"],
"all_frames": true,
"run_at": "document_start"
}],
"homepage_url": "https://4chan-x.just-believe.in/",

File diff suppressed because one or more lines are too long

View File

@ -354,6 +354,15 @@ a[href="javascript:;"] {
overflow: hidden;
}
/* Announcement Hiding */
:root.hide-announcement #globalMessage {
display: none;
}
a.hide-announcement,
a.show-announcement {
float: left;
}
/* Unread */
#unread-line {
margin: 0;

View File

@ -4,6 +4,8 @@
$ = (selector, root=d.body) ->
root.querySelector selector
$.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000)))
$$ = (selector, root=d.body) ->
[root.querySelectorAll(selector)...]
@ -48,10 +50,7 @@ $.extend String::,
contains: (string) ->
@indexOf(string) > -1
$.DAY = 24 * ($.HOUR = 60 * ($.MINUTE = 60 * ($.SECOND = 1000)))
$.extend $,
engine: '<% if (type === 'crx') { %>webkit<% } else if (type === 'userjs') { %>presto<% } else { %>gecko<% } %>'
id: (id) ->
d.getElementById id
ready: (fc) ->
@ -146,7 +145,8 @@ $.extend $,
rmAll: (root) ->
# jsperf.com/emptify-element
while node = root.firstChild
$.rm node
# HTMLSelectElement.remove !== Element.remove
root.removeChild node
return
tn: (s) ->
d.createTextNode s

View File

@ -4,14 +4,17 @@ UI = do ->
className: 'dialog'
innerHTML: html
id: id
el.style.cssText = position
$.get "#{id}.position", position, (item) ->
el.style.cssText = item["#{id}.position"]
move = $ '.move', el
$.on move, 'touchstart mousedown', dragstart
for child in move.children
continue unless child.tagName
$.on child, 'touchstart mousedown', (e) ->
e.stopPropagation()
el
class Menu

View File

@ -1,6 +1,6 @@
{
"name": "4chan-X",
"version": "3.0.6",
"version": "3.1.3",
"description": "Cross-browser extension for productive lurking on 4chan.",
"meta": {
"name": "4chan X",
@ -20,7 +20,7 @@
"grunt-bump": "~0.0.0",
"grunt-contrib-clean": "~0.4.0",
"grunt-contrib-coffee": "~0.6.6",
"grunt-contrib-compress": "~0.4.9",
"grunt-contrib-compress": "~0.4.10",
"grunt-contrib-concat": "~0.2.0",
"grunt-contrib-copy": "~0.4.1",
"grunt-contrib-watch": "~0.3.1",

View File

@ -21,6 +21,10 @@ Config =
true
'Show custom links instead of the full board list.'
]
'Announcement Hiding': [
true
'Add button to hide 4chan announcements.'
]
'404 Redirect': [
true
'Redirect dead threads and images.'

View File

@ -8,7 +8,10 @@ class DataBoard
return unless sync
# Chrome also fires the onChanged callback on the current tab,
# so we only start syncing when we're ready.
$.on d, '4chanXInitFinished', => @sync = sync
init = =>
$.off d, '4chanXInitFinished', init
@sync = sync
$.on d, '4chanXInitFinished', init
delete: ({boardID, threadID, postID}) ->
if postID

View File

@ -163,7 +163,7 @@ Header =
unless Conf['Bottom header']
headRect = Header.bar.getBoundingClientRect()
top += - headRect.top - headRect.height
(if $.engine is 'webkit' then d.body else doc).scrollTop += top
<% if (type === 'crx') { %>d.body<% } else { %>doc<% } %>.scrollTop += top
toggleBarVisibility: (e) ->
return if e.type is 'mousedown' and e.button isnt 0 # not LMB
@ -322,6 +322,7 @@ Settings =
localStorage.setItem '4chan-settings', JSON.stringify settings
open: (openSection) ->
$.off d, '4chanXInitFinished', Settings.open
return if Settings.dialog
$.event 'CloseMenu'
@ -468,13 +469,14 @@ Settings =
download: "<%= meta.name %> v#{g.VERSION}-#{now}.json"
href: "data:application/json;base64,#{btoa unescape encodeURIComponent JSON.stringify data, null, 2}"
target: '_blank'
if $.engine isnt 'gecko'
a.click()
return
<% if (type === 'userscript') { %>
# XXX Firefox won't let us download automatically.
p = $ '.imp-exp-result', Settings.dialog
$.rmAll p
$.add p, a
<% } else { %>
a.click()
<% } %>
import: ->
@nextElementSibling.click()
onImport: ->
@ -810,6 +812,55 @@ Settings =
@value = key
$.cb.value.call @
PSAHiding =
init: ->
return if !Conf['Announcement Hiding']
$.addClass doc, 'hide-announcement'
$.on d, '4chanXInitFinished', @setup
setup: ->
$.off d, '4chanXInitFinished', PSAHiding.setup
unless psa = $.id 'globalMessage'
$.rmClass doc, 'hide-announcement'
return
PSAHiding.btn = btn = $.el 'a',
title: 'Toggle announcement.'
href: 'javascript:;'
$.on btn, 'click', PSAHiding.toggle
text = PSAHiding.trim psa
$.get 'hiddenPSAs', [], (item) ->
PSAHiding.sync item['hiddenPSAs']
$.before psa, btn
$.rmClass doc, 'hide-announcement'
$.sync 'hiddenPSAs', PSAHiding.sync
toggle: (e) ->
hide = $.hasClass @, 'hide-announcement'
text = PSAHiding.trim $.id 'globalMessage'
$.get 'hiddenPSAs', [], (item) ->
{hiddenPSAs} = item
if hide
hiddenPSAs.push text
else
i = hiddenPSAs.indexOf text
hiddenPSAs.splice i, 1
hiddenPSAs = hiddenPSAs[-5..]
PSAHiding.sync hiddenPSAs
$.set 'hiddenPSAs', hiddenPSAs
sync: (hiddenPSAs) ->
{btn} = PSAHiding
psa = $.id 'globalMessage'
[psa.hidden, btn.innerHTML, btn.className] = if PSAHiding.trim(psa) in hiddenPSAs
[true, '<span>[&nbsp;+&nbsp;]</span>', 'show-announcement']
else
[false, '<span>[&nbsp;-&nbsp;]</span>', 'hide-announcement']
trim: (psa) ->
psa.textContent.replace(/\W+/g, '').toLowerCase()
Fourchan =
init: ->
return if g.VIEW is 'catalog'
@ -1716,21 +1767,23 @@ DeleteLink =
return if DeleteLink.cooldown.counting isnt post
unless 0 <= seconds <= length
if DeleteLink.cooldown.counting is post
node.textContent = 'Delete'
delete DeleteLink.cooldown.counting
return
setTimeout DeleteLink.cooldown.count, 1000, post, seconds - 1, length, node
if seconds is 0
node.textContent = 'Delete'
return
node.textContent = "Delete (#{seconds})"
DownloadLink =
init: ->
<% if (type === 'userscript') { %>
# Firefox won't let us download cross-domain content.
return
<% } %>
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Download Link']
# Firefox won't let us download cross-domain content.
# Test for download feature support.
return if $.engine is 'gecko' or $.el('a').download is undefined
return unless 'download' of $.el 'a'
a = $.el 'a',
className: 'download-link'
textContent: 'Download file'
@ -1804,11 +1857,13 @@ Keybinds =
init: ->
return if g.VIEW is 'catalog' or !Conf['Keybinds']
$.on d, '4chanXInitFinished', ->
init = ->
$.off d, '4chanXInitFinished', init
$.on d, 'keydown', Keybinds.keydown
for node in $$ '[accesskey]'
node.removeAttribute 'accesskey'
return
$.on d, '4chanXInitFinished', init
keydown: (e) ->
return unless key = Keybinds.keyCode e
@ -2025,7 +2080,10 @@ Nav =
$.on next, 'click', @next
$.add span, [prev, $.tn(' '), next]
$.on d, '4chanXInitFinished', -> $.add d.body, span
append = ->
$.off d, '4chanXInitFinished', append
$.add d.body, span
$.on d, '4chanXInitFinished', append
prev: ->
if g.VIEW is 'thread'
@ -2640,67 +2698,71 @@ Quotify =
name: 'Resurrect Quotes'
cb: @node
node: ->
return if @isClone
for deadlink in $$ '.deadlink', @nodes.comment
if deadlink.parentNode.className is 'prettyprint'
# Don't quotify deadlinks inside code tags,
# un-`span` them.
$.replace deadlink, [deadlink.childNodes...]
continue
if @isClone
if $.hasClass deadlink, 'quotelink'
@nodes.quotelinks.push deadlink
else
Quotify.parseDeadlink.call @, deadlink
return
quote = deadlink.textContent
continue unless postID = quote.match(/\d+$/)?[0]
boardID =
if m = quote.match /^>>>\/([a-z\d]+)/
m[1]
else
@board.ID
quoteID = "#{boardID}.#{postID}"
parseDeadlink: (deadlink) ->
if deadlink.parentNode.className is 'prettyprint'
# Don't quotify deadlinks inside code tags,
# un-`span` them.
$.replace deadlink, [deadlink.childNodes...]
return
# \u00A0 is nbsp
if post = g.posts[quoteID]
unless post.isDead
# Don't (Dead) when quotifying in an archived post,
# and we know the post still exists.
a = $.el 'a',
href: "/#{boardID}/#{post.thread}/res/#p#{postID}"
className: 'quotelink'
textContent: quote
else
# Replace the .deadlink span if we can redirect.
a = $.el 'a',
href: "/#{boardID}/#{post.thread}/res/#p#{postID}"
className: 'quotelink deadlink'
target: '_blank'
textContent: "#{quote}\u00A0(Dead)"
a.setAttribute 'data-boardid', boardID
a.setAttribute 'data-threadid', post.thread.ID
a.setAttribute 'data-postid', postID
else if redirect = Redirect.to {boardID, threadID: 0, postID}
quote = deadlink.textContent
return unless postID = quote.match(/\d+$/)?[0]
boardID = if m = quote.match /^>>>\/([a-z\d]+)/
m[1]
else
@board.ID
quoteID = "#{boardID}.#{postID}"
if post = g.posts[quoteID]
unless post.isDead
# Don't (Dead) when quotifying in an archived post,
# and we know the post still exists.
a = $.el 'a',
href: "/#{boardID}/#{post.thread}/res/#p#{postID}"
className: 'quotelink'
textContent: quote
else
# Replace the .deadlink span if we can redirect.
a = $.el 'a',
href: redirect
className: 'deadlink'
href: "/#{boardID}/#{post.thread}/res/#p#{postID}"
className: 'quotelink deadlink'
target: '_blank'
textContent: "#{quote}\u00A0(Dead)"
if Redirect.post boardID, postID
# Make it function as a normal quote if we can fetch the post.
$.addClass a, 'quotelink'
a.setAttribute 'data-boardid', boardID
a.setAttribute 'data-postid', postID
unless @quotes.contains quoteID
@quotes.push quoteID
a.setAttribute 'data-boardid', boardID
a.setAttribute 'data-threadid', post.thread.ID
a.setAttribute 'data-postid', postID
else if redirect = Redirect.to {boardID, threadID: 0, postID}
# Replace the .deadlink span if we can redirect.
a = $.el 'a',
href: redirect
className: 'deadlink'
target: '_blank'
textContent: "#{quote}\u00A0(Dead)"
if Redirect.post boardID, postID
# Make it function as a normal quote if we can fetch the post.
$.addClass a, 'quotelink'
a.setAttribute 'data-boardid', boardID
a.setAttribute 'data-postid', postID
unless a
deadlink.textContent += "\u00A0(Dead)"
continue
unless quoteID in @quotes
@quotes.push quoteID
$.replace deadlink, a
if $.hasClass a, 'quotelink'
@nodes.quotelinks.push a
a = null
return
unless a
deadlink.textContent = "#{quote}\u00A0(Dead)"
return
$.replace deadlink, a
if $.hasClass a, 'quotelink'
@nodes.quotelinks.push a
QuoteInline =
init: ->
@ -3346,11 +3408,13 @@ ImageExpand =
ImageExpand.contract post
rect = post.nodes.root.getBoundingClientRect()
return unless rect.top <= 0 or rect.left <= 0
# Scroll back to the thumbnail when contracting the image
# to avoid being left miles away from the relevant post.
headRect = Header.bar.getBoundingClientRect()
top = rect.top - headRect.top - headRect.height
root = if $.engine is 'webkit' then d.body else doc
root = <% if (type === 'crx') { %>d.body<% } else { %>doc<% } %>
root.scrollTop += top if rect.top < 0
root.scrollLeft = 0 if rect.left < 0
@ -3392,7 +3456,7 @@ ImageExpand =
$.addClass post.nodes.root, 'expanded-image'
$.rmClass post.file.thumb, 'expanding'
return unless prev.top + prev.height <= 0
root = if $.engine is 'webkit' then d.body else doc
root = <% if (type === 'crx') { %>d.body<% } else { %>doc<% } %>
curr = post.nodes.root.getBoundingClientRect()
root.scrollTop += curr.height - prev.height + curr.top - prev.top
@ -4268,7 +4332,7 @@ ThreadUpdater =
$.add ThreadUpdater.root, nodes
if scroll
if Conf['Bottom Scroll']
(if $.engine is 'webkit' then d.body else doc).scrollTop = d.body.clientHeight
<% if (type === 'crx') { %>d.body<% } else { %>doc<% } %>.scrollTop = d.body.clientHeight
else
Header.scrollToPost nodes[0]
@ -4316,6 +4380,7 @@ ThreadWatcher =
$.delete 'AutoWatch'
ready: ->
$.off d, '4chanXInitFinished', ThreadWatcher.ready
return unless Main.isThisPageLegit()
ThreadWatcher.refresh()
$.add d.body, ThreadWatcher.dialog

View File

@ -325,6 +325,8 @@ Main =
g.THREADID = +pathname[3]
switch location.hostname
when 'api.4chan.org'
return
when 'sys.4chan.org'
Report.init()
return
@ -335,7 +337,7 @@ Main =
location.href = url if url
return
initFeatures = (features) ->
init = (features) ->
for name, module of features
# c.time "#{name} initialization"
try
@ -350,11 +352,12 @@ Main =
# c.time 'All initializations'
initFeatures
init
'Polyfill': Polyfill
'Header': Header
'Catalog Links': CatalogLinks
'Settings': Settings
'Announcement Hiding': PSAHiding
'Fourchan thingies': Fourchan
'Custom CSS': CustomCSS
'Linkify': Linkify
@ -410,7 +413,7 @@ Main =
return unless Main.isThisPageLegit()
# disable the mobile layout
$('link[href*=mobile]', d.head)?.disabled = true
$.addClass doc, $.engine
$.addClass doc, '<% if (type === 'crx') { %>webkit<% } else if (type === 'userjs') { %>presto<% } else { %>gecko<% } %>'
$.addClass doc, 'fourchan-x'
$.addStyle Main.css

View File

@ -11,6 +11,7 @@
"content_scripts": [{
"js": ["script.js"],
"matches": <%= JSON.stringify(meta.matches) %>,
"all_frames": true,
"run_at": "document_start"
}],
"homepage_url": "<%= meta.page %>",

View File

@ -30,11 +30,18 @@ QR =
cb: @node
initReady: ->
$.off d, '4chanXInitFinished', QR.initReady
QR.postingIsEnabled = !!$.id 'postForm'
return unless QR.postingIsEnabled
if $.engine is 'webkit'
$.on d, 'paste', QR.paste
$.on d, 'QRGetSelectedPost', ({detail: cb}) ->
cb QR.selected
$.on d, 'QRAddPreSubmitHook', ({detail: cb}) ->
QR.preSubmitHooks.push cb
<% if (type === 'crx') { %>
$.on d, 'paste', QR.paste
<% } %>
$.on d, 'dragover', QR.dragOver
$.on d, 'drop', QR.dropFile
$.on d, 'dragstart dragend', QR.drag
@ -269,6 +276,11 @@ QR =
text += ">#{s}\n"
QR.open()
if QR.selected.isLocked
index = QR.posts.indexOf QR.selected
(QR.posts[index+1] or new QR.post()).select()
$.addClass QR.nodes.el, 'dump'
QR.cooldown.auto = true
{com, thread} = QR.nodes
thread.value = OP.ID unless com.value
@ -448,6 +460,7 @@ QR =
rectList = @nodes.el.parentNode.getBoundingClientRect()
@nodes.el.parentNode.scrollLeft += rectEl.left + rectEl.width/2 - rectList.left - rectList.width/2
@load()
$.event 'QRPostSelection', @
load: ->
# Load this post's values.
for name in ['thread', 'name', 'email', 'sub', 'com']
@ -789,7 +802,10 @@ QR =
# Add empty mimeType to avoid errors with URLs selected in Window's file dialog.
QR.mimeTypes.push ''
nodes.fileInput.max = $('input[name=MAX_FILE_SIZE]').value
nodes.fileInput.accept = "text/*, #{mimeTypes}" if $.engine isnt 'presto' # Opera's accept attribute is fucked up
<% if (type !== 'userjs') { %>
# Opera's accept attribute is fucked up
nodes.fileInput.accept = "text/*, #{mimeTypes}"
<% } %>
QR.spoiler = !!$ 'input[name=spoiler]'
nodes.spoiler.parentElement.hidden = !QR.spoiler
@ -857,6 +873,7 @@ QR =
# Use it to extend the QR's functionalities, or for XTRM RICE.
$.event 'QRDialogCreation', null, dialog
preSubmitHooks: []
submit: (e) ->
e?.preventDefault()
@ -887,8 +904,11 @@ QR =
err = 'You can\'t reply to this thread anymore.'
else unless post.com or post.file
err = 'No file selected.'
else if post.file and thread.fileLimit and !thread.isSticky
else if post.file and thread.fileLimit
err = 'Max limit of image replies has been reached.'
else for hook in QR.preSubmitHooks
if err = hook post, thread
break
if QR.captcha.isEnabled and !err
{challenge, response} = QR.captcha.getOne()
@ -1049,7 +1069,7 @@ QR =
board: g.BOARD
threadID
postID
}, QR.nodes.el
}
# Enable auto-posting if we have stuff to post, disable it otherwise.
QR.cooldown.auto = QR.posts.length > 1 and isReply