Merge tag 'beta' into unreadcount
4chan X v1.8.7. Conflicts: CHANGELOG.md LICENSE builds/4chan-X-beta.crx builds/4chan-X-beta.meta.js builds/4chan-X-beta.user.js builds/4chan-X-noupdate.crx builds/4chan-X-noupdate.user.js builds/4chan-X.crx builds/4chan-X.meta.js builds/4chan-X.user.js builds/4chan-X.zip builds/updates-beta.xml builds/updates.xml package.json
37
CHANGELOG.md
@ -1,3 +1,21 @@
|
||||
**ccd0**
|
||||
- Fix unread count updating bug in Firefox as in v1.8.6.2.
|
||||
|
||||
### v1.8.7
|
||||
*2014-07-20*
|
||||
|
||||
Based on v1.8.6.1.
|
||||
|
||||
**ccd0**
|
||||
- Various bugfixes.
|
||||
|
||||
**MayhemYDG**
|
||||
- Improvements to Chrom* storage API.
|
||||
|
||||
**Zixaphir**
|
||||
- Add gallery option `Scroll to Post`: Scrolls to the post containing the currently active image.
|
||||
- Gallery now loops.
|
||||
|
||||
### v1.8.6.2
|
||||
*2014-07-23*
|
||||
|
||||
@ -67,7 +85,6 @@
|
||||
- Begin making available a version of the script with the updater disabled.
|
||||
- The removal of slugs from the URL when you open a thread is now optional, and can be disabled by unchecking `Normalize URL`.
|
||||
- The boards and file extensions for which a Sauce link is displayed can now be controlled by adding `;boards:[list]` and `types:[list]` respectively.
|
||||
- Restrict Sauce links to `http` and `https` to prevent malicious script installation.
|
||||
- Although usually not needed, `%%` can be used in format specifiers to write a literal `%`.
|
||||
- Various bugfixes.
|
||||
|
||||
@ -647,7 +664,7 @@ Remove /v/ from stable Foolz archive.
|
||||
**Spittie**
|
||||
- Check image dimension before uploading
|
||||
|
||||

|
||||

|
||||
- Bug fixes
|
||||
- Update archives
|
||||
|
||||
@ -695,7 +712,7 @@ Remove /v/ from stable Foolz archive.
|
||||
**Spittie**
|
||||
- Upload images directly from urls
|
||||
|
||||

|
||||

|
||||
- Add gfycat.com embedding
|
||||
- Replace some icons with fontawesome
|
||||
- Add Metro favicons (lel)
|
||||
@ -771,7 +788,7 @@ Remove /v/ from stable Foolz archive.
|
||||
- The last index refresh timer will now indicate the last time the index changed from 4chan's side, instead of the last time you refreshed the index.
|
||||
- You can now refresh the index page you are on with the refresh shortcut in the header bar or the same keybind for refreshing threads.
|
||||
- You can now switch between paged and all-threads index modes via the "Index Navigation" header sub-menu (note that this replaces infinite scrolling):<br>
|
||||

|
||||

|
||||
- Threads in the index can now be sorted by:
|
||||
<ul>
|
||||
<li> Bump order
|
||||
@ -881,7 +898,7 @@ Remove /v/ from stable Foolz archive.
|
||||
|
||||
**Zixaphir**:
|
||||
- Fix an issue with the file dialog randomly opening multiple times (with seaweedchan)
|
||||

|
||||

|
||||
- Add new feature: `Gallery`.
|
||||
* Opens images in a lightweight Gallery script.
|
||||
* If enabled while Image Expansion is disabled, will takeover as the default action when images are clicked.
|
||||
@ -932,7 +949,7 @@ Remove /v/ from stable Foolz archive.
|
||||
|
||||
**seaweedchan**:
|
||||
|
||||

|
||||

|
||||
|
||||
- Ported `Custom Board Titles` feature from Appchan X (with Zixaphir)
|
||||
- This allows you to edit the board title and subtitle in real-time by ctrl+clicking them
|
||||
@ -963,7 +980,7 @@ Remove /v/ from stable Foolz archive.
|
||||
|
||||
**MayhemYDG**:
|
||||
|
||||

|
||||

|
||||
|
||||
- Greatly improved thread watcher
|
||||
- Added submenu with ability to prune 404'd threads, filter by current board, etc
|
||||
@ -978,7 +995,7 @@ Remove /v/ from stable Foolz archive.
|
||||
|
||||
**Zixaphir**:
|
||||
|
||||

|
||||

