Merge branch 'v3'

Conflicts:
	.gitignore
	CHANGELOG.md
	CONTRIBUTING.md
	LICENSE
	builds/appchan-x.user.js
	builds/crx/manifest.json
	builds/crx/script.js
	latest.js
	package.json
	src/General/Header.coffee
	src/General/Index.coffee
	src/General/Settings.coffee
	src/General/UI.coffee
	src/General/html/Settings/Settings.html
	src/Images/Gallery.coffee
	src/Images/ImageExpand.coffee
	src/Monitoring/ThreadWatcher.coffee
This commit is contained in:
Zixaphir 2014-04-11 00:29:52 -07:00
commit ea47aefd93
46 changed files with 1387 additions and 15752 deletions

7
.gitignore vendored
View File

@ -3,6 +3,11 @@ node_modules/
*.db
tmp-crx/
tmp-userscript/
<<<<<<< HEAD
=======
testbuilds/
builds/4chan-X.zip
>>>>>>> v3
Gruntfile.js
builds/4chan-*
Gruntfile.js
Gruntfile.js

View File

@ -1,3 +1,26 @@
**ccd0**
- `Loop in New Tab` (enabled by default) causes videos opened in a separate tab to loop, and applies your settings for inline expanded videos to them.
- Add WebM support to gallery (currently no controls).
- Add PDF support to gallery, disabled by default. Enable with `PDF in Gallery`.
- Fix behavior of .webm videos expanded within inline quotes.
- Contract thumbnails in quoted previews to avoid crashes caused by videos in quoted previews on some systems.
- Change interface when both `Autoplay` and `Show Controls` are unchecked. In this mode, videos are now activated by clicking on them. The first click expands the video, the second click plays the video, and the third click contracts it.
- Add item `Expand videos` in `Image Expansion` menu, which enables expansion of videos by `Expand All Images`. Disabled by default. Previously videos were expanded.
- Disable autoplay for videos expanded by `Expand All Images`.
- Fix minimum width bug.
**fgts**
- Update archive list.
**Nebukazar**
- `Quote Threading` disabled by default
- Added missing titles to Header icons
**Zixaphir**:
- WebM Thumbnail Replacement. Use at your own risk.
- Bugfixes.
### v2.9.15
*2014-04-05*
@ -187,8 +210,8 @@
**Spittie**
- Check image dimension before uploading
**Vampiricwulf**
- Flash embedding and other Flash features.
## v1.7.0
*2014-04-06*
**Zixaphir**
- Update Custom Navigation legend to reflect index mode changes.

View File

