Merge tag 'beta' into archiveupdate
4chan X v1.8.8.3. 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
This commit is contained in:
commit
9b0407754f
37
CHANGELOG.md
37
CHANGELOG.md
@ -1,3 +1,40 @@
|
||||
### v1.8.8.3
|
||||
*2014-08-02*
|
||||
|
||||
**ccd0**, **Zixaphir**
|
||||
- Bug fixes.
|
||||
|
||||
### v1.8.8.2
|
||||
*2014-07-29*
|
||||
|
||||
**ccd0**
|
||||
- Bug fixes.
|
||||
|
||||
### v1.8.8.1
|
||||
*2014-07-28*
|
||||
|
||||
**ccd0**
|
||||
- Fix for image 404 redirection which was broken in v1.8.8.
|
||||
|
||||
### v1.8.8
|
||||
*2014-07-27*
|
||||
|
||||
Based on v1.8.7.1.
|
||||
|
||||
**MayhemYDG**
|
||||
- Add messages for WebM playback errors.
|
||||
- Pause WebM videos off screen for better performance.
|
||||
|
||||
**Zixaphir**
|
||||
- Code cleanup.
|
||||
|
||||
**ccd0**
|
||||
- Improve error handling and implement WebM decoding error messages for hover and gallery features.
|
||||
- Re-enable autoplay for WebM videos expanded by `Expand All Images` (disabled in [v1.7.3](#v173) due to performance issues). Videos will still only be expanded by EAI if the `Expand Videos` option is checked.
|
||||
- When an image or video is expanded, scroll so that as much of the image/video is on screen as possible. This can be disabled by unchecking `Scroll into view` in the `Image Expansion` menu.
|
||||
- Various image expansion related bugfixes.
|
||||
- `Page Count in Stats` is now enabled by default.
|
||||
|
||||
### v1.8.7.2
|
||||
*2014-08-04*
|
||||
|
||||
|
||||
2
LICENSE
2
LICENSE
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* 4chan X - Version 1.8.7.2 - 2014-08-04
|
||||
* 4chan X - Version 1.8.8.3 - 2014-08-02
|
||||
*
|
||||
* Licensed under the MIT license.
|
||||
* https://github.com/ccd0/4chan-x/blob/master/LICENSE
|
||||
|
||||
12
README.md
12
README.md
@ -4,13 +4,15 @@ If you're looking for a maintained fork of OneeChan, try
|
||||
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.
|
||||
Install [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/versions/2.1), then click the link above to install.
|
||||
|
||||
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).
|
||||
**Important**: If you are using Greasemonkey 2.0, you may be subject to a [bug](https://github.com/greasemonkey/greasemonkey/issues/1938) which prevents Greasemonkey from updating 4chan X and other userscripts to the latest version. You should update to [Greasemonkey 2.1](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/versions/2.1), in which the bug is fixed. After doing so, you may want to update 4chan X manually since it is likely to be out of date.
|
||||
|
||||
Greasemonkey 2.1 also fixes [another bug](https://github.com/greasemonkey/greasemonkey/issues/1937) that can break posting of images from URLs and downloading images with the original filename.
|
||||
|
||||
### 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.
|
||||
The combination of Firefox 29 and Greasemonkey 2.0+ causes 4chan X not to work.
|
||||
If you have this problem, you should [upgrade 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)).
|
||||
|
||||
## [Install](https://ccd0.github.io/4chan-x/builds/4chan-X.crx) (Chromium)
|
||||
@ -20,7 +22,7 @@ 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.
|
||||
Some recent versions of Chromium/Chrome (38.0.2085 - 38.0.2103) suffer from a (now fixed) [bug](https://crbug.com/393686) that prevents extensions from making HTTP requests if more than one extension is enabled. This breaks, among other things, thread updating, quick reply, and, when `JSON Navigation` is enabled, the thread index. If you are experiencing this, try upgrading/downgrading to an unaffected version of Chromium/Chrome, disabling your other extensions, or using 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.
|
||||
|
||||
Binary file not shown.
@ -1,6 +1,6 @@
|
||||
// ==UserScript==
|
||||
// @name 4chan X
|
||||
// @version 1.8.7.2
|
||||
// @version 1.8.8.3
|
||||
// @minGMVer 1.14
|
||||
// @minFFVer 26
|
||||
// @namespace 4chan-X
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1,6 +1,6 @@
|
||||
// ==UserScript==
|
||||
// @name 4chan X
|
||||
// @version 1.8.7.2
|
||||
// @version 1.8.8.3
|
||||
// @minGMVer 1.14
|
||||
// @minFFVer 26
|
||||
// @namespace 4chan-X
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -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.7.2' />
|
||||
<updatecheck codebase='https://ccd0.github.io/4chan-x/builds/4chan-X-beta.crx' version='1.8.8.3' />
|
||||
</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.7.2' />
|
||||
<updatecheck codebase='https://ccd0.github.io/4chan-x/builds/4chan-X.crx' version='1.8.8.3' />
|
||||
</app>
|
||||
</gupdate>
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "4chan-X",
|
||||
"version": "1.8.7.2",
|
||||
"version": "1.8.8.3",
|
||||
"description": "Cross-browser userscript for maximum lurking on 4chan.",
|
||||
"meta": {
|
||||
"name": "4chan X",
|
||||
|
||||
@ -276,7 +276,7 @@ Config =
|
||||
'Display reply and image count.'
|
||||
]
|
||||
'Page Count in Stats': [
|
||||
false
|
||||
true
|
||||
'Display the page count in the thread stats as well.'
|
||||
]
|
||||
'Updater and Stats in Header': [
|
||||
@ -427,6 +427,10 @@ Config =
|
||||
false
|
||||
''
|
||||
]
|
||||
'Scroll into view': [
|
||||
true
|
||||
'Scroll down when expanding images to bring the full image into view.'
|
||||
]
|
||||
'Expand spoilers': [
|
||||
true
|
||||
'Expand all images along with spoilers.'
|
||||
|
||||
@ -86,7 +86,7 @@ Header =
|
||||
# Wait for #boardNavMobile instead of #boardNavDesktop,
|
||||
# it might be incomplete otherwise.
|
||||
$.asap (-> $.id('boardNavMobile') or d.readyState isnt 'loading'), Header.setBoardList
|
||||
$.prepend d.body, @bar
|
||||
$.prepend d.body, [@bar, @noticesRoot]
|
||||
$.add d.body, Header.hover
|
||||
@setBarPosition Conf['Bottom Header']
|
||||
@
|
||||
@ -142,7 +142,7 @@ Header =
|
||||
|
||||
$.rm $ '#navtopright', fullBoardList
|
||||
$.add boardList, fullBoardList
|
||||
$.add Header.bar, [Header.boardList, Header.shortcuts, Header.noticesRoot, Header.toggle]
|
||||
$.add Header.bar, [Header.boardList, Header.shortcuts, Header.toggle]
|
||||
|
||||
Header.setCustomNav Conf['Custom Board Navigation']
|
||||
Header.generateBoardList Conf['boardnav'].replace /(\r\n|\n|\r)/g, ' '
|
||||
@ -308,18 +308,15 @@ Header =
|
||||
'bottom-header'
|
||||
'top-header'
|
||||
'bottom'
|
||||
'after'
|
||||
] else [
|
||||
'top-header'
|
||||
'bottom-header'
|
||||
'top'
|
||||
'add'
|
||||
]
|
||||
|
||||
$.addClass doc, args[0]
|
||||
$.rmClass doc, args[1]
|
||||
Header.bar.parentNode.className = args[2]
|
||||
$[args[3]] Header.bar, Header.noticesRoot
|
||||
|
||||
toggleBarPosition: ->
|
||||
$.cb.checked.call @
|
||||
@ -406,6 +403,9 @@ Header =
|
||||
headRect = Header.toggle.getBoundingClientRect()
|
||||
bottom -= clientHeight - headRect.bottom + headRect.height
|
||||
bottom
|
||||
isNodeVisible: (node) ->
|
||||
{height} = node.getBoundingClientRect()
|
||||
Header.getTopOf(node) + height >= 0 and Header.getBottomOf(node) + height >= 0
|
||||
isHidden: ->
|
||||
{top} = Header.bar.getBoundingClientRect()
|
||||
if Conf['Bottom header']
|
||||
|
||||
@ -57,6 +57,9 @@ Main =
|
||||
Redirect.navigate URL
|
||||
else if Conf['Loop in New Tab'] and video = $ 'video'
|
||||
video.loop = true
|
||||
video.controls = false
|
||||
video.play()
|
||||
ImageCommon.addControls video
|
||||
return
|
||||
|
||||
if Conf['Normalize URL'] and g.VIEW is 'thread'
|
||||
|
||||
@ -718,6 +718,9 @@ span.hide-announcement {
|
||||
:root.gecko.fit-width .full-image:not(#ihover) {
|
||||
width: 100%;
|
||||
}
|
||||
.fileThumb > .warning {
|
||||
clear: both;
|
||||
}
|
||||
#ihover {
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
|
||||
@ -58,19 +58,14 @@ class Clone extends Post
|
||||
@file.text = file.firstElementChild
|
||||
@file.thumb = $ 'img[data-md5]', file
|
||||
@file.fullImage = $ '.full-image', file
|
||||
@file.videoControls = $ '.video-controls', @file.text
|
||||
|
||||
# Contract thumbnails in quote preview
|
||||
if contractThumb
|
||||
$.rmClass root, 'expanded-image'
|
||||
$.rmClass @file.thumb, 'expanding'
|
||||
@file.isExpanded = $.hasClass root, 'expanded-image'
|
||||
ImageExpand.contract @ if contractThumb
|
||||
|
||||
# Remove any #ihover ID
|
||||
@file.fullImage?.removeAttribute 'id'
|
||||
|
||||
# Remove video controls.
|
||||
($ '.video-controls', @file.text)?.remove()
|
||||
|
||||
@isDead = true if origin.isDead
|
||||
@isClone = true
|
||||
root.dataset.clone = origin.clones.push(@) - 1
|
||||
|
||||
@ -156,9 +156,12 @@ Gallery =
|
||||
file.src = name.href = @href
|
||||
|
||||
$.extend file.dataset, @dataset
|
||||
nodes.current.pause?()
|
||||
nodes.current.pause?() unless nodes.current.error
|
||||
$.replace nodes.current, file
|
||||
Video.configure file if elType is 'video'
|
||||
if elType is 'video'
|
||||
file.loop = true
|
||||
file.play() if Conf['Autoplay']
|
||||
ImageCommon.addControls file if Conf['Show Controls']
|
||||
nodes.count.textContent = +@dataset.id + 1
|
||||
nodes.current = file
|
||||
nodes.frame.scrollTop = 0
|
||||
@ -178,33 +181,13 @@ Gallery =
|
||||
Gallery.build @
|
||||
|
||||
error: (file, thumb) ->
|
||||
post = g.posts[file.dataset.post]
|
||||
|
||||
src = file.src.split '/'
|
||||
if src[2] is 'i.4cdn.org'
|
||||
URL = Redirect.to 'file',
|
||||
boardID: src[3]
|
||||
filename: src[src.length - 1]
|
||||
if URL
|
||||
thumb.href = URL
|
||||
if URL and (/^https:\/\//.test(URL) or location.protocol is 'http:')
|
||||
return unless Gallery.nodes.current is file
|
||||
file.src = URL
|
||||
return
|
||||
if g.DEAD or post.isDead or post.file.isDead
|
||||
return
|
||||
|
||||
# XXX CORS for i.4cdn.org WHEN?
|
||||
$.ajax "//a.4cdn.org/#{post.board}/thread/#{post.thread}.json", onload: ->
|
||||
return if @status isnt 200
|
||||
i = 0
|
||||
{posts} = @response
|
||||
while postObj = posts[i++]
|
||||
break if postObj.no is post.ID
|
||||
unless postObj.no
|
||||
return post.kill()
|
||||
if postObj.filedeleted
|
||||
post.kill true
|
||||
if file.error?.code is MediaError.MEDIA_ERR_DECODE
|
||||
return new Notice 'error', 'Corrupt or unplayable video', 30
|
||||
return unless file.src.split('/')[2] is 'i.4cdn.org'
|
||||
ImageCommon.error file, g.posts[file.dataset.post], null, (URL) ->
|
||||
return unless URL
|
||||
thumb.href = URL
|
||||
file.src = URL if Gallery.nodes.current is file
|
||||
|
||||
prev: ->
|
||||
Gallery.cb.open.call(
|
||||
@ -235,7 +218,7 @@ Gallery =
|
||||
Gallery.cb.cleanupTimer()
|
||||
{current} = Gallery.nodes
|
||||
isVideo = current.nodeName is 'VIDEO'
|
||||
Video.start current if isVideo
|
||||
current.play() if isVideo
|
||||
if (if isVideo then current.readyState > 4 else current.complete) or current.nodeName is 'IFRAME'
|
||||
Gallery.cb.startTimer()
|
||||
else
|
||||
|
||||
62
src/Images/ImageCommon.coffee
Normal file
62
src/Images/ImageCommon.coffee
Normal file
@ -0,0 +1,62 @@
|
||||
ImageCommon =
|
||||
decodeError: (file, post) ->
|
||||
return false unless file.error?.code is MediaError.MEDIA_ERR_DECODE
|
||||
unless message = $ '.warning', post.file.thumb.parentNode
|
||||
message = $.el 'div', className: 'warning'
|
||||
$.after post.file.thumb, message
|
||||
message.textContent = 'Error: Corrupt or unplayable video'
|
||||
return true
|
||||
|
||||
error: (file, post, delay, cb) ->
|
||||
src = post.file.URL.split '/'
|
||||
URL = Redirect.to 'file',
|
||||
boardID: post.board.ID
|
||||
filename: src[src.length - 1]
|
||||
unless URL and (/^https:\/\//.test(URL) or location.protocol is 'http:')
|
||||
URL = null
|
||||
|
||||
return cb URL if (post.isDead or post.file.isDead) and file.src.split('/')[2] is 'i.4cdn.org'
|
||||
|
||||
timeoutID = setTimeout (-> cb URL), delay if delay?
|
||||
return if post.isDead or post.file.isDead
|
||||
redirect = ->
|
||||
if file.src.split('/')[2] is 'i.4cdn.org'
|
||||
clearTimeout timeoutID if delay?
|
||||
cb URL
|
||||
|
||||
<% if (type === 'crx') { %>
|
||||
$.ajax post.file.URL,
|
||||
onloadend: ->
|
||||
if @status is 200
|
||||
URL = post.file.URL
|
||||
else
|
||||
post.kill true if @status is 404
|
||||
redirect()
|
||||
,
|
||||
type: 'head'
|
||||
<% } else { %>
|
||||
# XXX CORS for i.4cdn.org WHEN?
|
||||
$.ajax "//a.4cdn.org/#{post.board}/thread/#{post.thread}.json", onload: ->
|
||||
post.kill() if @status is 404
|
||||
return redirect() if @status isnt 200
|
||||
for postObj in @response.posts
|
||||
break if postObj.no is post.ID
|
||||
if postObj.no isnt post.ID
|
||||
post.kill()
|
||||
redirect()
|
||||
else if postObj.filedeleted
|
||||
post.kill true
|
||||
redirect()
|
||||
else
|
||||
URL = post.file.URL
|
||||
<% } %>
|
||||
|
||||
# Add controls, but not until the mouse is moved over the video.
|
||||
addControls: (video) ->
|
||||
handler = ->
|
||||
$.off video, 'mouseover', handler
|
||||
# Hacky workaround for Firefox forever-loading bug for very short videos
|
||||
t = new Date().getTime()
|
||||
$.asap (-> chrome? or (video.readyState >= 3 and video.currentTime <= Math.max 0.1, (video.duration - 0.5)) or new Date().getTime() >= t + 1000), ->
|
||||
video.controls = true
|
||||
$.on video, 'mouseover', handler
|
||||
@ -7,35 +7,37 @@ ImageExpand =
|
||||
textContent: 'EAI'
|
||||
title: 'Expand All Images'
|
||||
href: 'javascript:;'
|
||||
$.on @EAI, 'click', ImageExpand.cb.toggleAll
|
||||
$.on @EAI, 'click', @cb.toggleAll
|
||||
Header.addShortcut @EAI, 3
|
||||
$.on d, 'scroll visibilitychange', @cb.playVideos
|
||||
|
||||
Post.callbacks.push
|
||||
name: 'Image Expansion'
|
||||
cb: @node
|
||||
|
||||
node: ->
|
||||
return unless @file?.isImage or @file?.isVideo
|
||||
{thumb} = @file
|
||||
$.on thumb.parentNode, 'click', ImageExpand.cb.toggle
|
||||
if @isClone and $.hasClass thumb, 'expanding'
|
||||
# If we clone a post where the image is still loading,
|
||||
# make it loading in the clone too.
|
||||
ImageExpand.contract @
|
||||
ImageExpand.expand @
|
||||
else if @isClone and @file.isExpanded and @file.isVideo
|
||||
clone = @
|
||||
ImageExpand.setupVideoControls clone
|
||||
unless clone.origin.file.fullImage.paused
|
||||
$.queueTask -> Video.start clone.file.fullImage
|
||||
return unless @file and (@file.isImage or @file.isVideo)
|
||||
$.on @file.thumb.parentNode, 'click', ImageExpand.cb.toggle
|
||||
if @isClone
|
||||
if @file.isExpanding
|
||||
# If we clone a post where the image is still loading,
|
||||
# make it loading in the clone too.
|
||||
ImageExpand.contract @
|
||||
ImageExpand.expand @
|
||||
else if @file.isExpanded and @file.isVideo
|
||||
ImageExpand.setupVideoCB @
|
||||
ImageExpand.setupVideo @, !@origin.file.fullImage?.paused or @origin.file.wasPlaying, @file.fullImage.controls
|
||||
else if ImageExpand.on and !@isHidden and
|
||||
(Conf['Expand spoilers'] or !@file.isSpoiler) and
|
||||
(Conf['Expand videos'] or !@file.isVideo)
|
||||
ImageExpand.expand @, null, true
|
||||
ImageExpand.expand @
|
||||
|
||||
cb:
|
||||
toggle: (e) ->
|
||||
return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0
|
||||
post = Get.postFromNode @
|
||||
return if post.file.isExpanded and post.file.fullImage?.controls
|
||||
{file} = post
|
||||
return if file.isExpanded and file.isVideo and file.fullImage.controls
|
||||
e.preventDefault()
|
||||
ImageExpand.toggle post
|
||||
|
||||
@ -46,34 +48,48 @@ ImageExpand =
|
||||
return unless file and (file.isImage or file.isVideo) and doc.contains post.nodes.root
|
||||
if ImageExpand.on and
|
||||
(!Conf['Expand spoilers'] and file.isSpoiler or
|
||||
!Conf['Expand videos'] and file.isVideo or
|
||||
Conf['Expand from here'] and Header.getTopOf(file.thumb) < 0)
|
||||
!Conf['Expand videos'] and file.isVideo or
|
||||
Conf['Expand from here'] and Header.getTopOf(file.thumb) < 0)
|
||||
return
|
||||
$.queueTask func, post
|
||||
|
||||
if ImageExpand.on = $.hasClass ImageExpand.EAI, 'expand-all-shortcut'
|
||||
ImageExpand.EAI.className = 'contract-all-shortcut fa fa-compress'
|
||||
ImageExpand.EAI.title = 'Contract All Images'
|
||||
func = (post) -> ImageExpand.expand post, null, true
|
||||
func = (post) -> ImageExpand.expand post
|
||||
else
|
||||
ImageExpand.EAI.className = 'expand-all-shortcut fa fa-expand'
|
||||
ImageExpand.EAI.title = 'Expand All Images'
|
||||
func = ImageExpand.contract
|
||||
|
||||
g.posts.forEach (post) ->
|
||||
toggle post
|
||||
toggle post for post in post.clones
|
||||
toggle post for post in [post, post.clones...]
|
||||
return
|
||||
|
||||
playVideos: (e) ->
|
||||
g.posts.forEach (post) ->
|
||||
return unless post.file and post.file.isVideo and post.file.isExpanded
|
||||
video = post.file.fullImage
|
||||
visible = !d.hidden and Header.isNodeVisible video
|
||||
if visible and post.file.wasPlaying
|
||||
delete post.file.wasPlaying
|
||||
video.play()
|
||||
else if !visible and !video.paused
|
||||
post.file.wasPlaying = true
|
||||
video.pause()
|
||||
return
|
||||
|
||||
setFitness: ->
|
||||
(if @checked then $.addClass else $.rmClass) doc, @name.toLowerCase().replace /\s+/g, '-'
|
||||
|
||||
toggle: (post) ->
|
||||
{thumb} = post.file
|
||||
unless post.file.isExpanded or $.hasClass thumb, 'expanding'
|
||||
unless post.file.isExpanding or post.file.isExpanded
|
||||
post.file.scrollIntoView = Conf['Scroll into view']
|
||||
ImageExpand.expand post
|
||||
return
|
||||
|
||||
ImageExpand.contract post
|
||||
|
||||
# Scroll back to the thumbnail when contracting the image
|
||||
# to avoid being left miles away from the relevant post.
|
||||
{root} = post.nodes
|
||||
@ -96,99 +112,134 @@ ImageExpand =
|
||||
if left < 0
|
||||
x = -window.scrollX
|
||||
window.scrollBy x, y if x or y
|
||||
ImageExpand.contract post
|
||||
|
||||
contract: (post) ->
|
||||
if post.file.isVideo and video = post.file.fullImage
|
||||
video.pause()
|
||||
TrashQueue.add video, post
|
||||
post.file.thumb.parentNode.href = video.src
|
||||
post.file.thumb.parentNode.target = '_blank'
|
||||
for eventName, cb of ImageExpand.videoCB
|
||||
$.off video, eventName, cb
|
||||
$.rm post.file.videoControls
|
||||
delete post.file.videoControls
|
||||
$.rmClass post.nodes.root, 'expanded-image'
|
||||
$.rmClass post.file.thumb, 'expanding'
|
||||
post.file.isExpanded = false
|
||||
{file} = post
|
||||
|
||||
expand: (post, src, disableAutoplay) ->
|
||||
{bottom} = post.nodes.root.getBoundingClientRect()
|
||||
oldHeight = d.body.clientHeight
|
||||
|
||||
$.rmClass post.nodes.root, 'expanded-image'
|
||||
$.rmClass file.thumb, 'expanding'
|
||||
$.rm file.videoControls if file.videoControls
|
||||
file.thumb.parentNode.href = file.URL
|
||||
file.thumb.parentNode.target = '_blank'
|
||||
for x in ['isExpanding', 'isExpanded', 'videoControls', 'wasPlaying', 'scrollIntoView']
|
||||
delete file[x]
|
||||
|
||||
# Scroll to keep our place in the thread when images are contracted above us.
|
||||
if doc.contains(post.nodes.root) and bottom <= 0
|
||||
window.scrollBy 0, d.body.clientHeight - oldHeight
|
||||
|
||||
if el = file.fullImage
|
||||
$.off el, 'error', ImageExpand.error
|
||||
if file.isVideo
|
||||
el.pause()
|
||||
TrashQueue.add el, post
|
||||
for eventName, cb of ImageExpand.videoCB
|
||||
$.off el, eventName, cb
|
||||
return
|
||||
|
||||
expand: (post, src) ->
|
||||
# Do not expand images of hidden/filtered replies, or already expanded pictures.
|
||||
{thumb, isVideo} = post.file
|
||||
return if post.isHidden or post.file.isExpanded or $.hasClass thumb, 'expanding'
|
||||
{file} = post
|
||||
{thumb, isVideo} = file
|
||||
return if post.isHidden or file.isExpanding or file.isExpanded
|
||||
|
||||
$.addClass thumb, 'expanding'
|
||||
if el = post.file.fullImage
|
||||
file.isExpanding = true
|
||||
|
||||
el = file.fullImage or $.el (if isVideo then 'video' else 'img'), className: 'full-image'
|
||||
$.on el, 'error', ImageExpand.error
|
||||
if file.fullImage
|
||||
# Expand already-loaded/ing picture.
|
||||
TrashQueue.remove el
|
||||
else
|
||||
el = post.file.fullImage = $.el (if isVideo then 'video' else 'img'),
|
||||
className: 'full-image'
|
||||
$.on el, 'error', ImageExpand.error
|
||||
el.src = src or post.file.URL
|
||||
$.after thumb, el unless el is thumb.nextSibling
|
||||
$.asap (-> if isVideo then el.videoHeight else el.naturalHeight), ->
|
||||
ImageExpand.completeExpand post, disableAutoplay
|
||||
el.src = src or file.URL
|
||||
$.after thumb, el
|
||||
file.fullImage = el
|
||||
|
||||
if isVideo
|
||||
# add contract link to file info
|
||||
if Conf['Show Controls'] and !file.videoControls
|
||||
file.videoControls = ImageExpand.videoControls.cloneNode true
|
||||
$.add file.text, file.videoControls
|
||||
|
||||
# disable link to file so native controls can work
|
||||
thumb.parentNode.removeAttribute 'href'
|
||||
thumb.parentNode.removeAttribute 'target'
|
||||
|
||||
el.loop = true
|
||||
ImageExpand.setupVideoCB post
|
||||
|
||||
if !isVideo
|
||||
$.asap (-> el.naturalHeight), -> ImageExpand.completeExpand post
|
||||
else if el.readyState >= el.HAVE_CURRENT_DATA
|
||||
ImageExpand.completeExpand post
|
||||
else
|
||||
$.on el, 'loadeddata', -> ImageExpand.completeExpand post
|
||||
|
||||
completeExpand: (post) ->
|
||||
{file} = post
|
||||
return unless file.isExpanding # contracted before the image loaded
|
||||
|
||||
completeExpand: (post, disableAutoplay) ->
|
||||
{thumb} = post.file
|
||||
return unless $.hasClass thumb, 'expanding' # contracted before the image loaded
|
||||
unless post.nodes.root.parentNode
|
||||
# Image might start/finish loading before the post is inserted.
|
||||
# Don't scroll when it's expanded in a QP for example.
|
||||
ImageExpand.completeExpand2 post
|
||||
return
|
||||
{bottom} = post.nodes.root.getBoundingClientRect()
|
||||
$.queueTask ->
|
||||
ImageExpand.completeExpand2 post, disableAutoplay
|
||||
return unless bottom <= 0
|
||||
window.scrollBy 0, post.nodes.root.getBoundingClientRect().bottom - bottom
|
||||
oldHeight = d.body.clientHeight
|
||||
|
||||
completeExpand2: (post, disableAutoplay) ->
|
||||
{thumb} = post.file
|
||||
$.addClass post.nodes.root, 'expanded-image'
|
||||
$.rmClass post.file.thumb, 'expanding'
|
||||
post.file.isExpanded = true
|
||||
if post.file.isVideo
|
||||
ImageExpand.setupVideoControls post
|
||||
Video.configure post.file.fullImage, disableAutoplay
|
||||
$.rmClass file.thumb, 'expanding'
|
||||
file.isExpanded = true
|
||||
delete file.isExpanding
|
||||
|
||||
videoCB:
|
||||
click: (e) ->
|
||||
# Scroll to keep our place in the thread when images are expanded above us.
|
||||
if doc.contains(post.nodes.root) and bottom <= 0
|
||||
window.scrollBy 0, d.body.clientHeight - oldHeight
|
||||
|
||||
# Scroll to display full image.
|
||||
if file.scrollIntoView
|
||||
delete file.scrollIntoView
|
||||
imageBottom = Header.getBottomOf(file.fullImage) - 25
|
||||
if imageBottom < 0
|
||||
window.scrollBy 0, Math.min(-imageBottom, Header.getTopOf file.fullImage)
|
||||
|
||||
if file.isVideo
|
||||
ImageExpand.setupVideo post, Conf['Autoplay'], Conf['Show Controls']
|
||||
|
||||
setupVideo: (post, playing, controls) ->
|
||||
{fullImage} = post.file
|
||||
unless playing
|
||||
fullImage.controls = controls
|
||||
return
|
||||
fullImage.controls = false
|
||||
$.asap (=> doc.contains fullImage), =>
|
||||
if !d.hidden and Header.isNodeVisible fullImage
|
||||
fullImage.play()
|
||||
else
|
||||
post.file.wasPlaying = true
|
||||
if controls
|
||||
ImageCommon.addControls fullImage
|
||||
|
||||
videoControls: $.el 'span',
|
||||
className: 'video-controls'
|
||||
innerHTML: '\u00A0<a href="javascript:;" title="You can also contract the video by dragging it to the left.">contract</a>'
|
||||
|
||||
videoCB: do ->
|
||||
# dragging to the left contracts the video
|
||||
mousedown = false
|
||||
mouseover: -> mousedown = false
|
||||
mousedown: (e) -> mousedown = true if e.button is 0
|
||||
mouseup: (e) -> mousedown = false if e.button is 0
|
||||
mouseout: (e) -> ImageExpand.toggle(Get.postFromNode @) if mousedown and e.clientX <= @getBoundingClientRect().left
|
||||
click: (e) ->
|
||||
if @paused and not @controls
|
||||
@play()
|
||||
e.stopPropagation()
|
||||
|
||||
# dragging to the left contracts the video
|
||||
mousedown: (e) -> @dataset.mousedown = 'true' if e.button is 0
|
||||
mouseup: (e) -> @dataset.mousedown = 'false' if e.button is 0
|
||||
mouseover: (e) -> @dataset.mousedown = 'false'
|
||||
mouseout: (e) ->
|
||||
if @dataset.mousedown is 'true' and e.clientX <= @getBoundingClientRect().left
|
||||
ImageExpand.contract (Get.postFromNode @)
|
||||
|
||||
setupVideoControls: (post) ->
|
||||
{file} = post
|
||||
video = file.fullImage
|
||||
|
||||
# disable link to file so native controls can work
|
||||
file.thumb.parentNode.removeAttribute 'href'
|
||||
file.thumb.parentNode.removeAttribute 'target'
|
||||
|
||||
# setup callbacks on video element
|
||||
video.dataset.mousedown = 'false'
|
||||
$.on video, eventName, cb for eventName, cb of ImageExpand.videoCB
|
||||
|
||||
# setup controls in file info
|
||||
file.videoControls = $.el 'span',
|
||||
className: 'video-controls'
|
||||
if Conf['Show Controls']
|
||||
contract = $.el 'a',
|
||||
textContent: 'contract'
|
||||
href: 'javascript:;'
|
||||
title: 'You can also contract the video by dragging it to the left.'
|
||||
$.on contract, 'click', (e) -> ImageExpand.contract post
|
||||
$.add file.videoControls, [$.tn('\u00A0'), contract]
|
||||
$.add file.text, file.videoControls
|
||||
setupVideoCB: (post) ->
|
||||
for eventName, cb of ImageExpand.videoCB
|
||||
$.on post.file.fullImage, eventName, cb
|
||||
if post.file.videoControls
|
||||
$.on post.file.videoControls.firstElementChild, 'click', -> ImageExpand.toggle post
|
||||
|
||||
error: ->
|
||||
post = Get.postFromNode @
|
||||
@ -197,44 +248,17 @@ ImageExpand =
|
||||
# Images can error:
|
||||
# - before the image started loading.
|
||||
# - after the image started loading.
|
||||
unless $.hasClass(post.file.thumb, 'expanding') or $.hasClass post.nodes.root, 'expanded-image'
|
||||
# Don't try to re-expend if it was already contracted.
|
||||
return
|
||||
ImageExpand.contract post
|
||||
|
||||
src = @src.split '/'
|
||||
if src[2] is 'i.4cdn.org'
|
||||
URL = Redirect.to 'file',
|
||||
boardID: src[3]
|
||||
filename: src[src.length - 1]
|
||||
if URL and (/^https:\/\//.test(URL) or location.protocol is 'http:')
|
||||
setTimeout ImageExpand.expand, 10000, post, URL
|
||||
return
|
||||
if g.DEAD or post.isDead or post.file.isDead
|
||||
return
|
||||
|
||||
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?
|
||||
$.ajax "//a.4cdn.org/#{post.board}/thread/#{post.thread}.json", onload: ->
|
||||
return if @status isnt 200
|
||||
for postObj in @response.posts
|
||||
break if postObj.no is post.ID
|
||||
if postObj.no isnt post.ID
|
||||
clearTimeout timeoutID
|
||||
post.kill()
|
||||
else if postObj.filedeleted
|
||||
clearTimeout timeoutID
|
||||
post.kill true
|
||||
<% } %>
|
||||
# Don't try to re-expand if it was already contracted.
|
||||
return unless post.file.isExpanding or post.file.isExpanded
|
||||
if ImageCommon.decodeError @, post
|
||||
return ImageExpand.contract post
|
||||
# Don't autoretry images from the archive.
|
||||
unless @src.split('/')[2] is 'i.4cdn.org'
|
||||
return ImageExpand.contract post
|
||||
ImageCommon.error @, post, 10 * $.SECOND, (URL) ->
|
||||
if post.file.isExpanding or post.file.isExpanded
|
||||
ImageExpand.contract post
|
||||
ImageExpand.expand post, URL if URL
|
||||
|
||||
menu:
|
||||
init: ->
|
||||
|
||||
@ -12,6 +12,7 @@ ImageHover =
|
||||
post = Get.postFromNode @
|
||||
{file} = post
|
||||
{isVideo} = file
|
||||
return if post.file.isExpanding or post.file.isExpanded
|
||||
if el = file.fullImage
|
||||
el.id = 'ihover'
|
||||
TrashQueue.remove el
|
||||
@ -19,12 +20,11 @@ ImageHover =
|
||||
file.fullImage = el = $.el (if isVideo then 'video' else 'img'),
|
||||
className: 'full-image'
|
||||
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.loop = true
|
||||
el.controls = false
|
||||
el.play() if Conf['Autoplay']
|
||||
UI.hover
|
||||
@ -35,45 +35,19 @@ ImageHover =
|
||||
asapTest: -> (if isVideo then el.readyState >= el.HAVE_CURRENT_DATA else el.naturalHeight)
|
||||
noRemove: true
|
||||
cb: ->
|
||||
$.off el, 'error', ImageHover.error
|
||||
if isVideo
|
||||
el.pause()
|
||||
TrashQueue.add el, post
|
||||
el.removeAttribute 'id'
|
||||
error: ->
|
||||
return unless doc.contains @
|
||||
post = g.posts[@dataset.fullID]
|
||||
|
||||
src = @src.split '/'
|
||||
if src[2] is 'i.4cdn.org'
|
||||
URL = Redirect.to 'file',
|
||||
boardID: src[3]
|
||||
filename: src[src.length - 1].replace /\?.+$/, ''
|
||||
if URL and (/^https:\/\//.test(URL) or location.protocol is 'http:')
|
||||
@src = URL
|
||||
return
|
||||
if g.DEAD or post.isDead or post.file.isDead
|
||||
return
|
||||
|
||||
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?
|
||||
$.ajax "//a.4cdn.org/#{post.board}/thread/#{post.thread}.json", onload: ->
|
||||
return if @status isnt 200
|
||||
for postObj in @response.posts
|
||||
break if postObj.no is post.ID
|
||||
if postObj.no isnt post.ID
|
||||
clearTimeout timeoutID
|
||||
post.kill()
|
||||
else if postObj.filedeleted
|
||||
clearTimeout timeoutID
|
||||
post.kill true
|
||||
<% } %>
|
||||
post = Get.postFromNode @
|
||||
return if post.file.isExpanding or post.file.isExpanded
|
||||
if @id is 'ihover' # still hovering
|
||||
return if ImageCommon.decodeError @, post
|
||||
ImageCommon.error @, post, 3 * $.SECOND, (URL) =>
|
||||
if URL
|
||||
@src = URL + if @src is URL then '?' + Date.now() else ''
|
||||
else
|
||||
$.off @, 'error', ImageHover.error
|
||||
$.rm @
|
||||
delete post.file.fullImage
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
Video =
|
||||
configure: (video, disableAutoplay) ->
|
||||
video.loop = true
|
||||
video.controls = Conf['Show Controls']
|
||||
video.autoplay = false
|
||||
if Conf['Autoplay'] and not disableAutoplay
|
||||
Video.start video
|
||||
else
|
||||
video.pause()
|
||||
|
||||
start: (video) ->
|
||||
return unless video.paused
|
||||
{controls} = video
|
||||
video.controls = false
|
||||
video.play()
|
||||
# Hacky workaround for Firefox forever-loading bug for very short videos
|
||||
if controls
|
||||
$.asap (-> (video.readyState >= 3 and video.currentTime <= Math.max 0.1, (video.duration - 0.5)) or !d.contains video), ->
|
||||
video.controls = true
|
||||
, 500
|
||||
|
||||
@ -32,7 +32,8 @@ CatalogLinks =
|
||||
for a in $$ """#board-list a:not(.catalog), #boardNavDesktopFoot a"""
|
||||
continue if a.hostname not in ['boards.4chan.org', 'catalog.neet.tv', '4index.gropes.us'] or
|
||||
!(board = a.pathname.split('/')[1]) or
|
||||
board in ['f', 'status', '4chan']
|
||||
board in ['f', 'status', '4chan'] or
|
||||
$.hasClass a, 'external'
|
||||
|
||||
# Href is easier than pathname because then we don't have
|
||||
# conditions where External Catalog has been disabled between switches.
|
||||
|
||||
@ -6,6 +6,6 @@ Dice =
|
||||
cb: @node
|
||||
node: ->
|
||||
return if @isClone or not dicestats = @info.email?.match /dice[+\s](\d+)d(\d+)/
|
||||
# Use the text node directly, as the <b> has two <br>.
|
||||
# Use the text node directly, as the <b> has two <br>. Count dice since 4chan imposes a maximum.
|
||||
roll = $('b', @nodes.comment).firstChild
|
||||
roll.data = "Rolled #{dicestats[1]}d#{dicestats[2]}: #{roll.data.slice 7}"
|
||||
roll.data = "Rolled #{roll.data.split(',').length}d#{dicestats[2]}: #{roll.data.slice 7}"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user