|
||||
|
||||
- Drastically improved the accuracy and quality of the linkifier (with seaweedchan)
|
||||
- Removed `Allow False Positives` option due to the accuracy of the new linkifier regex
|
||||
@ -1220,7 +1237,7 @@ Remove /v/ from stable Foolz archive.
|
||||
**seaweedchan**:
|
||||
- Small bug fixes
|
||||
|
||||
## v1.2.0 - "Youmu" 
|
||||
## v1.2.0 - "Youmu" 
|
||||
*2013-05-10*
|
||||
|
||||
**MayhemYDG**:
|
||||
@ -1252,7 +1269,7 @@ Remove /v/ from stable Foolz archive.
|
||||
- QR with 4chan Pass made a little wider
|
||||
- Styling changes for spoiler label, also added `.has-spoiler` class for QR
|
||||
|
||||

|
||||

|
||||
|
||||
### v1.1.17
|
||||
*2013-05-08*
|
||||
|
||||
@ -180,16 +180,6 @@ module.exports = (grunt) ->
|
||||
pkg = grunt.config 'pkg'
|
||||
pkg.type = type
|
||||
grunt.config 'pkg', pkg
|
||||
|
||||
if type is 'crx'
|
||||
pkg.align = '-webkit-align'
|
||||
pkg.justify = '-webkit-justify-content'
|
||||
pkg.transform = '-webkit-transform'
|
||||
else
|
||||
pkg.align = 'align'
|
||||
pkg.justify = 'justify-content'
|
||||
pkg.transform = 'transform'
|
||||
|
||||
grunt.log.ok 'pkg.type = %s', type
|
||||
|
||||
grunt.registerTask 'set-channel', 'Set the update channel', (channel) ->
|
||||
|
||||
2
LICENSE
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* 4chan X - Version 1.8.6.2 - 2014-07-23
|
||||
* 4chan X - Version 1.8.7 - 2014-07-20
|
||||
*
|
||||
* Licensed under the MIT license.
|
||||
* https://github.com/ccd0/4chan-x/blob/master/LICENSE
|
||||
|
||||
14
README.md
@ -6,7 +6,10 @@ https://github.com/Nebukazar/OneeChan
|
||||
## [Install](https://ccd0.github.io/4chan-x/builds/4chan-X.user.js) (Firefox)
|
||||
Install [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/), then click the link above to install.
|
||||
|
||||
**Note**: The combination of Firefox 29 and Greasemonkey 2.0 may cause 4chan X not to work.
|
||||
You may want to try the [Greasemonkey 2.1 beta](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/versions/2.1beta1), which fixes bugs in 2.0 that can prevent 4chan X from updating[[1]](https://github.com/greasemonkey/greasemonkey/issues/1938) or, in some versions of Firefox, break posting images from URLs and downloading with the original filename[[2]](https://github.com/greasemonkey/greasemonkey/issues/1937).
|
||||
|
||||
### Known issues
|
||||
The combination of Firefox 29 and Greasemonkey 2.0 may cause 4chan X not to work.
|
||||
Try [upgrading Firefox](http://www.mozilla.org/en-US/firefox/new/) to version 30 or higher.
|
||||
Alternatively, you can downgrade to [Greasemonkey 1.15](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/versions/#version-1.15) and turn off automatic updates for Greasemonkey ([see pic](https://raw.githubusercontent.com/ccd0/4chan-x/master/img/2014-07-12_16-19-32.png)).
|
||||
|
||||
@ -16,6 +19,9 @@ This should also work for non-Windows/dev/canary Chrome and Chromium-based versi
|
||||
|
||||
**Note**: The stable and beta releases of Chrome on Windows will disable extensions not installed from the Chrome store, so users will need to install 4chan X from [here](https://chrome.google.com/webstore/detail/4chan-x/ohnjgmpcibpbafdlkimncjhflgedgpam).
|
||||
|
||||
### Known issues
|
||||
Some recent versions of Chromium/Chrome (starting at 38.0.2085.0) suffer from a [bug](https://crbug.com/393686) that prevents extensions from making HTTP requests in the usual way. This breaks, among other things, thread updating, quick reply, and, when `JSON Navigation` is enabled, the thread index. Until this is fixed, try another version or a different browser.
|
||||
|
||||
## Other browsers
|
||||
This fork of 4chan X is not guaranteed to work correctly in other browsers, but you are welcome to try your luck. Pull requests to fix the bugs you will likely find are always welcome.
|
||||
|
||||
@ -32,7 +38,7 @@ New features and non-urgent bugfixes are released on the beta channel for furthe
|
||||
- [Firefox version](https://ccd0.github.io/4chan-x/builds/4chan-X-beta.user.js)
|
||||
- [Chromium version](https://ccd0.github.io/4chan-x/builds/4chan-X-beta.crx)
|
||||
|
||||
## [Frequently Asked Questions](https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions)
|
||||
|
||||
## [Reporting Bugs and Contributing](https://github.com/ccd0/4chan-x/blob/master/CONTRIBUTING.md)
|
||||
## More information
|
||||
### [Frequently Asked Questions](https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions)
|
||||
### [Reporting Bugs and Contributing](https://github.com/ccd0/4chan-x/blob/master/CONTRIBUTING.md)
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// ==UserScript==
|
||||
// @name 4chan X
|
||||
// @version 1.8.6.2
|
||||
// @version 1.8.7
|
||||
// @minGMVer 1.14
|
||||
// @minFFVer 26
|
||||
// @namespace 4chan-X
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// ==UserScript==
|
||||
// @name 4chan X
|
||||
// @version 1.8.6.2
|
||||
// @version 1.8.7
|
||||
// @minGMVer 1.14
|
||||
// @minFFVer 26
|
||||
// @namespace 4chan-X
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
|
||||
<app appid='lacclbnghgdicfifcamcmcnilckjamag'>
|
||||
<updatecheck codebase='https://ccd0.github.io/4chan-x/builds/4chan-X-beta.crx' version='1.8.6.2' />
|
||||
<updatecheck codebase='https://ccd0.github.io/4chan-x/builds/4chan-X-beta.crx' version='1.8.7' />
|
||||
</app>
|
||||
</gupdate>
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
|
||||
<app appid='lacclbnghgdicfifcamcmcnilckjamag'>
|
||||
<updatecheck codebase='https://ccd0.github.io/4chan-x/builds/4chan-X.crx' version='1.8.6.2' />
|
||||
<updatecheck codebase='https://ccd0.github.io/4chan-x/builds/4chan-X.crx' version='1.8.7' />
|
||||
</app>
|
||||
</gupdate>
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 355 KiB After Width: | Height: | Size: 355 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "4chan-X",
|
||||
"version": "1.8.6.2",
|
||||
"version": "1.8.7",
|
||||
"description": "Cross-browser userscript for maximum lurking on 4chan.",
|
||||
"meta": {
|
||||
"name": "4chan X",
|
||||
|
||||
@ -448,13 +448,15 @@ Config =
|
||||
'Hide Thumbnails': [
|
||||
false
|
||||
]
|
||||
# Fit Width =/= Fit width
|
||||
'Fit Width': [
|
||||
'Fit Width': [ # 'Fit width' (lowercase W) belongs to Image Expansion. Engine limitations, heh.
|
||||
true
|
||||
]
|
||||
'Fit Height': [
|
||||
true
|
||||
]
|
||||
'Scroll to Post': [
|
||||
true
|
||||
]
|
||||
'Slide Delay': [
|
||||
5.0
|
||||
]
|
||||
|
||||
@ -360,7 +360,7 @@ Header =
|
||||
editCustomNav: ->
|
||||
Settings.open 'Advanced'
|
||||
settings = $.id 'fourchanx-settings'
|
||||
$('input[name=boardnav]', settings).focus()
|
||||
$('textarea[name=boardnav]', settings).focus()
|
||||
|
||||
hashScroll: ->
|
||||
hash = @location.hash[1..]
|
||||
|
||||
@ -147,6 +147,7 @@ Index =
|
||||
return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0
|
||||
switch e.target.nodeName
|
||||
when 'BUTTON'
|
||||
e.target.blur()
|
||||
a = e.target.parentNode
|
||||
when 'A'
|
||||
a = e.target
|
||||
|
||||
@ -35,7 +35,7 @@ Main =
|
||||
Conf['CachedTitles'] = []
|
||||
$.get Conf, (items) ->
|
||||
$.extend Conf, items
|
||||
$.asap (-> doc = d.documentElement), Main.initFeatures
|
||||
Main.initFeatures()
|
||||
|
||||
$.on d, '4chanMainInit', Main.initStyle
|
||||
|
||||
@ -85,7 +85,7 @@ Main =
|
||||
return if !Main.isThisPageLegit() or $.hasClass doc, 'fourchan-x'
|
||||
# disable the mobile layout
|
||||
$('link[href*=mobile]', d.head)?.disabled = true
|
||||
$.addClass doc, 'fourchan-x', 'seaweedchan', g.VIEW, '<% if (type === 'crx') { %>blink<% } else { %>gecko<% } %>'
|
||||
$.addClass doc, 'fourchan-x', 'seaweedchan', g.VIEW, if chrome? then 'blink' else 'gecko'
|
||||
$.addStyle Main.css
|
||||
|
||||
Main.setClass()
|
||||
@ -335,4 +335,4 @@ Main =
|
||||
<% } %>
|
||||
]
|
||||
|
||||
Main.init()
|
||||
$.asap (-> (doc = d.documentElement) and d.head), Main.init
|
||||
|
||||
@ -313,9 +313,9 @@ Settings =
|
||||
interval.value = Conf['Interval']
|
||||
customCSS.checked = Conf['Custom CSS']
|
||||
inputs['usercss'].disabled = !Conf['Custom CSS']
|
||||
$.on interval, 'change', ThreadUpdater.cb.interval
|
||||
$.on customCSS, 'change', Settings.togglecss
|
||||
$.on $.id('apply-css'), 'click', Settings.usercss
|
||||
$.on interval, 'change', ThreadUpdater.cb.interval
|
||||
$.on customCSS, 'change', Settings.togglecss
|
||||
$.on $('#apply-css', section), 'click', Settings.usercss
|
||||
|
||||
archBoards = {}
|
||||
for {name, boards, files, software, withCredentials} in Redirect.archives
|
||||
@ -357,7 +357,7 @@ Settings =
|
||||
|
||||
boardSelect = $('#archive-board-select', section)
|
||||
$.add boardSelect, boardOptions
|
||||
table = $.id 'archive-table'
|
||||
table = $('#archive-table', section)
|
||||
$.on boardSelect, 'change', ->
|
||||
$('tbody > :not([hidden])', table).hidden = true
|
||||
$("tbody > .#{@value}", table).hidden = false
|
||||
|
||||
@ -849,7 +849,9 @@ span.hide-announcement {
|
||||
}
|
||||
.persona {
|
||||
width: 100%;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
#dump-button {
|
||||
@ -869,6 +871,7 @@ span.hide-announcement {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.persona .field {
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
width: 0;
|
||||
}
|
||||
@ -1279,22 +1282,32 @@ div.boardTitle {
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 30;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
background: rgba(0,0,0,0.7);
|
||||
}
|
||||
.gal-viewport {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
<%= align %>-items: stretch;
|
||||
-webkit-align-items: stretch;
|
||||
align-items: stretch;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.gal-thumbnails {
|
||||
-webkit-flex: 0 0 150px;
|
||||
flex: 0 0 150px;
|
||||
overflow-y: auto;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
<%= align %>-items: stretch;
|
||||
-webkit-align-items: stretch;
|
||||
align-items: stretch;
|
||||
text-align: center;
|
||||
background: rgba(0,0,0,.5);
|
||||
border-left: 1px solid #222;
|
||||
@ -1309,6 +1322,7 @@ div.boardTitle {
|
||||
width: auto;
|
||||
}
|
||||
.gal-thumb {
|
||||
-webkit-flex: 0 0 auto;
|
||||
flex: 0 0 auto;
|
||||
padding: 3px;
|
||||
line-height: 0;
|
||||
@ -1327,6 +1341,7 @@ div.boardTitle {
|
||||
}
|
||||
.gal-prev,
|
||||
.gal-next {
|
||||
-webkit-flex: 0 0 20px;
|
||||
flex: 0 0 20px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
@ -1341,7 +1356,8 @@ div.boardTitle {
|
||||
.gal-next::after {
|
||||
position: absolute;
|
||||
top: 48.6%;
|
||||
<%= transform %>: translateY(-50%)
|
||||
-webkit-transform: translateY(-50%);
|
||||
transform: translateY(-50%);
|
||||
display: inline-block;
|
||||
border-top: 11px solid transparent;
|
||||
border-bottom: 11px solid transparent;
|
||||
@ -1357,10 +1373,14 @@ div.boardTitle {
|
||||
}
|
||||
.gal-image {
|
||||
order: 1;
|
||||
-webkit-flex: 1 0 auto;
|
||||
flex: 1 0 auto;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
<%= align %>-items: flex-start;
|
||||
<%= justify %>: space-around;
|
||||
-webkit-align-items: flex-start;
|
||||
align-items: flex-start;
|
||||
-webkit-justify-content: space-around;
|
||||
justify-content: space-around;
|
||||
overflow: hidden;
|
||||
/* Flex > Non-Flex child max-width and overflow fix (Firefox only?) */
|
||||
width: 1%;
|
||||
|
||||
@ -42,11 +42,24 @@ $.ajax = do ->
|
||||
# With the `If-Modified-Since` header we only receive the HTTP headers and no body for 304 responses.
|
||||
# This saves a lot of bandwidth and CPU time for both the users and the servers.
|
||||
lastModified = {}
|
||||
blockedURLs = {}
|
||||
blockedError = (url) ->
|
||||
return if blockedURLs[url]
|
||||
blockedURLs[url] = true
|
||||
h_message = '<%= meta.name %> was blocked from loading the following URL:<br><span></span><br>'
|
||||
h_message += '[<a href="<%= meta.faq %>#why-was-4chan-x-blocked-from-loading-a-url" target="_blank">More info</a>]'
|
||||
message = $.el 'div', innerHTML: h_message
|
||||
$('span', message).textContent = (if /^\/\//.test url then location.protocol else '') + url
|
||||
new Notice 'error', message, 30, -> delete blockedURLs[url]
|
||||
(url, options, extra={}) ->
|
||||
{type, whenModified, upCallbacks, form} = extra
|
||||
r = new XMLHttpRequest()
|
||||
type or= form and 'post' or 'get'
|
||||
r.open type, url, true
|
||||
try
|
||||
r.open type, url, true
|
||||
catch err
|
||||
blockedError url
|
||||
return options.onerror?()
|
||||
if whenModified
|
||||
r.setRequestHeader 'If-Modified-Since', lastModified[url] if url of lastModified
|
||||
$.on r, 'load', -> lastModified[url] = r.getResponseHeader 'Last-Modified'
|
||||
@ -275,9 +288,6 @@ $.sync = do ->
|
||||
cb changes[key].newValue, key
|
||||
return
|
||||
(key, cb) -> $.syncing[key] = cb
|
||||
|
||||
$.desync = (key) -> delete $.syncing[key]
|
||||
|
||||
$.localKeys = [
|
||||
# filters
|
||||
'name',
|
||||
@ -295,85 +305,99 @@ $.localKeys = [
|
||||
# custom css
|
||||
'usercss'
|
||||
]
|
||||
|
||||
# https://developer.chrome.com/extensions/storage.html
|
||||
|
||||
$.delete = (keys) ->
|
||||
chrome.storage.sync.remove keys
|
||||
|
||||
$.get = (key, val, cb) ->
|
||||
if typeof cb is 'function'
|
||||
items = $.item key, val
|
||||
else
|
||||
items = key
|
||||
cb = val
|
||||
localItems = null
|
||||
syncItems = null
|
||||
for key, val of items
|
||||
if key in $.localKeys
|
||||
(localItems or= {})[key] = val
|
||||
else
|
||||
(syncItems or= {})[key] = val
|
||||
|
||||
count = 0
|
||||
done = (item) ->
|
||||
if chrome.runtime.lastError
|
||||
c.error chrome.runtime.lastError.message
|
||||
$.extend items, item
|
||||
cb items unless --count
|
||||
|
||||
if localItems
|
||||
count++
|
||||
chrome.storage.local.get localItems, done
|
||||
if syncItems
|
||||
count++
|
||||
chrome.storage.sync.get syncItems, done
|
||||
|
||||
$.set = do ->
|
||||
do ->
|
||||
items =
|
||||
sync: {}
|
||||
local: {}
|
||||
timeout = {}
|
||||
sync: {}
|
||||
|
||||
$.delete = (keys) ->
|
||||
if typeof keys is 'string'
|
||||
keys = [keys]
|
||||
local = []
|
||||
sync = []
|
||||
for key in keys
|
||||
if key in $.localKeys
|
||||
local.push key
|
||||
delete items.local[key]
|
||||
else
|
||||
sync.push key
|
||||
delete items.sync[key]
|
||||
chrome.storage.local.remove local
|
||||
chrome.storage.sync.remove sync
|
||||
|
||||
$.get = (key, val, cb) ->
|
||||
if typeof cb is 'function'
|
||||
data = $.item key, val
|
||||
else
|
||||
data = key
|
||||
cb = val
|
||||
|
||||
localItems = null
|
||||
syncItems = null
|
||||
for key, val of data
|
||||
if key in $.localKeys
|
||||
(localItems or= {})[key] = val
|
||||
else
|
||||
(syncItems or= {})[key] = val
|
||||
|
||||
count = 0
|
||||
done = (result) ->
|
||||
if chrome.runtime.lastError
|
||||
c.error chrome.runtime.lastError.message
|
||||
$.extend data, result
|
||||
cb data unless --count
|
||||
|
||||
if localItems
|
||||
count++
|
||||
chrome.storage.local.get localItems, done
|
||||
if syncItems
|
||||
count++
|
||||
chrome.storage.sync.get syncItems, done
|
||||
|
||||
timeout = {}
|
||||
setArea = (area) ->
|
||||
data = items[area]
|
||||
return if !Object.keys(data).length or timeout[area]
|
||||
items[area] = {}
|
||||
return if !Object.keys(data).length or timeout[area] > Date.now()
|
||||
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]
|
||||
if area is 'sync' and chrome.storage.sync.QUOTA_BYTES_PER_ITEM < JSON.stringify(val).length + key.length
|
||||
c.error chrome.runtime.lastError.message, key, val
|
||||
continue
|
||||
items[area][key] = val
|
||||
timeout[area] = setTimeout setArea, $.MINUTE, area
|
||||
setTimeout setArea, $.MINUTE, area
|
||||
timeout[area] = Date.now() + $.MINUTE
|
||||
return
|
||||
delete timeout[area]
|
||||
items[area] = {}
|
||||
|
||||
setAll = $.debounce $.SECOND, ->
|
||||
for key in $.localKeys
|
||||
if key of items.sync
|
||||
items.local[key] = items.sync[key]
|
||||
delete items.sync[key]
|
||||
try
|
||||
setArea 'local'
|
||||
setArea 'sync'
|
||||
catch err
|
||||
c.error err.stack
|
||||
setSync = $.debounce $.SECOND, ->
|
||||
setArea 'sync'
|
||||
|
||||
(key, val) ->
|
||||
$.set = (key, val) ->
|
||||
if typeof key is 'string'
|
||||
items.sync[key] = val
|
||||
else
|
||||
$.extend items.sync, key
|
||||
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
|
||||
for key in $.localKeys when key of items.sync
|
||||
items.local[key] = items.sync[key]
|
||||
delete items.sync[key]
|
||||
setArea 'local'
|
||||
setSync()
|
||||
|
||||
$.clear = (cb) ->
|
||||
items.local = {}
|
||||
items.sync = {}
|
||||
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 { %>
|
||||
|
||||
# http://wiki.greasespot.net/Main_Page
|
||||
@ -383,8 +407,6 @@ $.sync = do ->
|
||||
cb JSON.parse(newValue), key
|
||||
(key, cb) -> $.syncing[g.NAMESPACE + key] = cb
|
||||
|
||||
$.desync = (key) -> delete $.syncing[g.NAMESPACE + key]
|
||||
|
||||
$.delete = (keys) ->
|
||||
unless keys instanceof Array
|
||||
keys = [keys]
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
class Notice
|
||||
constructor: (type, content, @timeout) ->
|
||||
constructor: (type, content, @timeout, @onclose) ->
|
||||
@el = $.el 'div',
|
||||
innerHTML: '<a href="javascript:;" class="close fa fa-times" title="Close"></a><div class="message"></div>'
|
||||
@el.style.opacity = 0
|
||||
@ -27,3 +27,4 @@ class Notice
|
||||
close: =>
|
||||
$.off d, 'visibilitychange', @add
|
||||
$.rm @el
|
||||
@onclose?()
|
||||
|
||||
@ -77,7 +77,7 @@ Gallery =
|
||||
nodes.current.parentElement.scrollTop = 0
|
||||
|
||||
Gallery.cb.open.call if image
|
||||
$ "[href='#{image.href.replace /https?:/, ''}']", nodes.thumbs
|
||||
$("[href='#{image.href}']", nodes.thumbs) or Gallery.images[0]
|
||||
else
|
||||
Gallery.images[0]
|
||||
|
||||
@ -86,6 +86,7 @@ Gallery =
|
||||
|
||||
generateThumb: (file) ->
|
||||
post = Get.postFromNode file
|
||||
return if post.isClone or post.isHidden
|
||||
return unless post.file and (post.file.isImage or post.file.isVideo or Conf['PDF in Gallery'])
|
||||
return if Gallery.fullIDs[post.fullID]
|
||||
Gallery.fullIDs[post.fullID] = true
|
||||
@ -137,7 +138,9 @@ Gallery =
|
||||
|
||||
{nodes} = Gallery
|
||||
{name} = nodes
|
||||
slideshow = Gallery.slideshow and +@dataset.id > +nodes.current.dataset.id
|
||||
oldID = +nodes.current.dataset.id
|
||||
newID = +@dataset.id
|
||||
slideshow = Gallery.slideshow and (newID > oldID or (oldID is Gallery.images.length-1 and newID is 0))
|
||||
|
||||
$.rmClass el, 'gal-highlight' if el = $ '.gal-highlight', nodes.thumbs
|
||||
$.addClass @, 'gal-highlight'
|
||||
@ -162,6 +165,10 @@ Gallery =
|
||||
nodes.next.focus()
|
||||
Gallery.cb[if slideshow then 'setupTimer' else 'stop']()
|
||||
|
||||
# Scroll to post
|
||||
if Conf['Scroll to Post'] and post = (post = g.posts[file.dataset.post])?.nodes.root
|
||||
Header.scrollTo post
|
||||
|
||||
# Center selected thumbnail
|
||||
nodes.thumbs.scrollTop = @offsetTop + @offsetHeight/2 - nodes.thumbs.clientHeight/2
|
||||
|
||||
@ -199,8 +206,14 @@ Gallery =
|
||||
if postObj.filedeleted
|
||||
post.kill true
|
||||
|
||||
prev: -> Gallery.cb.open.call Gallery.images[+Gallery.nodes.current.dataset.id - 1]
|
||||
next: -> Gallery.cb.open.call Gallery.images[+Gallery.nodes.current.dataset.id + 1]
|
||||
prev: ->
|
||||
Gallery.cb.open.call(
|
||||
Gallery.images[+Gallery.nodes.current.dataset.id - 1] or Gallery.images[Gallery.images.length - 1]
|
||||
)
|
||||
next: ->
|
||||
Gallery.cb.open.call(
|
||||
Gallery.images[+Gallery.nodes.current.dataset.id + 1] or Gallery.images[0]
|
||||
)
|
||||
enterKey: -> if Gallery.nodes.current.paused then Gallery.nodes.current.play() else Gallery.cb.next()
|
||||
click: -> Gallery.cb[if Gallery.nodes.current.controls then 'stop' else 'enterKey']()
|
||||
toggle: -> (if Gallery.nodes then Gallery.cb.close else Gallery.build)()
|
||||
@ -223,7 +236,6 @@ Gallery =
|
||||
{current} = Gallery.nodes
|
||||
isVideo = current.nodeName is 'VIDEO'
|
||||
Video.start current if isVideo
|
||||
return Gallery.cb.stop() if !Gallery.images[+Gallery.nodes.current.dataset.id + 1]
|
||||
if (if isVideo then current.readyState > 4 else current.complete) or current.nodeName is 'IFRAME'
|
||||
Gallery.cb.startTimer()
|
||||
else
|
||||
@ -291,7 +303,7 @@ Gallery =
|
||||
el: label
|
||||
|
||||
createSubEntries: ->
|
||||
subEntries = ['Hide Thumbnails', 'Fit Width', 'Fit Height'].map Gallery.menu.createSubEntry
|
||||
subEntries = ['Hide Thumbnails', 'Fit Width', 'Fit Height', 'Scroll to Post'].map Gallery.menu.createSubEntry
|
||||
|
||||
delayLabel = $.el 'label', innerHTML: 'Slide Delay: <input type="number" name="Slide Delay" min="0" step="any" class="field">'
|
||||
delayInput = delayLabel.firstElementChild
|
||||
|
||||
@ -10,22 +10,19 @@ ImageHover =
|
||||
$.on @file.thumb, 'mouseover', ImageHover.mouseover
|
||||
mouseover: (e) ->
|
||||
post = Get.postFromNode @
|
||||
{isVideo} = post.file
|
||||
if post.file.fullImage
|
||||
el = post.file.fullImage
|
||||
{file} = post
|
||||
{isVideo} = file
|
||||
if el = file.fullImage
|
||||
el.id = 'ihover'
|
||||
TrashQueue.remove el
|
||||
else
|
||||
el = $.el (if isVideo then 'video' else 'img'),
|
||||
file.fullImage = el = $.el (if isVideo then 'video' else 'img'),
|
||||
className: 'full-image'
|
||||
src: post.file.URL
|
||||
post.file.fullImage = el
|
||||
{thumb} = post.file
|
||||
if d.body.contains thumb
|
||||
$.after thumb, el unless el is thumb.nextSibling
|
||||
else
|
||||
$.add Header.hover, el if el.parentNode isnt Header.hover
|
||||
el.id = 'ihover'
|
||||
el.dataset.fullID = post.fullID
|
||||
id: 'ihover'
|
||||
el.dataset.fullID = post.fullID
|
||||
$.on el, 'error', ImageHover.error
|
||||
el.src = file.URL
|
||||
$.after file.thumb, el
|
||||
if isVideo
|
||||
el.loop = true
|
||||
el.controls = false
|
||||
@ -35,7 +32,7 @@ ImageHover =
|
||||
el: el
|
||||
latestEvent: e
|
||||
endEvents: 'mouseout click'
|
||||
asapTest: -> (if isVideo then el.videoHeight else el.naturalHeight)
|
||||
asapTest: -> (if isVideo then el.readyState >= el.HAVE_CURRENT_DATA else el.naturalHeight)
|
||||
noRemove: true
|
||||
cb: ->
|
||||
$.off el, 'error', ImageHover.error
|
||||
@ -43,7 +40,6 @@ ImageHover =
|
||||
el.pause()
|
||||
TrashQueue.add el, post
|
||||
el.removeAttribute 'id'
|
||||
$.on el, 'error', ImageHover.error
|
||||
error: ->
|
||||
return unless doc.contains @
|
||||
post = g.posts[@dataset.fullID]
|
||||
|
||||
@ -44,7 +44,6 @@ Sauce =
|
||||
a = Sauce.link.cloneNode true
|
||||
a.href = parts['url']
|
||||
a.textContent = parts['text']
|
||||
return null unless /^https?:$/.test a.protocol
|
||||
a
|
||||
node: ->
|
||||
return if @isClone or !@file
|
||||
|
||||
@ -151,13 +151,12 @@ Linkify =
|
||||
|
||||
embed: (data) ->
|
||||
[key, uid, options, link, post] = data
|
||||
href = link.href
|
||||
embed = $.el 'a',
|
||||
className: 'embedder'
|
||||
href: 'javascript:;'
|
||||
href: link.href
|
||||
textContent: '(embed)'
|
||||
|
||||
embed.dataset[name] = value for name, value of {key, href, uid, options}
|
||||
embed.dataset[name] = value for name, value of {key, uid, options}
|
||||
|
||||
$.addClass link, "#{embed.dataset.key}"
|
||||
|
||||
@ -181,7 +180,8 @@ Linkify =
|
||||
return
|
||||
|
||||
cb:
|
||||
toggle: ->
|
||||
toggle: (e) ->
|
||||
e?.preventDefault()
|
||||
if $.hasClass @, "embedded"
|
||||
$.rm @previousElementSibling
|
||||
@previousElementSibling.hidden = false
|
||||
@ -253,14 +253,14 @@ Linkify =
|
||||
el: (a) ->
|
||||
el = $.el 'div'
|
||||
el.innerHTML = '<a target="_blank"><img></a>'
|
||||
el.firstChild.href = el.firstChild.firstChild.src = a.dataset.href
|
||||
el.firstChild.href = el.firstChild.firstChild.src = a.href
|
||||
el
|
||||
,
|
||||
key: 'InstallGentoo'
|
||||
regExp: /.*(?:paste.installgentoo.com\/view\/)([0-9a-z_]+)/
|
||||
el: (a) ->
|
||||
$.el 'iframe',
|
||||
src: "http://paste.installgentoo.com/view/embed/#{a.dataset.uid}"
|
||||
src: "https://paste.installgentoo.com/view/embed/#{a.dataset.uid}"
|
||||
,
|
||||
key: 'Twitter'
|
||||
regExp: /.*twitter.com\/(.+\/status\/\d+)/
|
||||
@ -302,7 +302,7 @@ Linkify =
|
||||
el.firstChild.children[i].src = "https://mediacru.sh/#{a.dataset.uid}.#{ext}"
|
||||
when 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg'
|
||||
el.innerHTML = '<a target="_blank"><img></a>'
|
||||
el.firstChild.href = a.dataset.href
|
||||
el.firstChild.href = a.href
|
||||
el.firstChild.firstChild.src = "https://mediacru.sh/#{file.file}"
|
||||
when 'audio/mpeg', 'audio/ogg'
|
||||
el.innerHTML = '<audio controls><source type="audio/ogg"><source type="audio/mpeg"></audio>'
|
||||
@ -322,20 +322,20 @@ Linkify =
|
||||
regExp: /.*gfycat.com\/(?:iframe\/)?(\S*)/
|
||||
el: (a) ->
|
||||
div = $.el 'iframe',
|
||||
src: "http://gfycat.com/iframe/#{a.dataset.uid}"
|
||||
src: "//gfycat.com/iframe/#{a.dataset.uid}"
|
||||
,
|
||||
key: 'SoundCloud'
|
||||
regExp: /.*(?:soundcloud.com\/|snd.sc\/)([^#\&\?]*).*/
|
||||
style: 'border: 0; width: 500px; height: 400px;'
|
||||
el: (a) ->
|
||||
$.el 'iframe',
|
||||
src: "//w.soundcloud.com/player/?visual=true&show_comments=false&url=https%3A%2F%2Fsoundcloud.com%2F#{encodeURIComponent a.dataset.uid}"
|
||||
src: "https://w.soundcloud.com/player/?visual=true&show_comments=false&url=https%3A%2F%2Fsoundcloud.com%2F#{encodeURIComponent a.dataset.uid}"
|
||||
title:
|
||||
api: (uid) -> "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F#{encodeURIComponent uid}"
|
||||
text: (_) -> _.title
|
||||
,
|
||||
key: 'StrawPoll'
|
||||
regExp: /strawpoll\.me\/(?:embed_\d+\/)?(\d+)/
|
||||
regExp: /strawpoll\.me\/(?:embed_\d+\/)?(\d+(?:\/r)?)/
|
||||
style: 'border: 0; width: 600px; height: 406px;'
|
||||
el: (a) ->
|
||||
$.el 'iframe',
|
||||
|
||||