@ -1,5 +1,6 @@
## Reporting bugs and suggestions
<<<<<<< HEAD
Reporting bugs: (note that some of these links refer to Mayhem's 4chan X repo to avoid duplication of information resources. Bugs MUST be filed on [our issue tracker](https://github.com/zixaphir/appchan-x/issues), not Mayhem's)
1. Make sure both your **browser** and **Appchan X** are up to date.<br>
@ -12,6 +13,19 @@ Reporting bugs: (note that some of these links refer to Mayhem's 4chan X repo to
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. Appchan X version, browser variant, browser version, and Greasemonkey version if you are using it.
=======
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/ccd0/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.
>>>>>>> v3
4. Your exported settings. If your settings contains sensible information (e.g. personas), edit the text file manually.
Respect these guidelines:

View File

@ -44,8 +44,7 @@ module.exports = (grunt) ->
meta:
files:
'LICENSE': 'src/General/meta/banner.js',
'latest.js': 'src/General/meta/latest.js'
'LICENSE': 'src/General/meta/banner.js'
crx:
files:

View File

@ -1,5 +1,5 @@
/*
* appchan x - Version 2.9.15 - 2014-04-05
* appchan x - Version 2.9.15 - 2014-04-11
*
* Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE

View File

@ -1,6 +1,6 @@
// ==UserScript==
// @name 4chan X
// @version 1.5.1
// @version 1.7.7
// @minGMVer 1.14
// @minFFVer 26
// @namespace 4chan-X

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -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/crx.crx' version='1.5.1' />
<updatecheck codebase='https://ccd0.github.io/4chan-x/builds/crx.crx' version='1.7.7' />
</app>
</gupdate>

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -1,62 +0,0 @@
function GM_openInTab(_url) {
self.port.emit("GM_openInTab", _url);
return; // Should return the Window object
};
function GM_setValue(_name, _value) {
localStorage[_name] = _value;
return;
};
function GM_getValue(_name, _default) {
if (localStorage[_name] === null && _default === null) return null;
return (localStorage[_name] || _default);
};
function GM_deleteValue(_name) {
localStorage.removeItem(_name);
return;
};
function GM_listValues() {
return Object.keys(localStorage);
};
function GM_setClipboard(_text) {
self.port.emit("GM_setClipboard", _text);
};
//Deprecated
function GM_log(_message) {
console.log(_message);
return;
};
function GM_xmlhttpRequest(_details) {
//Ugly hack? Race condition? Memory leak?
_onload = _details.onload;
_context = _details.context;
self.port.emit("GM_xmlhttpRequest", _details);
};
self.port.on("callback_GM_xmlhttpRequest", function(_response) {
_response.context = _context;
_onload(_response);
});
function GM_addStyle(_css) {
self.port.emit("GM_addStyle", _css);
}
var GM_info = new Object();
GM_info.version = '1.15';
GM_info.scriptWillUpdate = true;
//To do
function GM_registerMenuCommand(_caption, _commandFunc, _accessKey) {
return;
}
self.port.on("load-userscript", function(_script) {
eval(_script);
});

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 B

View File

@ -1,82 +0,0 @@
var data = require("sdk/self").data;
var Request = require("sdk/request").Request;
var tabs = require("sdk/tabs");
var system = require("sdk/system");
if (system.platform !== 'android') {
// Clipboard is not supported on Android
var clipboard = require("sdk/clipboard");
}
var pageMod = require("sdk/page-mod");
pageMod.PageMod({
include: ["*.4chan.org", "*.4cdn.org"],
contentScriptFile: data.url("greaseshim.js"),
contentScriptWhen: "start",
onAttach: function(worker) {
worker.port.emit("load-userscript", data.load("4chan-X.user.js"));
//GM_openInTab
worker.port.on("GM_openInTab", function(url) {
tabs.open(url);
});
//GM_setClipboard
worker.port.on("GM_setClipboard", function(text) {
if (system.platform !== 'android') {
// Clipboard is not supported on Android
clipboard.set(text);
}
});
//GM_xmlhttpRequest
worker.port.on("GM_xmlhttpRequest", function(details) {
request = new Object();
request.url = details.url;
if (details.headers) {
request.headers = details.headers;
if (details.headers["Content-Type"]) {request.contentType = details.headers["Content-Type"]};
};
if (details.data) {request.content = encodeURIComponent(details.data)};
if (details.overrideMimeType) {request.overrideMimeType = details.overrideMimeType};
request.onComplete = function(response) {
response.finalUrl = details.url;
response.responseText = response.text;
for (var headerName in response.headers) {
_string = headerName + ": " + response.headers[headerName] + " \n";
response.responseHeaders += _string;
}
response.readyState = 4;
worker.port.emit("callback_GM_xmlhttpRequest", response);
}
xhr = Request(request);
switch(details.method){
case "GET":
xhr.get();
break;
case "POST":
xhr.post();
break;
case "HEAD":
xhr.head();
break;
case "PUT":
xhr.put();
break;
default: xhr.get();
}
});
//GM_addStyle
worker.port.on("GM_addStyle", function(css) {
tabs.activeTab.attach({
contentScript: "var style = document.createElement('style');" +
"style.type = 'text/css';" +
"style.innerHTML = '" + css + "';" +
"document.head.appendChild(style);"
});
});
}
});

View File

@ -1,9 +0,0 @@
{
"name": "4chanx",
"title": "4chan X",
"id": "72DAF86E-9689-11E3-8BA3-F4B66188709B",
"description": "Adds various features to 4chan.",
"author": "Spittie",
"license": "MIT",
"version": "1.4.1"
}

View File

@ -1,12 +0,0 @@
var main = require("./main");
exports["test main"] = function(assert) {
assert.pass("Unit test running!");
};
exports["test main async"] = function(assert, done) {
assert.pass("async Unit test running!");
done();
};
require("sdk/test").run(exports);

File diff suppressed because it is too large Load Diff

View File

@ -1,128 +0,0 @@
[{
"uid": 0,
"name": "Foolz",
"domain": "archive.foolz.us",
"http": true,
"https": true,
"software": "foolfuuka",
"boards": ["a", "biz", "co", "diy", "gd", "jp", "m", "sci", "sp", "tg", "tv", "v", "vg", "vp", "vr", "wsg"],
"files": ["a", "biz", "gd", "diy", "jp", "m", "sci", "tg", "vg", "vp", "vr", "wsg"]
}, {
"uid": 1,
"name": "NSFW Foolz",
"domain": "nsfw.foolz.us",
"http": true,
"https": true,
"software": "foolfuuka",
"boards": ["u"],
"files": ["u"]
}, {
"uid": 2,
"name": "The Dark Cave",
"domain": "archive.thedarkcave.org",
"http": true,
"https": true,
"software": "foolfuuka",
"boards": ["c", "int", "out", "po"],
"files": ["c", "po"]
}, {
"uid": 3,
"name": "4plebs Archive",
"domain": "archive.4plebs.org",
"http": true,
"https": true,
"software": "foolfuuka",
"boards": ["adv", "hr", "o", "pol", "s4s", "tg", "trv", "tv", "x"],
"files": ["adv", "hr", "o", "pol", "s4s", "tg", "trv", "tv", "x"]
}, {
"uid": 18,
"name": "4plebs Flash Archive",
"domain": "flash.4plebs.org",
"http": true,
"https": true,
"software": "foolfuuka",
"boards": ["f"],
"files": ["f"]
}, {
"uid": 4,
"name": "Nyafuu",
"domain": "archive.nyafuu.org",
"http": true,
"https": true,
"software": "foolfuuka",
"boards": ["c", "e", "w", "wg"],
"files": ["c", "e", "w", "wg"]
}, {
"uid": 5,
"name": "Love is Over",
"domain": "loveisover.me",
"http": true,
"https": true,
"software": "foolfuuka",
"boards": ["d", "i"],
"files": ["d", "i"]
}, {
"uid": 8,
"name": "Rebecca Black Tech",
"domain": "archive.rebeccablacktech.com",
"http": true,
"https": true,
"software": "fuuka",
"boards": ["cgl", "g", "mu", "w"],
"files": ["cgl", "g", "mu", "w"]
}, {
"uid": 9,
"name": "Heinessen",
"domain": "archive.heinessen.com",
"http": true,
"https": false,
"software": "fuuka",
"boards": ["an", "fit", "k", "mlp", "r9k", "toy"],
"files": ["an", "fit", "k", "r9k", "toy"]
}, {
"uid": 10,
"name": "warosu",
"domain": "fuuka.warosu.org",
"http": false,
"https": true,
"software": "fuuka",
"boards": ["3", "biz", "cgl", "ck", "diy", "fa", "g", "ic", "jp", "lit", "sci", "tg", "vr"],
"files": ["3", "biz", "cgl", "ck", "diy", "fa", "ic", "jp", "lit", "sci", "tg", "vr"]
}, {
"uid": 15,
"name": "fgts",
"domain": "fgts.eu",
"http": true,
"https": true,
"software": "foolfuuka",
"boards": ["cm", "hm", "r", "soc", "y"],
"files": ["cm", "hm", "r", "soc", "y"]
}, {
"uid": 16,
"name": "maware",
"domain": "archive.mawa.re",
"http": true,
"https": false,
"software": "foolfuuka",
"boards": ["t"],
"files": ["t"]
}, {
"uid": 17,
"name": "installgentoo.com",
"domain": "chan.installgentoo.com",
"http": true,
"https": false,
"software": "foolfuuka",
"boards": ["g", "t"],
"files": ["g", "t"]
}, {
"uid": 13,
"name": "Foolz Beta",
"domain": "beta.foolz.us",
"http": true,
"https": true,
"withCredentials": true,
"software": "foolfuuka",
"boards": ["a", "biz", "co", "d", "diy", "gd", "jp", "m", "s4s", "sci", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"],
"files": ["a", "biz", "d", "diy", "gd", "jp", "m", "s4s", "sci", "tg", "u", "vg", "vp", "vr", "wsg"]
}]

View File

@ -95,8 +95,8 @@
"http": true,
"https": true,
"software": "foolfuuka",
"boards": ["cm", "hm", "r", "soc", "y"],
"files": ["cm", "hm", "r", "soc", "y"]
"boards": ["cm", "h", "hc", "hm", "r", "s", "soc", "y"],
"files": ["cm", "h", "hc", "hm", "r", "s", "soc", "y"]
}, {
"uid": 16,
"name": "maware",

View File

@ -2,7 +2,7 @@ Config =
main:
'Miscellaneous':
'JSON Navigation' : [
true
false
'Use JSON for loading the Board Index and Threads. Also allows searching and sorting the board index and infinite scolling.'
]
'Catalog Links': [
@ -69,6 +69,10 @@ Config =
false
'Indicate spoilers if Remove Spoilers is enabled, or make the text appear hovered if Remove Spoiler is disabled.'
]
'Show Support Message': [
true
'Warn if your browser is unsupported. 4chan X may not operate correctly on unsupported browser versions.'
]
'Linkification':
'Linkify': [
@ -131,6 +135,10 @@ Config =
true
'Adds a simple and cute image gallery.'
]
'PDF in Gallery': [
false
'Show PDF files in gallery.'
]
'Sauce': [
true
'Add sauce links to images.'
@ -141,15 +149,19 @@ Config =
]
'Replace GIF': [
false
'Replace thumbnail of gifs with its actual image.'
]
'Replace PNG': [
false
'Replace pngs.'
'Replace gif thumbnails with the actual image.'
]
'Replace JPG': [
false
'Replace jpgs.'
'Replace jpg thumbnails with the actual image.'
]
'Replace PNG': [
false
'Replace png thumbnails with the actual image.'
]
'Replace WEBM': [
false
'Replace webm thumbnails with the actual webm video. Probably will degrade browser performance ;)'
]
'Image Prefetching': [
false
@ -165,15 +177,19 @@ Config =
]
'Autoplay': [
true
'Videos begin playing immediately when opened inline.'
'Videos begin playing immediately when opened.'
]
'Show Controls': [
true
'Show native seek and volume controls on videos. Contract videos when dragged to the left.'
'Show controls on videos expanded inline. Turn this off if you want to contract videos by clicking on them.'
]
'Allow Sound': [
true
'Allow sound in inline videos.'
'Allow sound in videos.'
]
'Loop in New Tab': [
true
'Loop videos opened in their own tabs, and apply settings for inline expanded videos to them.'
]
'Menu':
@ -366,6 +382,7 @@ Config =
'Add \'(Cross-thread)\' to cross-threads quotes.'
'Highlights own posts if Quote Markers are enabled.'
]
imageExpansion:
'Fit width': [
false
@ -379,6 +396,10 @@ Config =
true
'Expand all images along with spoilers.'
]
'Expand videos': [
false
'Expand all images also expands videos (no autoplay).'
]
'Expand from here': [
false
'Expand all images only from current position to thread end.'

View File

@ -47,7 +47,7 @@ Get =
# get all their backlinks.
posts.forEach (qPost) ->
if fullID in qPost.quotes
handleQuotes qPost, 'quotelinks'
handleQuotes qPost, 'quotelinks'
# Second:
# If we have quote backlinks:
@ -62,6 +62,7 @@ Get =
quotelinks.filter (quotelink) ->
{boardID, postID} = Get.postDataFromLink quotelink
boardID is post.board.ID and postID is post.ID
postClone: (boardID, threadID, postID, root, context) ->
if post = g.posts["#{boardID}.#{postID}"]
Get.insert post, root, context
@ -80,7 +81,7 @@ Get =
insert: (post, root, context) ->
# Stop here if the container has been removed while loading.
return unless root.parentNode
clone = post.addClone context
clone = post.addClone context, ($.hasClass root, 'dialog')
Clone.callbacks.execute [clone]
# Get rid of the side arrows/stubs.
@ -104,6 +105,7 @@ Get =
$.cache url,
-> Get.archivedPost @, boardID, postID, root, context
,
responseType: 'json'
withCredentials: url.archive.withCredentials
else
$.addClass root, 'warning'

View File

@ -362,6 +362,10 @@ Header =
bottom -= clientHeight - headRect.bottom + headRect.height - 10
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']

View File

@ -171,9 +171,9 @@ Index =
scroll: ->
return if Index.req or Conf['Index Mode'] isnt 'infinite' or (window.scrollY <= doc.scrollHeight - (300 + window.innerHeight)) or g.VIEW is 'thread'
Index.pageNum = (Index.pageNum or Index.getCurrentPage()) + 1 # Avoid having to pushState to keep track of the current page
Index.currentPage = (Index.currentPage or Index.getCurrentPage()) + 1 # Avoid having to pushState to keep track of the current page
return Index.endNotice() if Index.pageNum >= Index.pagesNum
return Index.endNotice() if Index.currentPage >= Index.pagesNum
Index.buildIndex true
@ -419,8 +419,8 @@ Index =
Header.scrollToIfNeeded Index.navLinks
getCurrentPage: ->
if Conf['Index Mode'] is 'infinite' and Index.pageNum
return Index.pageNum
if Conf['Index Mode'] is 'infinite' and Index.currentPage
return Index.currentPage
+window.location.pathname.split('/')[2]
userPageNav: (pageNum) ->
@ -469,8 +469,8 @@ Index =
$.add pagesRoot, nodes
Index.togglePagelist()
setPage: (pageNum) ->
pageNum or= Index.getCurrentPage()
setPage: (pageNum = Index.getCurrentPage()) ->
Index.currentPage = pageNum
maxPageNum = Index.getMaxPageNum()
pagesRoot = $ '.pages', Index.pagelist
# Previous/Next buttons
@ -513,7 +513,7 @@ Index =
return
unless d.readyState is 'loading' or Index.root.parentElement
$.replace $('.board'), Index.root
delete Index.pageNum
Index.currentPage = 0
Index.req?.abort()
Index.notice?.close()
@ -564,10 +564,14 @@ Index =
Navigate.title()
try
pageNum or= 0
if req.status is 200
Index.parse req.response, pageNum
else if req.status is 304
Index.pageNav pageNum or 0
if Index.currentPage is pageNum
Index.buildIndex()
else
Index.pageNav pageNum
catch err
c.error "Index failure: #{err.message}", err.stack
# network error or non-JSON content for example.
@ -588,7 +592,7 @@ Index =
Index.parseThreadList pages
Index.buildThreads()
Index.sort()
if pageNum?
if pageNum? and Index.currentPage isnt pageNum
Index.pageNav pageNum
return
Index.buildIndex()
@ -752,16 +756,12 @@ Index =
buildIndex: (infinite) ->
{sortedThreads} = Index
nodes = []
switch Conf['Index Mode']
when 'paged', 'infinite'
pageNum = Index.getCurrentPage()
if pageNum > Index.getMaxPageNum()
# Go to the last available page if we were past the limit.
Index.pageNav Index.getMaxPageNum()
return
threadsPerPage = Index.getThreadsNumPerPage()
nodes = []
threads = []
i = threadsPerPage * pageNum
max = i + threadsPerPage
@ -777,8 +777,7 @@ Index =
nodes = Index.buildCatalogViews()
else
nodes = []
i = 0
i = 0
while thread = sortedThreads[i++]
nodes.push thread.OP.nodes.root.parentNode, $.el 'hr'
Index.buildReplies thread
@ -799,9 +798,11 @@ Index =
unless Index.searchInput.dataset.searching
Index.searchInput.dataset.searching = 1
Index.pageBeforeSearch = Index.getCurrentPage()
pageNum = 0
Index.setPage pageNum = 0
else
pageNum = Index.getCurrentPage()
unless Conf['Index Mode'] is 'infinite'
pageNum = Index.getCurrentPage()
else
return unless Index.searchInput.dataset.searching
pageNum = Index.pageBeforeSearch
@ -832,7 +833,6 @@ Index =
filtered.push thread if Index.searchMatch thread, keywords
Index.sortedThreads = filtered
searchMatch: (thread, keywords) ->
{info, file} = thread.OP
text = []

View File

@ -83,6 +83,11 @@ Main =
Report.init()
return
when 'i.4cdn.org'
if Conf['Loop in New Tab'] and video = $ 'video'
Video.configure video
$.on video, 'click', ->
if !video.controls
if video.paused then video.play() else video.pause()
$.ready ->
if Conf['404 Redirect'] and d.title in ['4chan - Temporarily Offline', '4chan - 404 Not Found']
Redirect.init()
@ -204,7 +209,7 @@ Main =
<% if (type === 'userscript') { %>
test = $.el 'span'
test.classList.add 'a', 'b'
if test.className isnt 'a b'
if test.className isnt 'a b' and Conf['Show Support Message']
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 '.'

View File

@ -115,11 +115,15 @@ Navigate =
index: ->
delete g.THREADID
Index.cb.toggleCatalogMode() if Conf['Index Mode'] is 'catalog'
QR.posts[0]?.thread = 'new'
thread: ->
$.rmClass doc, 'catalog-mode' if Conf['Index Mode'] is 'catalog'
QR.posts[0]?.thread = g.THREADID
}[view]()
QR.status() # Re-enable the QR in the case of a 404'd thread or something.
g.VIEW = view
updateBoard: (boardID) ->

View File

@ -3,6 +3,7 @@ Settings =
# Appchan X settings link
el = $.el 'a',
className: 'settings-link'
title: 'Appchan X Settings'
href: 'javascript:;'
textContent: 'Settings'
$.on el, 'click', @open

View File

@ -309,7 +309,7 @@ UI = do ->
$.off d, 'mouseup', @up
$.set "#{@id}.position", @style.cssText
hoverstart = ({root, el, latestEvent, endEvents, asapTest, cb, offsetX, offsetY}) ->
hoverstart = ({root, el, latestEvent, endEvents, asapTest, cb, offsetX, offsetY, noRemove}) ->
o = {
root
el
@ -322,6 +322,7 @@ UI = do ->
clientWidth: doc.clientWidth
offsetX: offsetX or 45
offsetY: offsetY or -120
noRemove
}
o.hover = hover.bind o
o.hoverend = hoverend.bind o
@ -338,7 +339,7 @@ UI = do ->
$.on root, 'mousemove', o.hover
<% if (type === 'userscript') { %>
# Workaround for https://github.com/MayhemYDG/4chan-x/issues/377
o.workaround = (e) -> o.hoverend() unless root.contains e.target
o.workaround = (e) -> o.hoverend(e) unless root.contains e.target
$.on doc, 'mousemove', o.workaround
<% } %>

View File

@ -223,7 +223,7 @@ div.center:not(.ad-cnt) {
height: 10px;
position: absolute;
}
:root:not(.autohide) #scroll-marker {
:root #header-bar:not(.autohide) #scroll-marker {
pointer-events: none;
}
#header-bar #scroll-marker {
@ -824,20 +824,26 @@ span.hide-announcement {
/* File */
.fileText:hover .fntrunc,
.fileText:not(:hover) .fnfull,
.expanded-image > .post > .file > .fileThumb > img[data-md5],
:not(.expanded-image) > .post > .file .full-image {
.expanded-image > .post > .file > .fileThumb > video[data-md5],
.expanded-image > .post > .file > .fileThumb > img[data-md5] {
display: none;
}
.full-image:not(#ihover) {
display: none;
}
.expanded-image > .post > .file > .fileThumb > .full-image:not(#ihover) {
display: inline;
}
.expanding {
opacity: .5;
}
:root.fit-height .full-image {
:root.fit-height .full-image:not(#ihover) {
max-height: 100vh;
}
:root.fit-width .full-image {
:root.fit-width .full-image:not(#ihover) {
max-width: 100%;
}
:root.gecko.fit-width .full-image {
:root.gecko.fit-width .full-image:not(#ihover) {
width: 100%;
}
#ihover {
@ -1459,34 +1465,44 @@ div.boardTitle {
/* Flex > Non-Flex child max-width and overflow fix (Firefox only?) */
width: 1%;
}
:root:not(.gal-fit-height) .gal-image {
:root:not(.gal-fit-height):not(.gal-pdf) .gal-image {
overflow-y: scroll !important;
}
:root:not(.gal-fit-width) .gal-image {
:root:not(.gal-fit-width):not(.gal-pdf) .gal-image {
overflow-x: scroll !important;
}
.gal-image a {
margin: auto;
line-height: 0;
}
:root.gal-pdf .gal-image a {
width: 100%;
height: 100%;
}
.gal-fit-width .gal-image video,
.gal-fit-width .gal-image img {
max-width: 100%;
}
.gal-fit-height .gal-image video,
.gal-fit-height .gal-image img {
/*
Chrome doesn't support viewpoint units in calc()
http://bugs.chromium.org/168840
"It looks like the original author of viewport units in WebKit is not coming back to fix this stuff."
Well, fuck.
*/
max-height: 95vh;
max-height: calc(100vh - 25px);
}
.gal-image iframe {
width: 100%;
height: 100%;
}
.gal-buttons {
font-size: 2em;
margin-right: 10px;
margin-right: 3px;
padding-left: 7px;
padding-right: 7px;
top: 5px;
}
.gal-pdf .gal-buttons {
top: 40px;
background: rgba(0,0,0,0.6) !important;
border-radius: 3px;
}
.gal-buttons i {
vertical-align: baseline;
border-top-width: .4em;
@ -1506,12 +1522,12 @@ div.boardTitle {
.gal-name,
.gal-count {
position: fixed;
right: 178px;
right: 195px;
}
.gal-hide-thumbnails .gal-buttons,
.gal-hide-thumbnails .gal-count,
.gal-hide-thumbnails .gal-name {
right: 28px;
right: 44px;
}
.gal-name {
bottom: 6px;
@ -1526,6 +1542,10 @@ div.boardTitle {
.gal-buttons .menu-button:hover {
color: rgb(95, 95, 101) !important;
}
:root.gal-pdf .gal-close:hover,
:root.gal-pdf .gal-buttons .menu-button:hover {
color: rgb(204, 204, 204) !important;
}
.gal-count {
bottom: 27px;
background: rgba(0,0,0,0.6) !important;
@ -1533,21 +1553,21 @@ div.boardTitle {
padding: 1px 5px 2px 5px;
color: #ffffff !important;
}
:root:not(.gal-fit-width) .gal-name {
:root:not(.gal-fit-width):not(.gal-pdf) .gal-name {
bottom: 23px !important;
}
:root:not(.gal-fit-width) .gal-count {
:root:not(.gal-fit-width):not(.gal-pdf) .gal-count {
bottom: 44px !important;
}
:root:not(.gal-fit-height):not(.gal-hide-thumbnails) .gal-buttons,
:root:not(.gal-fit-height):not(.gal-hide-thumbnails) .gal-name,
:root:not(.gal-fit-height):not(.gal-hide-thumbnails) .gal-count {
right: 195px !important;
:root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-buttons,
:root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-name,
:root.gal-fit-height:not(.gal-pdf):not(.gal-hide-thumbnails) .gal-count {
right: 178px !important;
}
:root.gal-hide-thumbnails:not(.gal-fit-height) .gal-buttons,
:root.gal-hide-thumbnails:not(.gal-fit-height) .gal-name,
:root.gal-hide-thumbnails:not(.gal-fit-height) .gal-count {
right: 44px !important;
:root.gal-hide-thumbnails:.gal-fit-height:not(.gal-pdf) .gal-buttons,
:root.gal-hide-thumbnails:.gal-fit-height:not(.gal-pdf) .gal-name,
:root.gal-hide-thumbnails:.gal-fit-height:not(.gal-pdf) .gal-count {
right: 28px !important;
}
@media screen and (resolution: 1dppx) {
.fa-bars {

View File

@ -13,4 +13,4 @@
</div>
</nav>
<hr>
<div class=section-container><section></section></div>
<div class=section-container><section></section></div>

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -1,11 +1,14 @@
class Clone extends Post
constructor: (@origin, @context) ->
constructor: (@origin, @context, contractThumb) ->
for key in ['ID', 'fullID', 'board', 'thread', 'info', 'quotes', 'isReply']
# Copy or point to the origin's key value.
@[key] = origin[key]
{nodes} = origin
root = nodes.root.cloneNode true
root = if contractThumb
@cloneWithoutVideo nodes.root
else
nodes.root.cloneNode true
post = $ '.post', root
info = $ '.postInfo', post
@nodes =
@ -56,6 +59,29 @@ class Clone extends Post
@file.thumb = $ 'img[data-md5]', file
@file.fullImage = $ '.full-image', file
# Contract thumbnails in quote preview
if contractThumb
$.rmClass root, 'expanded-image'
$.rmClass @file.thumb, 'expanding'
@file.isExpanded = $.hasClass root, 'expanded-image'
# 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
cloneWithoutVideo: (node) ->
if node.tagName is 'VIDEO'
[]
else if node.nodeType is Node.ELEMENT_NODE and $ 'video', node
clone = node.cloneNode false
$.add clone, @cloneWithoutVideo child for child in node.childNodes
clone
else
node.cloneNode true

View File

@ -290,8 +290,8 @@ class Post
@thread.posts.rm @
@board.posts.rm @
addClone: (context) ->
new Clone @, context
addClone: (context, contractThumb) ->
new Clone @, context, contractThumb
rmClone: (index) ->
@clones.splice index, 1

View File

@ -1 +0,0 @@
postMessage({version:'<%= version %>'},'*')

View File

@ -1,34 +0,0 @@
AutoGIF =
init: ->
return if !Conf['Auto-GIF'] or g.BOARD.ID in ['gif', 'wsg']
Post.callbacks.push
name: 'Auto-GIF'
cb: @node
CatalogThread.callbacks.push
name: 'Auto-GIF'
cb: @catalogNode
node: ->
return if @isClone or @isHidden or @thread.isHidden or !@file?.isImage
{thumb, URL} = @file
return unless /gif$/.test(URL) and !/spoiler/.test thumb.src
if @file.isSpoiler
# Revealed spoilers do not have height/width set, this fixes auto-gifs dimensions.
{style} = thumb
style.maxHeight = style.maxWidth = if @isReply then '125px' else '250px'
AutoGIF.replaceThumbnail thumb, URL
catalogNode: ->
{OP} = @thread
return unless OP.file?.isImage
{URL} = OP.file
return unless /gif$/.test URL
AutoGIF.replaceThumbnail @nodes.thumb, URL, true
replaceThumbnail: (thumb, URL, isBackground) ->
gif = $.el 'img'
$.on gif, 'load', ->
# Replace the thumbnail once the GIF has finished loading.
if isBackground
thumb.style.backgroundImage = "url(#{URL})"
else
thumb.src = URL
gif.src = URL

View File

@ -15,10 +15,10 @@ Gallery =
Post.callbacks.push
name: 'Gallery'
cb: @node
cb: @node
node: ->
return unless @file?.isImage
return unless @file
if Gallery.nodes
Gallery.generateThumb $ '.file', @nodes.root
Gallery.nodes.total.textContent = Gallery.images.length
@ -33,23 +33,23 @@ Gallery =
nodes.el = dialog = $.el 'div',
id: 'a-gallery'
innerHTML: """
<div class=gal-viewport>
<span class=gal-buttons>
<a class="menu-button" href="javascript:;"><i class="fa">\uf107</i></a>
<a href=javascript:; class='gal-close fa'>\uf00d</a>
</span>
<a class=gal-name target="_blank"></a>
<span class=gal-count>
<span class='count'></span> / <span class='total'></span>
</span>
<div class=gal-prev></div>
<div class=gal-image>
<a href=javascript:;><img></a>
</div>
<div class=gal-next></div>
</div>
<div class=gal-thumbnails></div>
"""
<div class=gal-viewport>
<span class=gal-buttons>
<a class="menu-button" href="javascript:;"><i></i></a>
<a href=javascript:; class=gal-close>×</a>
</span>
<a class=gal-name target="_blank"></a>
<span class=gal-count>
<span class='count'></span> / <span class='total'></span>
</span>
<div class=gal-prev></div>
<div class=gal-image>
<a href=javascript:;><img></a>
</div>
<div class=gal-next></div>
</div>
<div class=gal-thumbnails></div>
"""
nodes[key] = $ value, dialog for key, value of {
frame: '.gal-image'
@ -65,12 +65,11 @@ Gallery =
nodes.menu = new UI.Menu 'gallery'
{cb} = Gallery
$.on nodes.frame, 'click', cb.blank
$.on nodes.current, 'click', cb.download
$.on nodes.next, 'click', cb.next
$.on ($ '.gal-prev', dialog), 'click', cb.prev
$.on ($ '.gal-next', dialog), 'click', cb.next
$.on ($ '.gal-close', dialog), 'click', cb.close
$.on nodes.frame, 'click', cb.blank
$.on nodes.next, 'click', cb.advance
$.on $('.gal-prev', dialog), 'click', cb.prev
$.on $('.gal-next', dialog), 'click', cb.next
$.on $('.gal-close', dialog), 'click', cb.close
$.on menuButton, 'click', (e) ->
nodes.menu.toggle e, @, g
@ -86,12 +85,7 @@ Gallery =
$.on d, 'keydown', cb.keybinds
$.off d, 'keydown', Keybinds.keydown
i = 0
files = $$ '.post .file'
while file = files[i++]
continue if $ '.fileDeletedRes, .fileDeleted', file
Gallery.generateThumb file
Gallery.generateThumb file for file, i in $$ '.post .file' when !$ '.fileDeletedRes, .fileDeleted', file
$.add d.body, dialog
nodes.thumbs.scrollTop = 0
@ -107,16 +101,22 @@ Gallery =
generateThumb: (file) ->
post = Get.postFromNode file
return unless post.file and (post.file.isImage or post.file.isVideo or Conf['PDF in Gallery'])
title = ($ '.fileText a', file).textContent
thumb = post.file.thumb.parentNode.cloneNode true
if dupe = $ 'img + img', thumb
$.rm dupe
thumb.className = 'gal-thumb'
thumb.title = title
thumb.dataset.id = Gallery.images.length
thumb.dataset.post = $('a[title="Highlight this post"]', post.nodes.info).href
thumb.firstElementChild.style.cssText = ''
thumb = $.el 'a',
className: 'gal-thumb'
href: post.file.URL
target: '_blank'
title: title
thumb.dataset.id = Gallery.images.length
thumb.dataset.post = $('a[title="Highlight this post"]', post.nodes.info).href
thumb.dataset.isVideo = true if post.file.isVideo
thumbImg = post.file.thumb.cloneNode false
thumbImg.style.cssText = ''
$.add thumb, thumbImg
$.on thumb, 'click', Gallery.cb.open
@ -130,8 +130,10 @@ Gallery =
cb = switch key
when 'Esc', Conf['Open Gallery']
Gallery.cb.close
when 'Right', 'Enter'
when 'Right'
Gallery.cb.next
when 'Enter'
Gallery.cb.advance
when 'Left', ''
Gallery.cb.prev
@ -150,15 +152,20 @@ Gallery =
$.rmClass el, 'gal-highlight' if el = $ '.gal-highlight', nodes.thumbs
$.addClass @, 'gal-highlight'
img = $.el 'img',
elType = if @dataset.isVideo then 'video' else if /\.pdf$/.test(@href) then 'iframe' else 'img'
$[if elType is 'iframe' then 'addClass' else 'rmClass'] nodes.el, 'gal-pdf'
file = $.el elType,
src: name.href = @href
title: name.download = name.textContent = @title
$.extend img.dataset, @dataset
$.replace nodes.current, img
$.extend file.dataset, @dataset
nodes.current.pause?()
$.replace nodes.current, file
Video.configure file if @dataset.isVideo
nodes.count.textContent = +@dataset.id + 1
nodes.current = img
nodes.frame.scrollTop = 0
nodes.current = file
nodes.frame.scrollTop = 0
nodes.next.focus()
# Scroll
@ -169,9 +176,9 @@ Gallery =
return if top < 0
nodes.thumbs.scrollTop += top
$.on img, 'error', ->
Gallery.cb.error img, thumb
$.on file, 'error', ->
Gallery.cb.error file, thumb
image: (e) ->
e.preventDefault()
@ -183,24 +190,20 @@ Gallery =
delete post.file.fullImage
src = @src.split '/'
if src[2] is 'images.4chan.org'
if src[2] is 'i.4cdn.org'
URL = Redirect.to 'file',
boardID: src[3]
filename: src[5]
if URL
thumb.href = URL
return unless Gallery.nodes.current is img
revived = $.el 'img',
src: URL
title: img.title
$.extend revived.dataset, img.dataset
$.replace img, revived
img.src = URL
return
if g.DEAD or post.isDead or post.file.isDead
return
# XXX CORS for images.4chan.org WHEN?
$.ajax "//api.4chan.org/#{post.board}/res/#{post.thread}.json", onload: ->
$.ajax "//a.4cdn.org/#{post.board}/res/#{post.thread}.json", onload: ->
return if @status isnt 200
i = 0
{posts} = @response
@ -211,12 +214,22 @@ 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]
toggle: -> (if Gallery.nodes then Gallery.cb.close else Gallery.build)()
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]
toggle: -> (if Gallery.nodes then Gallery.cb.close else Gallery.build)()
blank: (e) -> Gallery.cb.close() if e.target is @
advance: ->
if Gallery.nodes.current.controls then return
if Gallery.nodes.current.paused then return Gallery.nodes.current.play()
Gallery.cb.next()
pause: ->
{current} = Gallery.nodes
current[if current.paused then 'play' else 'pause']() if current.nodeType is 'VIDEO'
close: ->
Gallery.nodes.current.pause?()
$.rm Gallery.nodes.el
delete Gallery.nodes
d.body.style.overflow = ''

View File

@ -1,6 +1,6 @@
ImageExpand =
init: ->
return if !Conf['Image Expansion']
return if g.VIEW is 'catalog' or !Conf['Image Expansion']
@EAI = $.el 'a',
id: 'img-controls'
@ -17,51 +17,66 @@ ImageExpand =
cb: @node
node: ->
return unless @file and (@file.isImage or @file.isVideo)
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 @
if @isClone
if $.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 @file.isExpanded and @file.isVideo
clone = @
ImageExpand.setupVideoControls clone
unless clone.origin.file.fullImage.paused
$.queueTask -> Video.start clone.file.fullImage
return
if ImageExpand.on and !@isHidden and (Conf['Expand spoilers'] or !@file.isSpoiler)
ImageExpand.expand @
else if ImageExpand.on and !@isHidden and
(Conf['Expand spoilers'] or !@file.isSpoiler) and
(Conf['Expand videos'] or !@file.isVideo)
ImageExpand.expand @, null, true
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
e.preventDefault()
ImageExpand.toggle Get.postFromNode @
ImageExpand.toggle post
toggleAll: ->
$.event 'CloseMenu'
toggle = (post) ->
{file} = post
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)
return
$.queueTask func, post
if ImageExpand.on = $.hasClass ImageExpand.EAI, 'expand-all-shortcut'
ImageExpand.EAI.className = 'contract-all-shortcut a-icon'
ImageExpand.EAI.title = 'Contract All Images'
func = ImageExpand.expand
func = (post) -> ImageExpand.expand post, null, true
else
ImageExpand.EAI.className = 'expand-all-shortcut a-icon'
ImageExpand.EAI.title = 'Expand All Images'
func = ImageExpand.contract
g.posts.forEach (post) ->
for post in [post].concat post.clones
{file} = post
return unless file and (file.isImage or file.isVideo) and doc.contains post.nodes.root
if ImageExpand.on and !post.isHidden and
(!Conf['Expand spoilers'] and file.isSpoiler or
Conf['Expand from here'] and Header.getTopOf(file.thumb) < 0)
return
$.queueTask func, post
toggle post for post in [post, post.clones...]
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.isExpanded or $.hasClass post.file.thumb, 'expanding'
ImageExpand.expand post
return
@ -90,87 +105,94 @@ ImageExpand =
ImageExpand.contract post
contract: (post) ->
{thumb} = post.file
if post.file.isVideo and video = post.file.fullImage
video.pause()
TrashQueue.add video, post
thumb.parentNode.href = video.src
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'
$.rmClass thumb, 'expanding'
post.file.isExpanded = false
post.file.fullImage.pause() if post.file.isVideo
post.file.videoControls?.map($.rm)
delete post.file.videoControls
expand: (post, src) ->
expand: (post, src, disableAutoplay) ->
# 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'
$.addClass thumb, 'expanding'
if post.file.fullImage
if el = post.file.fullImage
# Expand already-loaded/ing picture.
$.asap (-> (file = post.file.fullImage) and (file.videoHeight or file.naturalHeight)), ->
ImageExpand.completeExpand post
return
file =
$.el (if post.file.isImage then 'img' else 'video'),
TrashQueue.remove el
else
el = post.file.fullImage = $.el (if isVideo then 'video' else 'img'),
className: 'full-image'
src: src or post.file.URL
post.file.fullImage = file
if isVideo
file.loop = true
file.controls = Conf['Show Controls']
$.on file, 'error', ImageExpand.error
$.asap (-> (file = post.file.fullImage) and (file.videoHeight or file.naturalHeight)), ->
$.on el, 'error', ImageExpand.error
el.src = src or post.file.URL
if isVideo
# XXX Firefox doesn't seem to size videos correctly?
file.style.maxHeight = file.videoHeight + "px"
file.style.maxWidth = file.videoWidth + "px"
ImageExpand.completeExpand post
$.after (if file.controls then thumb.parentNode else thumb), file
el.loop = true
$.after thumb, el unless el is thumb.nextSibling
$.asap (-> el.videoHeight or el.naturalHeight), ->
ImageExpand.completeExpand post, disableAutoplay
completeExpand: (post) ->
{thumb} = post.file
return unless $.hasClass thumb, 'expanding' # contracted before the image loaded
post.file.isExpanded = true
ImageExpand.setupVideo post if post.file.isVideo
$.addClass post.nodes.root, 'expanded-image'
$.rmClass post.file.thumb, 'expanding'
completeExpand: (post, disableAutoplay) ->
return unless $.hasClass post.file.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
setupVideo: (post) ->
{file} = post
video = file.fullImage
file.videoControls = []
video.muted = not Conf['Allow Sound']
if video.controls
# contract link in file info
completeExpand2: (post, disableAutoplay) ->
$.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
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.contract(Get.postFromNode @) if mousedown and e.clientX <= @getBoundingClientRect().left
click: (e) ->
if @paused and not @controls
@play()
e.preventDefault()
setupVideoControls: (post) ->
{file} = post
{thumb} = file
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
$.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
file.videoControls.push $.tn('\u00A0'), contract
# drag left to contract
file.mousedown = false
$.on video, 'mousedown', (e) -> file.mousedown = true if e.button is 0
$.on video, 'mouseup', (e) -> file.mousedown = false if e.button is 0
$.on video, 'mouseover', (e) -> file.mousedown = false
$.on video, 'mouseout', (e) ->
if file.mousedown and e.clientX <= video.getBoundingClientRect().left
ImageExpand.contract post
if Conf['Autoplay']
video.play()
else unless video.controls
play = $.el 'a',
textContent: 'play'
href: 'javascript:;'
$.on play, 'click', (e) ->
video[@textContent]()
@textContent = if @textContent is 'play' then 'pause' else 'play'
file.videoControls.push $.tn('\u00A0'), play
$.add file.videoControls, [$.tn('\u00A0'), contract]
$.add file.text, file.videoControls
error: ->
@ -198,7 +220,7 @@ ImageExpand =
timeoutID = setTimeout ImageExpand.expand, 10000, post
<% if (type === 'crx') { %>
$.ajax post.file.URL,
$.ajax @src,
onloadend: ->
return if @status isnt 404
clearTimeout timeoutID
@ -221,7 +243,7 @@ ImageExpand =
menu:
init: ->
return if !Conf['Image Expansion']
return if g.VIEW is 'catalog' or !Conf['Image Expansion']
el = $.el 'span',
textContent: 'Image Expansion'

View File

@ -9,36 +9,50 @@ ImageHover =
name: 'Image Hover'
cb: @catalogNode
node: ->
return unless @file and (@file.isImage or @file.isVideo)
return unless @file?.isImage or @file?.isVideo
$.on @file.thumb, 'mouseover', ImageHover.mouseover
catalogNode: ->
return unless @thread.OP.file and (@thread.OP.file.isImage or @thread.OP.file.isVideo)
return unless (file = @thread.OP.file) and (file.isImage or file.isVideo)
$.on @nodes.thumb, 'mouseover', ImageHover.mouseover
mouseover: (e) ->
post = if $.hasClass @, 'thumb'
g.posts[@parentNode.dataset.fullID]
else
Get.postFromNode @
el = if post.file.isImage
$.el 'img',
id: 'ihover'
src: post.file.URL
{isVideo} = post.file
if post.file.fullImage
el = post.file.fullImage
TrashQueue.remove el
else
$.el 'video',
controls: false
id: 'ihover'
el = $.el (if isVideo then 'video' else 'img'),
className: 'full-image'
src: post.file.URL
autoplay: Conf['Autoplay']
muted: !Conf['Allow Sound']
loop: true
$.add Header.hover, el
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
if isVideo
el.loop = true
el.controls = false
el.muted = not Conf['Allow Sound']
el.play() if Conf['Autoplay']
UI.hover
root: @
el: el
latestEvent: e
endEvents: 'mouseout click'
asapTest: -> post.file.isVideo or el.naturalHeight
asapTest: -> (el.videoHeight or el.naturalHeight)
noRemove: true
cb: ->
if isVideo
el.pause()
TrashQueue.add el, post
el.removeAttribute 'id'
$.on el, 'error', ImageHover.error
error: ->
return unless doc.contains @

View File

@ -1,17 +1,15 @@
ImageLoader =
init: ->
return unless Conf["Image Prefetching"] or Conf["Replace JPG"] or Conf["Replace PNG"] or Conf["Replace GIF"]
return unless Conf["Image Prefetching"] or Conf["Replace JPG"] or Conf["Replace PNG"] or Conf["Replace GIF"] or Conf["Replace WEBM"]
Post.callbacks.push
name: 'Image Replace'
cb: @node
Thread.callbacks.push
name: 'Image Replace'
cb: @thread
return unless Conf['Image Prefetching'] and g.VIEW is 'thread'
prefetch = $.el 'label',
innerHTML: '<input type=checkbox name="prefetch"> Prefetch Images'
@ -22,27 +20,49 @@ ImageLoader =
type: 'header'
el: prefetch
order: 104
thread: ->
ImageLoader.thread = @
node: ->
return if @isClone or @isHidden or @thread.isHidden or !@file?.isImage
return unless @file
{isImage, isVideo} = @file
return if @isClone or @isHidden or @thread.isHidden or !(isImage or isVideo)
{thumb, URL} = @file
return unless (Conf[string = "Replace #{if (type = (URL.match /\w{3}$/)[0].toUpperCase()) is 'PEG' then 'JPG' else type}"] and !/spoiler/.test thumb.src) or Conf['prefetch']
{style} = thumb
type = if (match = URL.match(/\.([^.]+)$/)[1].toUpperCase()) is 'JPEG' then 'JPG' else match
replace = "Replace #{type}"
return unless (Conf[replace] and !/spoiler/.test thumb.src) or (Conf['prefetch'] and g.VIEW is 'thread')
if @file.isSpoiler
# Revealed spoilers do not have height/width set, this fixes the image's dimensions.
{style} = thumb
style.maxHeight = style.maxWidth = if @isReply then '125px' else '250px'
img = $.el 'img'
if Conf[string]
$.on img, 'load', ->
# Replace the thumbnail once the GIF has finished loading.
file = $.el if isImage then 'img' else 'video'
if Conf[replace]
if isVideo
file.alt = thumb.alt
file.dataset.md5 = thumb.dataset.md5
file.style.height = style.height
file.style.width = style.width
file.style.maxHeight = style.maxHeight
file.style.maxWidth = style.maxWidth
file.loop = true
file.autoplay = Conf['Autoplay']
if Conf['Image Hover']
$.on file, 'mouseover', ImageHover.mouseover
cb = =>
$.off file, 'load loadedmetadata', cb
# Replace the thumbnail once the file has finished loading.
if isVideo
$.replace thumb, file
@file.thumb = file # XXX expanding requires the post.file.thumb node.
return
thumb.src = URL
img.src = URL
$.on file, 'load loadedmetadata', cb
file.src = URL
toggle: ->
enabled = Conf['prefetch'] = @checked
if enabled
ImageLoader.thread.posts.forEach ImageLoader.node.call
g.BOARD.posts.forEach ImageLoader.node.call
return

View File

@ -0,0 +1,14 @@
TrashQueue =
init: -> return
add: (video, post) ->
if @killNext and video isnt @killNext
delete @killNextPost?.file?.fullImage
$.rm @killNext
@killNext = video
@killNextPost = post
remove: (video) ->
if video is @killNext
delete @killNext

21
src/Images/Video.coffee Normal file
View File

@ -0,0 +1,21 @@
Video =
configure: (video, disableAutoplay) ->
video.loop = true
video.muted = !Conf['Allow Sound']
video.controls = Conf['Show Controls']
video.autoplay = false
if Conf['Autoplay'] and not disableAutoplay
Video.start video
else
video.pause()
start: (video) ->
{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

View File

@ -8,7 +8,7 @@ ExpandThread =
return unless summary = $.x 'following-sibling::*[contains(@class,"summary")][1]', thread.OP.nodes.root
a = $.el 'a',
textContent: ExpandThread.text '+', summary.textContent.match(/\d+/g)...
href: "#{thread.board.ID}/res/#{thread.ID}"
href: "res/#{thread.ID}"
className: 'summary'
$.on a, 'click', ExpandThread.cbToggle
$.replace summary, a

View File

@ -299,13 +299,11 @@ QR =
blob.name = url.substr(url.lastIndexOf('/')+1, url.length)
name_start = header.indexOf('name="') + 6
if (name_start - 6 != -1)
name_end = header.substr(name_start, header.length).indexOf('"')
name_end = header.substr(name_start, header.length).indexOf('"')
blob.name = header.substr(name_start, name_end)
return if blob.type is null
QR.error "Unsupported file type."
return unless blob.type in QR.mimeTypes
QR.error "Unsupported file type."
QR.handleFiles([blob])
handleUrl: ->
@ -778,6 +776,7 @@ QR =
QR.cooldown.auto = false
QR.status()
QR.error err
QR.captcha.setup() if QR.captcha.isEnabled
return
h1 = $ 'h1', resDoc
@ -812,10 +811,13 @@ QR =
# Enable auto-posting if we have stuff left to post, disable it otherwise.
postsCount = QR.posts.length - 1
QR.cooldown.auto = postsCount and isReply
QR.captcha.setup() if QR.captcha.isEnabled and QR.cooldown.auto
unless Conf['Persistent QR'] or QR.cooldown.auto
QR.close()
else
if QR.posts.length > 1
QR.captcha.setup()
post.rm()
QR.cooldown.set {req, post, isReply, threadID}

View File

@ -80,8 +80,6 @@ QR.post = class
$.rmClass QR.nodes.el, 'dump'
else if @ is QR.selected
(QR.posts[index-1] or QR.posts[index+1]).select()
if QR.captcha.isEnabled
QR.captcha.setup()
QR.posts.splice index, 1
QR.status()
delete: ->