Merge branch 'mayhem' into v3

Conflicts:
	CHANGELOG.md
	CONTRIBUTING.md
	css/style.css
	html/General/Settings-section-Main.html
	json/archives.json
	package.json
	src/Archive/Redirect.coffee
	src/General/Header.coffee
	src/General/Main.coffee
	src/General/Settings.coffee
	src/General/lib/$.coffee
	src/General/lib/thread.class
	src/Linkification/Linkify.coffee
	src/Miscellaneous/AnnouncementHiding.coffee
	src/Monitoring/ThreadStats.coffee
This commit is contained in:
Zixaphir 2014-01-22 12:08:37 -07:00
commit b6749b91a5
28 changed files with 761 additions and 489 deletions

View File

@ -1,5 +1,7 @@
### v1.3.2 **MayhemYDG**:
*2014-01-12* - Added a `Reset Settings` button in the settings.
- More stability update.
- Stability update.
**seaweedchan**: **seaweedchan**:
- Fix Menu errors on older Firefox versions, such as the ESR - Fix Menu errors on older Firefox versions, such as the ESR

48
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,48 @@
## Reporting bugs and suggestions
Reporting bugs:
1. Make sure both your **browser** and **4chan X** are up to date.<br>
Only **Chrome**, **Firefox** and **Opera** are supported.<br>
**SRWare Iron**, **Firefox ESR**, **Pale Moon**, **Waterfox**, and other derivatives are not supported, use them at your own risk.
2. Look at the list of [known problems and solutions](https://github.com/MayhemYDG/4chan-x/wiki/FAQ#known-problems).
3. Disable your other extensions & scripts to identify conflicts.
4. If your issue persists, open a [new issue](https://github.com/MayhemYDG/4chan-x/issues) with the following information:
1. Precise steps to reproduce the problem, with the expected and actual results.
2. [Console errors](https://github.com/MayhemYDG/4chan-x/wiki/FAQ#console-errors), if any.
3. 4chan X version, browser variant, browser version, and Greasemonkey version if you are using it.
4. Your exported settings. If your settings contains sensible information (e.g. personas), edit the text file manually.
Respect these guidelines:
- Describe the issue clearly, put some effort into it. A one-liner isn't a good enough description.
- If you want to get your suggestion implemented sooner, make it convincing.
- If you want to criticize, make it convincing and constructive.
- Be mature. Act like an idiot and you will be blocked without warning.
## Development & Contribution
### Get started
- Install [node.js](http://nodejs.org/).
- Install [Grunt's CLI](http://gruntjs.com/) with `npm install -g grunt-cli`.
- Clone 4chan X.
- `cd` into it.
- Install/Update 4chan X dependencies with `npm install`.
### Build
- Build with `grunt`.
- Continuously build with `grunt watch`.
### Release
- Update the version with `grunt patch`, `grunt minor` or `grunt major`.
- Release with `grunt release`.
Note: this is only used to release new 4chan X versions, and is **not** needed or wanted in pull requests.
### Contribute
- Edit the sources.
- If the edits affect regular users, edit the changelog.
- Open a pull request.

View File

@ -1,5 +1,5 @@
/* /*
* 4chan X - Version 1.3.2 - 2014-01-21 * 4chan X - Version 1.3.2 - 2014-01-22
* *
* Licensed under the MIT license. * Licensed under the MIT license.
* https://github.com/seaweedchan/4chan-x/blob/master/LICENSE * https://github.com/seaweedchan/4chan-x/blob/master/LICENSE

View File

@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name 4chan X // @name 4chan X
// @version 1.3.2 // @version 1.3.2
// @minGMVer 1.13 // @minGMVer 1.14
// @minFFVer 26 // @minFFVer 26
// @namespace 4chan-X // @namespace 4chan-X
// @description Cross-browser userscript for maximum lurking on 4chan. // @description Cross-browser userscript for maximum lurking on 4chan.
@ -13,6 +13,7 @@
// @grant GM_getValue // @grant GM_getValue
// @grant GM_setValue // @grant GM_setValue
// @grant GM_deleteValue // @grant GM_deleteValue
// @grant GM_listValues
// @grant GM_openInTab // @grant GM_openInTab
// @run-at document-start // @run-at document-start
// @updateURL https://github.com/seaweedchan/4chan-x/raw/stable/builds/4chan-X.meta.js // @updateURL https://github.com/seaweedchan/4chan-x/raw/stable/builds/4chan-X.meta.js

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -21,22 +21,22 @@
"min": { "min": {
"chrome": "31", "chrome": "31",
"firefox": "26", "firefox": "26",
"greasemonkey": "1.13" "greasemonkey": "1.14"
} }
}, },
"devDependencies": { "devDependencies": {
"font-awesome": "https://github.com/FortAwesome/Font-Awesome/archive/v4.0.3.tar.gz", "font-awesome": "~4.0.3",
"grunt": "~0.4.1", "grunt": "~0.4.2",
"grunt-bump": "~0.0.11", "grunt-bump": "~0.0.13",
"grunt-concurrent": "~0.4.0", "grunt-concurrent": "~0.4.3",
"grunt-contrib-clean": "~0.5.0", "grunt-contrib-clean": "~0.5.0",
"grunt-contrib-coffee": "~0.8.0", "grunt-contrib-coffee": "~0.8.2",
"grunt-contrib-compress": "~0.5.2", "grunt-contrib-compress": "~0.6.0",
"grunt-contrib-concat": "~0.3.0", "grunt-contrib-concat": "~0.3.0",
"grunt-contrib-copy": "~0.5.0", "grunt-contrib-copy": "~0.5.0",
"grunt-contrib-watch": "~0.5.3", "grunt-contrib-watch": "~0.5.3",
"grunt-shell": "~0.6.0", "grunt-shell": "~0.6.3",
"load-grunt-tasks": "~0.2.0" "load-grunt-tasks": "~0.2.1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -1,5 +1,4 @@
Redirect = Redirect =
init: -> init: ->
o = o =
thread: {} thread: {}
@ -52,8 +51,8 @@ Redirect =
software: "foolfuuka" software: "foolfuuka"
, ,
name: "4plebs" name: "4plebs"
boards: ["hr", "o", "pol", "s4s", "tg", "tv", "x"] boards: ["adv", "hr", "o", "pol", "s4s", "tg", "tv", "x"]
files: ["hr", "o", "pol", "s4s", "tg", "tv", "x"] files: ["adv", "hr", "o", "pol", "s4s", "tg", "tv", "x"]
data: data:
domain: "archive.4plebs.org" domain: "archive.4plebs.org"
http: true http: true
@ -68,6 +67,15 @@ Redirect =
http: true http: true
https: true https: true
software: "foolfuuka" software: "foolfuuka"
,
name: "Love is Over"
boards: ["d", "i"],
files: ["d", "i"]
data:
domain: "loveisover.me"
http: true
https: true
software: "foolfuuka"
, ,
name: "Install Gentoo" name: "Install Gentoo"
boards: ["diy", "g", "sci"] boards: ["diy", "g", "sci"]
@ -82,7 +90,7 @@ Redirect =
boards: ["cgl", "g", "mu", "w"] boards: ["cgl", "g", "mu", "w"]
files: ["cgl", "g", "mu", "w"] files: ["cgl", "g", "mu", "w"]
data: data:
domain: "rbt.asia" domain: "archive.rebeccablacktech.com"
http: true http: true
https: true https: true
software: "fuuka" software: "fuuka"
@ -100,9 +108,33 @@ Redirect =
files: ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "tg", "vr"] files: ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "tg", "vr"]
data: data:
domain: "fuuka.warosu.org" domain: "fuuka.warosu.org"
http: true
https: true https: true
software: "fuuka" software: "fuuka"
,
name: "fgts"
boards: ["soc"]
files: ["soc"]
data:
domain: "fgts.eu"
http: true
https: true
software: "foolfuuka"
,
name: "maware"
boards: ["t"]
files: ["t"]
data:
domain: "archive.mawa.re"
http: true
software: "foolfuuka"
,
name: "installgentoo.com"
boards: ["g", "t"]
files: ["g", "t"]
data:
domain: "chan.installgentoo.com"
http: true
software: "foolfuuka"
, ,
name: "Foolz Beta" name: "Foolz Beta"
boards: ["a", "co", "d", "gd", "h", "jp", "m", "mlp", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"], boards: ["a", "co", "d", "gd", "h", "jp", "m", "mlp", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"],
@ -113,15 +145,6 @@ Redirect =
https: true https: true
withCredentials: true withCredentials: true
software: "foolfuuka" software: "foolfuuka"
,
name: "Love is Over"
boards: ["d", "i"],
files: ["d", "i"]
data:
domain: "loveisover.me"
http: true
https: true
software: "foolfuuka"
] ]
to: (dest, data) -> to: (dest, data) ->

View File

@ -58,7 +58,7 @@ ThreadHiding =
$.cache "//a.4cdn.org/#{g.BOARD}/threads.json", -> $.cache "//a.4cdn.org/#{g.BOARD}/threads.json", ->
return unless @status is 200 return unless @status is 200
threads = {} threads = {}
for page in JSON.parse @response for page in @response
for thread in page.threads for thread in page.threads
if thread.no of hiddenThreadsOnCatalog if thread.no of hiddenThreadsOnCatalog
threads[thread.no] = hiddenThreadsOnCatalog[thread.no] threads[thread.no] = hiddenThreadsOnCatalog[thread.no]

View File

@ -80,6 +80,7 @@ Get =
$.cache url, $.cache url,
-> Get.archivedPost @, boardID, postID, root, context -> Get.archivedPost @, boardID, postID, root, context
, ,
responseType: 'json'
withCredentials: url.archive.withCredentials withCredentials: url.archive.withCredentials
insert: (post, root, context) -> insert: (post, root, context) ->
# Stop here if the container has been removed while loading. # Stop here if the container has been removed while loading.
@ -118,7 +119,7 @@ Get =
"Error #{req.statusText} (#{req.status})." "Error #{req.statusText} (#{req.status})."
return return
posts = JSON.parse(req.response).posts {posts} = req.response
Build.spoilerRange[boardID] = posts[0].custom_spoiler Build.spoilerRange[boardID] = posts[0].custom_spoiler
for post in posts for post in posts
break if post.no is postID # we found it! break if post.no is postID # we found it!
@ -149,7 +150,7 @@ Get =
Get.insert post, root, context Get.insert post, root, context
return return
data = JSON.parse req.response data = req.response
if data.error if data.error
$.addClass root, 'warning' $.addClass root, 'warning'
root.textContent = data.error root.textContent = data.error
@ -227,4 +228,4 @@ Get =
'[/moot]': '</div>' '[/moot]': '</div>'
'[banned]': '<strong style="color: red;">' '[banned]': '<strong style="color: red;">'
'[/banned]': '</strong>' '[/banned]': '</strong>'
}[text] or text.replace ':lit', '' }[text] or text.replace ':lit', ''

View File

@ -305,17 +305,15 @@ Header =
toggleHideBarOnScroll: (e) -> toggleHideBarOnScroll: (e) ->
hide = @checked hide = @checked
$.set 'Header auto-hide on scroll', hide $.cb.checked.call @
Header.setHideBarOnScroll hide Header.setHideBarOnScroll hide
hideBarOnScroll: -> hideBarOnScroll: ->
offsetY = window.pageYOffset offsetY = window.pageYOffset
if offsetY > (Header.previousOffset or 0) if offsetY > (Header.previousOffset or 0)
$.addClass Header.bar, 'autohide' $.addClass Header.bar, 'autohide', 'scroll'
$.addClass Header.bar, 'scroll'
else else
$.rmClass Header.bar, 'autohide' $.rmClass Header.bar, 'autohide', 'scroll'
$.rmClass Header.bar, 'scroll'
Header.previousOffset = offsetY Header.previousOffset = offsetY
setBarPosition: (bottom) -> setBarPosition: (bottom) ->
@ -389,9 +387,21 @@ Header =
scrollTo: (root, down, needed) -> scrollTo: (root, down, needed) ->
if down if down
x = Header.getBottomOf root x = Header.getBottomOf root
if Conf['Header auto-hide on scroll'] and Conf['Bottom header']
{height} = Header.bar.getBoundingClientRect()
if x <= 0
x += height if !Header.isHidden()
else
x -= height if Header.isHidden()
window.scrollBy 0, -x unless needed and x >= 0 window.scrollBy 0, -x unless needed and x >= 0
else else
x = Header.getTopOf root x = Header.getTopOf root
if Conf['Header auto-hide on scroll'] and !Conf['Bottom header']
{height} = Header.bar.getBoundingClientRect()
if x >= 0
x += height if !Header.isHidden()
else
x -= height if Header.isHidden()
window.scrollBy 0, x unless needed and x >= 0 window.scrollBy 0, x unless needed and x >= 0
scrollToIfNeeded: (root, down) -> scrollToIfNeeded: (root, down) ->
@ -411,6 +421,12 @@ Header =
headRect = Header.toggle.getBoundingClientRect() headRect = Header.toggle.getBoundingClientRect()
bottom -= clientHeight - headRect.bottom + headRect.height bottom -= clientHeight - headRect.bottom + headRect.height
bottom bottom
isHidden: ->
{top} = Header.bar.getBoundingClientRect()
if Conf['Bottom header']
top is doc.clientHeight
else
top < 0
addShortcut: (el) -> addShortcut: (el) ->
shortcut = $.el 'span', shortcut = $.el 'span',

View File

@ -279,7 +279,7 @@ Index =
try try
if req.status is 200 if req.status is 200
Index.parse JSON.parse(req.response), pageNum Index.parse req.response, pageNum
else if req.status is 304 and pageNum? else if req.status is 304 and pageNum?
Index.pageNav pageNum Index.pageNav pageNum
catch err catch err
@ -475,6 +475,7 @@ Index =
else else
pageNum = Index.getCurrentPage() pageNum = Index.getCurrentPage()
else else
return unless Index.searchInput.dataset.searching
pageNum = Index.pageBeforeSearch pageNum = Index.pageBeforeSearch
delete Index.pageBeforeSearch delete Index.pageBeforeSearch
<% if (type === 'userscript') { %> <% if (type === 'userscript') { %>

View File

@ -78,16 +78,9 @@ Main =
return if !Main.isThisPageLegit() or $.hasClass doc, 'fourchan-x' return if !Main.isThisPageLegit() or $.hasClass doc, 'fourchan-x'
# disable the mobile layout # disable the mobile layout
$('link[href*=mobile]', d.head)?.disabled = true $('link[href*=mobile]', d.head)?.disabled = true
<% if (type === 'crx') { %> $.addClass doc, 'fourchan-x', 'seaweedchan', g.VIEW, '<% if (type === 'crx') { %>blink<% } else { %>gecko<% } %>'
$.addClass doc, 'blink'
<% } else { %>
$.addClass doc, 'gecko'
<% } %>
$.addClass doc, 'fourchan-x'
$.addClass doc, 'seaweedchan'
$.addClass doc, g.VIEW
$.addStyle Main.css $.addStyle Main.css
Main.setClass() Main.setClass()
setClass: -> setClass: ->
@ -124,6 +117,7 @@ Main =
# Something might have gone wrong! # Something might have gone wrong!
Main.initStyle() Main.initStyle()
# 4chan Pass Link
if styleSelector = $.id 'styleSelector' if styleSelector = $.id 'styleSelector'
passLink = $.el 'a', passLink = $.el 'a',
textContent: '4chan Pass' textContent: '4chan Pass'
@ -134,12 +128,18 @@ Main =
'left=0,top=0,width=500,height=255,toolbar=0,resizable=0' 'left=0,top=0,width=500,height=255,toolbar=0,resizable=0'
$.before styleSelector.previousSibling, [$.tn '['; passLink, $.tn ']\u00A0\u00A0'] $.before styleSelector.previousSibling, [$.tn '['; passLink, $.tn ']\u00A0\u00A0']
# Parse HTML or skip it and start building from JSON.
unless Conf['JSON Navigation'] and g.VIEW is 'index' unless Conf['JSON Navigation'] and g.VIEW is 'index'
Main.initThread() Main.initThread()
else else
$.event '4chanXInitFinished' $.event '4chanXInitFinished'
<% if (type === 'userscript') { %> <% if (type === 'userscript') { %>
test = $.el 'span'
test.classList.add 'a', 'b'
if test.className isnt 'a b'
new Notice 'warning', "Your version of Firefox is outdated (v<%= meta.min.firefox %> minimum) and <%= meta.name %> may not operate correctly.", 30
GMver = GM_info.version.split '.' GMver = GM_info.version.split '.'
for v, i in "<%= meta.min.greasemonkey %>".split '.' for v, i in "<%= meta.min.greasemonkey %>".split '.'
continue if v is GMver[i] continue if v is GMver[i]
@ -176,6 +176,17 @@ Main =
Main.callbackNodesDB Post, posts, -> Main.callbackNodesDB Post, posts, ->
$.event '4chanXInitFinished' $.event '4chanXInitFinished'
$.get 'previousversion', null, ({previousversion}) ->
return if previousversion is g.VERSION
if previousversion
changelog = '<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md'
el = $.el 'span',
innerHTML: "<%= meta.name %> has been updated to <a href='#{changelog}' target=_blank>version #{g.VERSION}</a>."
new Notice 'info', el, 15
else
Settings.open()
$.set 'previousversion', g.VERSION
callbackNodes: (klass, nodes) -> callbackNodes: (klass, nodes) ->
i = 0 i = 0
cb = klass.callbacks cb = klass.callbacks

View File

@ -249,7 +249,7 @@ Navigate =
Navigate.title() Navigate.title()
try try
Navigate.parse JSON.parse(req.response).posts Navigate.parse req.response.posts
catch err catch err
console.error 'Navigate failure:' console.error 'Navigate failure:'
console.log err console.log err

View File

@ -9,19 +9,6 @@ Settings =
Header.addShortcut link Header.addShortcut link
$.get 'previousversion', null, (item) ->
if previous = item['previousversion']
return if previous is g.VERSION
changelog = '<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md'
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 Notice 'info', el, 30
else
$.on d, '4chanXInitFinished', Settings.open
$.set 'previousversion', g.VERSION
Settings.addSection 'Main', Settings.main Settings.addSection 'Main', Settings.main
Settings.addSection 'Filter', Settings.filter Settings.addSection 'Filter', Settings.filter
Settings.addSection 'Sauce', Settings.sauce Settings.addSection 'Sauce', Settings.sauce
@ -37,7 +24,6 @@ Settings =
localStorage.setItem '4chan-settings', JSON.stringify settings localStorage.setItem '4chan-settings', JSON.stringify settings
open: (openSection) -> open: (openSection) ->
$.off d, '4chanXInitFinished', Settings.open
return if Settings.dialog return if Settings.dialog
$.event 'CloseMenu' $.event 'CloseMenu'
@ -53,6 +39,7 @@ Settings =
$.on $('.export', Settings.dialog), 'click', Settings.export $.on $('.export', Settings.dialog), 'click', Settings.export
$.on $('.import', Settings.dialog), 'click', Settings.import $.on $('.import', Settings.dialog), 'click', Settings.import
$.on $('.reset', Settings.dialog), 'click', Settings.reset
$.on $('input', Settings.dialog), 'change', Settings.onImport $.on $('input', Settings.dialog), 'change', Settings.onImport
links = [] links = []
@ -124,56 +111,39 @@ Settings =
div = $.el 'div', div = $.el 'div',
innerHTML: "<button></button><span class=description>: Clear manually-hidden threads and posts on all boards. Reload the page to apply." innerHTML: "<button></button><span class=description>: Clear manually-hidden threads and posts on all boards. Reload the page to apply."
button = $ 'button', div button = $ 'button', div
hiddenNum = 0 $.get {hiddenThreads: {}, hiddenPosts: {}}, ({hiddenThreads, hiddenPosts}) ->
$.get 'hiddenThreads', boards: {}, (item) -> hiddenNum = 0
for ID, board of item.hiddenThreads.boards for ID, board of hiddenThreads.boards
hiddenNum += Object.keys(board).length
for ID, board of hiddenPosts.boards
for ID, thread of board for ID, thread of board
hiddenNum++ hiddenNum += Object.keys(thread).length
button.textContent = "Hidden: #{hiddenNum}"
$.get 'hiddenPosts', boards: {}, (item) ->
for ID, board of item.hiddenPosts.boards
for ID, thread of board
for ID, post of thread
hiddenNum++
button.textContent = "Hidden: #{hiddenNum}" button.textContent = "Hidden: #{hiddenNum}"
$.on button, 'click', -> $.on button, 'click', ->
@textContent = 'Hidden: 0' @textContent = 'Hidden: 0'
$.get 'hiddenThreads', boards: {}, (item) -> $.get 'hiddenThreads', {}, ({hiddenThreads}) ->
for boardID of item.hiddenThreads.boards for boardID of hiddenThreads.boards
localStorage.removeItem "4chan-hide-t-#{boardID}" localStorage.removeItem "4chan-hide-t-#{boardID}"
$.delete ['hiddenThreads', 'hiddenPosts'] $.delete ['hiddenThreads', 'hiddenPosts']
$.after $('input[name="Stubs"]', section).parentNode.parentNode, div $.after $('input[name="Stubs"]', section).parentNode.parentNode, div
export: (now, data) -> export: ->
unless typeof now is 'number' # Make sure to export the most recent data.
now = Date.now() $.get Conf, (Conf) ->
data = # XXX don't export archives.
version: g.VERSION delete Conf['archives']
date: now Settings.downloadExport {version: g.VERSION, date: Date.now(), Conf}
for db in DataBoard.keys downloadExport: (data) ->
Conf[db] = boards: {}
# Make sure to export the most recent data.
$.get Conf, (Conf) ->
# XXX don't export archives.
delete Conf['archives']
data.Conf = Conf
Settings.export now, data
return
a = $.el 'a', a = $.el 'a',
className: 'warning' download: "<%= meta.name %> v#{g.VERSION}-#{data.date}.json"
textContent: 'Save me!'
download: "<%= meta.name %> v#{g.VERSION}-#{now}.json"
href: "data:application/json;base64,#{btoa unescape encodeURIComponent JSON.stringify data, null, 2}" href: "data:application/json;base64,#{btoa unescape encodeURIComponent JSON.stringify data, null, 2}"
target: '_blank'
<% if (type === 'userscript') { %> <% if (type === 'userscript') { %>
# XXX Firefox won't let us download automatically.
p = $ '.imp-exp-result', Settings.dialog p = $ '.imp-exp-result', Settings.dialog
$.rmAll p $.rmAll p
$.add p, a $.add p, a
<% } else { %>
a.click()
<% } %> <% } %>
a.click()
import: -> import: ->
@nextElementSibling.click() $('input', @parentNode).click()
onImport: -> onImport: ->
return unless file = @files[0] return unless file = @files[0]
output = $('.imp-exp-result') output = $('.imp-exp-result')
@ -183,8 +153,7 @@ Settings =
reader = new FileReader() reader = new FileReader()
reader.onload = (e) -> reader.onload = (e) ->
try try
data = JSON.parse e.target.result Settings.loadSettings JSON.parse e.target.result
Settings.loadSettings data
if confirm 'Import successful. Reload now?' if confirm 'Import successful. Reload now?'
window.location.reload() window.location.reload()
catch err catch err
@ -194,6 +163,11 @@ Settings =
loadSettings: (data) -> loadSettings: (data) ->
version = data.version.split '.' version = data.version.split '.'
if version[0] is '2' if version[0] is '2'
convertSettings = (data, map) ->
for prevKey, newKey of map
data.Conf[newKey] = data.Conf[prevKey] if newKey
delete data.Conf[prevKey]
data
data = Settings.convertSettings data, data = Settings.convertSettings data,
# General confs # General confs
'Disable 4chan\'s extension': '' 'Disable 4chan\'s extension': ''
@ -265,11 +239,9 @@ Settings =
data.Conf['watchedThreads'] = boards: ThreadWatcher.convert data.Conf['WatchedThreads'] data.Conf['watchedThreads'] = boards: ThreadWatcher.convert data.Conf['WatchedThreads']
delete data.Conf['WatchedThreads'] delete data.Conf['WatchedThreads']
$.set data.Conf $.set data.Conf
convertSettings: (data, map) -> reset: ->
for prevKey, newKey of map if confirm 'Your current settings will be entirely wiped, are you sure?'
data.Conf[newKey] = data.Conf[prevKey] if newKey $.clear -> window.location.reload() if confirm 'Reset successful. Reload now?'
delete data.Conf[prevKey]
data
filter: (section) -> filter: (section) ->
section.innerHTML = <%= importHTML('Settings/Filter-select') %> section.innerHTML = <%= importHTML('Settings/Filter-select') %>

View File

@ -157,6 +157,9 @@ UI = do ->
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
onFocus: (e) =>
e.stopPropagation()
@focus e.target
focus: (entry) -> focus: (entry) ->
while focused = $.x 'parent::*/child::*[contains(@class,"focused")]', entry while focused = $.x 'parent::*/child::*[contains(@class,"focused")]', entry
$.rmClass focused, 'focused' $.rmClass focused, 'focused'
@ -199,10 +202,7 @@ UI = do ->
parseEntry: (entry) -> parseEntry: (entry) ->
{el, subEntries} = entry {el, subEntries} = entry
$.addClass el, 'entry' $.addClass el, 'entry'
$.on el, 'focus mouseover', ((e) -> $.on el, 'focus mouseover', @onFocus
e.stopPropagation()
@focus el
).bind @
el.style.order = entry.order or 100 el.style.order = entry.order or 100
return unless subEntries return unless subEntries
$.addClass el, 'has-submenu' $.addClass el, 'has-submenu'

View File

@ -653,6 +653,15 @@ span.hide-announcement {
text-decoration: none; text-decoration: none;
border-bottom: 1px dashed; border-bottom: 1px dashed;
} }
@supports (text-decoration-style: dashed) or (-moz-text-decoration-style: dashed) {
.quotelink.forwardlink,
.backlink.forwardlink {
text-decoration: underline;
-moz-text-decoration-style: dashed;
text-decoration-style: dashed;
border-bottom: none;
}
}
.filtered { .filtered {
text-decoration: underline line-through; text-decoration: underline line-through;
} }

View File

@ -4,11 +4,12 @@
<div class=credits> <div class=credits>
<a class=export>Export</a> | <a class=export>Export</a> |
<a class=import>Import</a> | <a class=import>Import</a> |
<input type=file style='display: none;'> <button class="reset">Reset Settings</button> |
<input type=file hidden>
<a href='<%= meta.page %>' target=_blank><%= meta.name %></a> | <a href='<%= meta.page %>' target=_blank><%= meta.name %></a> |
<a href='<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md' target=_blank>#{g.VERSION}</a> | <a href='<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md' target=_blank>#{g.VERSION}</a> |
<a href='<%= meta.repo %>blob/<%= meta.mainBranch %>/README.md#reporting-bugs-and-suggestions' target=_blank>Issues</a> | <a href='<%= meta.repo %>blob/<%= meta.mainBranch %>/README.md#reporting-bugs-and-suggestions' target=_blank>Issues</a> |
<a href=javascript:; class=close title=Close>×</a> <a href=javascript:; class='fa fa-times' title=Close></a>
</div> </div>
</nav> </nav>
<div class=section-container><section></section></div> <div class=section-container><section></section></div>

View File

@ -54,6 +54,8 @@ $.ajax = do ->
if whenModified if whenModified
r.setRequestHeader 'If-Modified-Since', lastModified[url] if url of lastModified r.setRequestHeader 'If-Modified-Since', lastModified[url] if url of lastModified
$.on r, 'load', -> lastModified[url] = r.getResponseHeader 'Last-Modified' $.on r, 'load', -> lastModified[url] = r.getResponseHeader 'Last-Modified'
if /\.json$/.test url
r.responseType = 'json'
$.extend r, options $.extend r, options
$.extend r.upload, upCallbacks $.extend r.upload, upCallbacks
r.send form r.send form
@ -113,11 +115,11 @@ $.X = (path, root) ->
# XPathResult.ORDERED_NODE_SNAPSHOT_TYPE === 7 # XPathResult.ORDERED_NODE_SNAPSHOT_TYPE === 7
d.evaluate path, root, null, 7, null d.evaluate path, root, null, 7, null
$.addClass = (el, className) -> $.addClass = (el, className...) ->
el.classList.add className el.classList.add className...
$.rmClass = (el, className) -> $.rmClass = (el, className...) ->
el.classList.remove className el.classList.remove className...
$.toggleClass = (el, className) -> $.toggleClass = (el, className) ->
el.classList.toggle className el.classList.toggle className
@ -332,29 +334,50 @@ $.get = (key, val, cb) ->
chrome.storage.sync.get syncItems, done chrome.storage.sync.get syncItems, done
$.set = do -> $.set = do ->
items = {} items =
localItems = {} sync: {}
local: {}
timeout = {}
set = $.debounce $.SECOND, -> setArea = (area) ->
data = items[area]
return if !Object.keys(data).length or timeout[area]
items[area] = {}
chrome.storage[area].set data, ->
if chrome.runtime.lastError
c.error chrome.runtime.lastError.message
for key, val of data when key not of items[area]
items[area][key] = val
timeout[area] = setTimeout setArea, $.MINUTE, area
return
delete timeout[area]
setAll = $.debounce $.SECOND, ->
for key in $.localKeys for key in $.localKeys
if key of items if key of items.sync
(localItems or= {})[key] = items[key] items.local[key] = items.sync[key]
delete items[key] delete items.sync[key]
try try
chrome.storage.local.set localItems setArea 'local'
chrome.storage.sync.set items setArea 'sync'
items = {}
localItems = {}
catch err catch err
c.error err.stack c.error err.stack
(key, val) -> (key, val) ->
if typeof key is 'string' if typeof key is 'string'
items[key] = val items.sync[key] = val
else else
$.extend items, key $.extend items.sync, key
set() setAll()
$.clear = (cb) ->
count = 2
done = ->
if chrome.runtime.lastError
c.error chrome.runtime.lastError.message
return
cb?() unless --count
chrome.storage.local.clear done
chrome.storage.sync.clear done
<% } else { %> <% } else { %>
# http://wiki.greasespot.net/Main_Page # http://wiki.greasespot.net/Main_Page
@ -402,6 +425,9 @@ $.set = do ->
for key, val of keys for key, val of keys
set key, val set key, val
return return
$.clear = (cb) ->
$.delete GM_listValues().map (key) -> key.replace g.NAMESPACE, ''
cb?()
<% } %> <% } %>
$$ = (selector, root=d.body) -> $$ = (selector, root=d.body) ->

View File

@ -78,7 +78,7 @@ class DataBoard
return return
board = @data.boards[boardID] board = @data.boards[boardID]
threads = {} threads = {}
for page in JSON.parse e.target.response for page in e.target.response
for thread in page.threads for thread in page.threads
if thread.no of board if thread.no of board
threads[thread.no] = board[thread.no] threads[thread.no] = board[thread.no]
@ -93,4 +93,4 @@ class DataBoard
disconnect: -> disconnect: ->
$.desync @key $.desync @key
delete @sync delete @sync
delete @data delete @data

View File

@ -14,6 +14,7 @@
// @grant GM_getValue // @grant GM_getValue
// @grant GM_setValue // @grant GM_setValue
// @grant GM_deleteValue // @grant GM_deleteValue
// @grant GM_listValues
// @grant GM_openInTab // @grant GM_openInTab
// @run-at document-start // @run-at document-start
// @updateURL <%= meta.repo %>raw/stable/builds/<%= meta.files.metajs %> // @updateURL <%= meta.repo %>raw/stable/builds/<%= meta.files.metajs %>

View File

@ -153,10 +153,19 @@ ImageExpand =
return return
timeoutID = setTimeout ImageExpand.expand, 10000, post timeoutID = setTimeout ImageExpand.expand, 10000, post
<% if (type === 'crx') { %>
$.ajax @src,
onloadend: ->
return if @status isnt 404
clearTimeout timeoutID
post.kill true
,
type: 'head'
<% } else { %>
# XXX CORS for i.4cdn.org WHEN? # XXX CORS for i.4cdn.org WHEN?
$.ajax "//a.4cdn.org/#{post.board}/res/#{post.thread}.json", onload: -> $.ajax "//a.4cdn.org/#{post.board}/res/#{post.thread}.json", onload: ->
return if @status isnt 200 return if @status isnt 200
for postObj in JSON.parse(@response).posts for postObj in @response.posts
break if postObj.no is post.ID break if postObj.no is post.ID
if postObj.no isnt post.ID if postObj.no isnt post.ID
clearTimeout timeoutID clearTimeout timeoutID
@ -164,6 +173,7 @@ ImageExpand =
else if postObj.filedeleted else if postObj.filedeleted
clearTimeout timeoutID clearTimeout timeoutID
post.kill true post.kill true
<% } %>
menu: menu:
init: -> init: ->

View File

@ -38,10 +38,19 @@ ImageHover =
return return
timeoutID = setTimeout (=> @src = post.file.URL + '?' + Date.now()), 3000 timeoutID = setTimeout (=> @src = post.file.URL + '?' + Date.now()), 3000
<% if (type === 'crx') { %>
$.ajax @src,
onloadend: ->
return if @status isnt 404
clearTimeout timeoutID
post.kill true
,
type: 'head'
<% } else { %>
# XXX CORS for i.4cdn.org WHEN? # XXX CORS for i.4cdn.org WHEN?
$.ajax "//a.4cdn.org/#{post.board}/res/#{post.thread}.json", onload: -> $.ajax "//a.4cdn.org/#{post.board}/res/#{post.thread}.json", onload: ->
return if @status isnt 200 return if @status isnt 200
for postObj in JSON.parse(@response).posts for postObj in @response.posts
break if postObj.no is post.ID break if postObj.no is post.ID
if postObj.no isnt post.ID if postObj.no isnt post.ID
clearTimeout timeoutID clearTimeout timeoutID
@ -49,3 +58,4 @@ ImageHover =
else if postObj.filedeleted else if postObj.filedeleted
clearTimeout timeoutID clearTimeout timeoutID
post.kill true post.kill true
<% } %>

View File

@ -1,10 +1,9 @@
PSAHiding = PSAHiding =
init: -> init: ->
return if !Conf['Announcement Hiding'] return if !Conf['Announcement Hiding']
$.addClass doc, 'hide-announcement' $.addClass doc, 'hide-announcement'
$.on d, '4chanXInitFinished', @setup $.on d, '4chanXInitFinished', @setup
setup: -> setup: ->
$.off d, '4chanXInitFinished', PSAHiding.setup $.off d, '4chanXInitFinished', PSAHiding.setup

View File

@ -77,13 +77,13 @@ ExpandThread =
a.textContent = "Error #{req.statusText} (#{req.status})" a.textContent = "Error #{req.statusText} (#{req.status})"
return return
data = JSON.parse(req.response).posts Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler
Build.spoilerRange[thread.board] = data.shift().custom_spoiler
posts = [] posts = []
postsRoot = [] postsRoot = []
filesCount = 0 filesCount = 0
for postData in data for postData in req.response.posts
continue if postData.no is thread.ID
if post = thread.posts[postData.no] if post = thread.posts[postData.no]
filesCount++ if 'file' of post filesCount++ if 'file' of post
postsRoot.push post.nodes.root postsRoot.push post.nodes.root

View File

@ -77,9 +77,8 @@ ThreadStats =
onThreadsLoad: -> onThreadsLoad: ->
return unless Conf["Page Count in Stats"] and @status is 200 return unless Conf["Page Count in Stats"] and @status is 200
pages = JSON.parse @response for page in @response
for page in pages
for thread in page.threads when thread.no is ThreadStats.thread.ID for thread in page.threads when thread.no is ThreadStats.thread.ID
ThreadStats.pageCountEl.textContent = page.page ThreadStats.pageCountEl.textContent = page.page
(if page.page is pages.length - 1 then $.addClass else $.rmClass) ThreadStats.pageCountEl, 'warning' (if page.page is @response.length - 1 then $.addClass else $.rmClass) ThreadStats.pageCountEl, 'warning'
return return

View File

@ -158,7 +158,7 @@ ThreadUpdater =
switch req.status switch req.status
when 200 when 200
g.DEAD = false g.DEAD = false
ThreadUpdater.parse JSON.parse(req.response).posts ThreadUpdater.parse req.response.posts
ThreadUpdater.setInterval() ThreadUpdater.setInterval()
when 404 when 404
g.DEAD = true g.DEAD = true

View File

@ -670,7 +670,7 @@ QR =
# Too many frequent mistyped captchas will auto-ban you! # Too many frequent mistyped captchas will auto-ban you!
# On connection error, the post most likely didn't go through. # On connection error, the post most likely didn't go through.
QR.cooldown.set delay: 2 QR.cooldown.set delay: 2
else if err.textContent and m = err.textContent.match /wait\s(\d+)\ssecond/i else if err.textContent and m = err.textContent.match /wait\s+(\d+)\s+second/i
QR.cooldown.auto = if QR.captcha.isEnabled QR.cooldown.auto = if QR.captcha.isEnabled
!!QR.captcha.captchas.length !!QR.captcha.captchas.length
else else