ImageExpand.coffee

This commit is contained in:
Zixaphir 2015-01-09 18:19:43 -07:00
parent bc67db84af
commit 4089d56720
3 changed files with 630 additions and 553 deletions

View File

@ -10886,7 +10886,8 @@
ImageExpand = { ImageExpand = {
init: function() { init: function() {
if (g.VIEW === 'catalog' || !Conf['Image Expansion']) { var _ref;
if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Image Expansion'])) {
return; return;
} }
this.EAI = $.el('a', { this.EAI = $.el('a', {
@ -10896,6 +10897,13 @@
href: 'javascript:;' href: 'javascript:;'
}); });
$.on(this.EAI, 'click', ImageExpand.cb.toggleAll); $.on(this.EAI, 'click', ImageExpand.cb.toggleAll);
$.on(d, 'scroll visibilitychange', this.cb.playVideos);
this.videoControls = $.el('span', {
className: 'video-controls'
});
$.extend(this.videoControls, {
innerHTML: " <a href=\"javascript:;\" title=\"You can also contract the video by dragging it to the left.\">contract</a>"
});
Header.addShortcut(this.EAI, true); Header.addShortcut(this.EAI, true);
return Post.callbacks.push({ return Post.callbacks.push({
name: 'Image Expansion', name: 'Image Expansion',
@ -10903,37 +10911,32 @@
}); });
}, },
node: function() { node: function() {
var clone, thumb, _ref, _ref1; var _ref;
if (!(((_ref = this.file) != null ? _ref.isImage : void 0) || ((_ref1 = this.file) != null ? _ref1.isVideo : void 0))) { if (!(this.file && (this.file.isImage || this.file.isVideo))) {
return; return;
} }
thumb = this.file.thumb; $.on(this.file.thumb.parentNode, 'click', ImageExpand.cb.toggle);
$.on(thumb.parentNode, 'click', ImageExpand.cb.toggle);
if (this.isClone) { if (this.isClone) {
if ($.hasClass(thumb, 'expanding')) { if (this.file.isExpanding) {
ImageExpand.contract(this); ImageExpand.contract(this);
ImageExpand.expand(this); return ImageExpand.expand(this);
} else if (this.file.isExpanded && this.file.isVideo) { } else if (this.file.isExpanded && this.file.isVideo) {
clone = this; ImageExpand.setupVideoCB(this);
ImageExpand.setupVideoControls(clone); return ImageExpand.setupVideo(this, !((_ref = this.origin.file.fullImage) != null ? _ref.paused : void 0) || this.origin.file.wasPlaying, this.file.fullImage.controls);
if (!clone.origin.file.fullImage.paused) {
$.queueTask(function() {
return Video.start(clone.file.fullImage);
});
} }
} } else if (ImageExpand.on && !this.isHidden && !this.isFetchedQuote && (Conf['Expand spoilers'] || !this.file.isSpoiler) && (Conf['Expand videos'] || !this.file.isVideo)) {
} else if (ImageExpand.on && !this.isHidden && (Conf['Expand spoilers'] || !this.file.isSpoiler) && (Conf['Expand videos'] || !this.file.isVideo)) { return ImageExpand.expand(this);
return ImageExpand.expand(this, null, true);
} }
}, },
cb: { cb: {
toggle: function(e) { toggle: function(e) {
var post, _ref; var file, post;
if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) {
return; return;
} }
post = Get.postFromNode(this); post = Get.postFromNode(this);
if (post.file.isExpanded && ((_ref = post.file.fullImage) != null ? _ref.controls : void 0)) { file = post.file;
if (file.isExpanded && file.isVideo && file.fullImage.controls) {
return; return;
} }
e.preventDefault(); e.preventDefault();
@ -10956,9 +10959,7 @@
if (ImageExpand.on = $.hasClass(ImageExpand.EAI, 'expand-all-shortcut')) { if (ImageExpand.on = $.hasClass(ImageExpand.EAI, 'expand-all-shortcut')) {
ImageExpand.EAI.className = 'contract-all-shortcut a-icon'; ImageExpand.EAI.className = 'contract-all-shortcut a-icon';
ImageExpand.EAI.title = 'Contract All Images'; ImageExpand.EAI.title = 'Contract All Images';
func = function(post) { func = ImageExpand.expand;
return ImageExpand.expand(post, null, true);
};
} else { } else {
ImageExpand.EAI.className = 'expand-all-shortcut a-icon'; ImageExpand.EAI.className = 'expand-all-shortcut a-icon';
ImageExpand.EAI.title = 'Expand All Images'; ImageExpand.EAI.title = 'Expand All Images';
@ -10973,122 +10974,207 @@
} }
}); });
}, },
playVideos: function(e) {
return g.posts.forEach(function(post) {
var file, video, visible, _i, _len, _ref;
_ref = [post].concat(__slice.call(post.clones));
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
post = _ref[_i];
file = post.file;
if (!(file && file.isVideo && file.isExpanded)) {
continue;
}
video = file.fullImage;
visible = Header.isNodeVisible(video);
if (visible && file.wasPlaying) {
delete file.wasPlaying;
video.play();
} else if (!visible && !video.paused) {
file.wasPlaying = true;
video.pause();
}
}
});
},
setFitness: function() { setFitness: function() {
return (this.checked ? $.addClass : $.rmClass)(doc, this.name.toLowerCase().replace(/\s+/g, '-')); return $[this.checked ? 'addClass' : 'rmClass'](doc, this.name.toLowerCase().replace(/\s+/g, '-'));
} }
}, },
toggle: function(post) { toggle: function(post) {
var headRect, left, root, top, x, y, _ref; var next;
if (!(post.file.isExpanded || $.hasClass(post.file.thumb, 'expanding'))) { if (!(post.file.isExpanding || post.file.isExpanded)) {
post.file.scrollIntoView = Conf['Scroll into view'];
ImageExpand.expand(post); ImageExpand.expand(post);
return; return;
} }
root = post.nodes.root; ImageExpand.contract(post);
_ref = (Conf['Advance on contract'] ? (function() { if (Conf['Advance on contract']) {
var next; next = post.nodes.root;
next = root;
while (next = $.x("following::div[contains(@class,'postContainer')][1]", next)) { while (next = $.x("following::div[contains(@class,'postContainer')][1]", next)) {
if ($('.stub', next) || next.offsetHeight === 0) { if (!($('.stub', next) || next.offsetHeight === 0)) {
continue; break;
}
return next;
}
return root;
})() : root).getBoundingClientRect(), top = _ref.top, left = _ref.left;
if (top < 0) {
y = top;
if (Conf['Fixed Header'] && !Conf['Bottom Header']) {
headRect = Header.bar.getBoundingClientRect();
y -= headRect.top + headRect.height;
} }
} }
if (left < 0) { if (next) {
x = -window.scrollX; return Header.scrollTo(next);
} }
if (x || y) {
window.scrollBy(x, y);
} }
return ImageExpand.contract(post);
}, },
contract: function(post) { contract: function(post) {
var cb, eventName, thumb, video, _ref; var bottom, cb, el, eventName, file, oldHeight, scrollY, top, x, _i, _len, _ref, _ref1;
thumb = post.file.thumb; file = post.file;
if (post.file.isVideo && (video = post.file.fullImage)) { if (el = file.fullImage) {
video.pause(); top = Header.getTopOf(el);
TrashQueue.add(video, post); bottom = top + el.getBoundingClientRect().height;
thumb.parentNode.href = video.src; oldHeight = d.body.clientHeight;
thumb.parentNode.target = '_blank'; scrollY = window.scrollY;
_ref = ImageExpand.videoCB;
for (eventName in _ref) {
cb = _ref[eventName];
$.off(video, eventName, cb);
}
$.rm(post.file.videoControls);
delete post.file.videoControls;
} }
$.rmClass(post.nodes.root, 'expanded-image'); $.rmClass(post.nodes.root, 'expanded-image');
$.rmClass(post.file.thumb, 'expanding'); $.rmClass(file.thumb, 'expanding');
delete post.file.isExpanding; if (file.videoControls) {
delete post.file.isExpanded; $.rm(file.videoControls);
if (post.file.isVideo && post.file.fullImage) {
return post.file.fullImage.pause();
} }
file.thumb.parentNode.href = file.URL;
file.thumb.parentNode.target = '_blank';
_ref = ['isExpanding', 'isExpanded', 'videoControls', 'wasPlaying', 'scrollIntoView'];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
x = _ref[_i];
delete file[x];
}
if (!el) {
return;
}
if (doc.contains(el)) {
if (bottom <= 0) {
window.scroll(0, scrollY + d.body.clientHeight - oldHeight);
} else {
Header.scrollToIfNeeded(post.nodes.root);
}
if (window.scrollX > 0) {
window.scroll(0, window.scrollY);
}
}
$.off(el, 'error', ImageExpand.error);
ImageCommon.pushCache(el);
if (file.isVideo) {
el.pause();
_ref1 = ImageExpand.videoCB;
for (eventName in _ref1) {
cb = _ref1[eventName];
$.off(el, eventName, cb);
}
}
if (Conf['Restart when Opened']) {
ImageCommon.rewind(file.thumb);
}
delete file.fullImage;
return $.queueTask(function() {
if (file.isExpanding || file.isExpanded) {
return;
}
$.rmClass(el, 'full-image');
if (el.id) {
return;
}
return $.rm(el);
});
}, },
expand: function(post, src, disableAutoplay) { expand: function(post, src) {
var el, isVideo, thumb, _ref; var el, file, isVideo, thumb, _ref;
_ref = post.file, thumb = _ref.thumb, isVideo = _ref.isVideo; file = post.file;
if (post.isHidden || post.file.isExpanded || $.hasClass(thumb, 'expanding')) { thumb = file.thumb, isVideo = file.isVideo;
if (post.isHidden || file.isExpanding || file.isExpanded) {
return; return;
} }
$.addClass(thumb, 'expanding'); $.addClass(thumb, 'expanding');
if (el = post.file.fullImage) { file.isExpanding = true;
el.className = 'full-image'; if (file.fullImage) {
el.style.cssText = ''; el = file.fullImage;
TrashQueue.remove(el); } else if (((_ref = ImageCommon.cache) != null ? _ref.dataset.fullID : void 0) === post.fullID) {
} else { el = file.fullImage = ImageCommon.popCache();
el = post.file.fullImage = $.el((isVideo ? 'video' : 'img'), {
className: 'full-image'
});
$.on(el, 'error', ImageExpand.error); $.on(el, 'error', ImageExpand.error);
el.src = src || post.file.URL; if (Conf['Restart when Opened'] && el.id !== 'ihover') {
if (isVideo) { ImageCommon.rewind(el);
el.loop = true;
} }
el.removeAttribute('id');
} else {
el = file.fullImage = $.el((isVideo ? 'video' : 'img'));
el.dataset.fullID = post.fullID;
$.on(el, 'error', ImageExpand.error);
el.src = src || file.URL;
} }
if (el !== thumb.nextSibling) { el.className = 'full-image';
$.after(thumb, el); $.after(thumb, el);
if (isVideo) {
if (Conf['Show Controls'] && !file.videoControls) {
file.videoControls = ImageExpand.videoControls.cloneNode(true);
$.add(file.text, file.videoControls);
} }
thumb.parentNode.removeAttribute('href');
thumb.parentNode.removeAttribute('target');
el.loop = true;
ImageExpand.setupVideoCB(post);
}
if (!isVideo) {
return $.asap((function() { return $.asap((function() {
return el.videoHeight || el.naturalHeight; return el.naturalHeight;
}), function() { }), function() {
return ImageExpand.completeExpand(post, disableAutoplay); return ImageExpand.completeExpand(post);
}); });
}, } else if (el.readyState >= el.HAVE_METADATA) {
completeExpand: function(post, disableAutoplay) { return ImageExpand.completeExpand(post);
var bottom; } else {
if (!$.hasClass(post.file.thumb, 'expanding')) { return $.on(el, 'loadedmetadata', function() {
return; return ImageExpand.completeExpand(post);
}
if (!post.nodes.root.parentNode) {
ImageExpand.completeExpand2(post);
return;
}
bottom = post.nodes.root.getBoundingClientRect().bottom;
return $.queueTask(function() {
ImageExpand.completeExpand2(post, disableAutoplay);
if (!(bottom <= 0)) {
return;
}
return window.scrollBy(0, post.nodes.root.getBoundingClientRect().bottom - bottom);
}); });
}
}, },
completeExpand2: function(post, disableAutoplay) { completeExpand: function(post) {
var bottom, file, imageBottom, oldHeight, scrollY;
file = post.file;
if (!file.isExpanding) {
return;
}
bottom = Header.getTopOf(file.thumb) + file.thumb.getBoundingClientRect().height;
oldHeight = d.body.clientHeight;
scrollY = window.scrollY;
$.addClass(post.nodes.root, 'expanded-image'); $.addClass(post.nodes.root, 'expanded-image');
$.rmClass(post.file.thumb, 'expanding'); $.rmClass(file.thumb, 'expanding');
post.file.isExpanded = true; file.isExpanded = true;
if (post.file.isVideo) { delete file.isExpanding;
ImageExpand.setupVideoControls(post); if (doc.contains(post.nodes.root) && bottom <= 0) {
return Video.configure(post.file.fullImage, disableAutoplay); window.scroll(window.scrollX, scrollY + d.body.clientHeight - oldHeight);
}
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) {
return ImageExpand.setupVideo(post, Conf['Autoplay'], Conf['Show Controls']);
}
},
setupVideo: function(post, playing, controls) {
var fullImage;
fullImage = post.file.fullImage;
if (!playing) {
fullImage.controls = controls;
return;
}
fullImage.controls = false;
$.asap((function() {
return doc.contains(fullImage);
}), function() {
if (!d.hidden && Header.isNodeVisible(fullImage)) {
return fullImage.play();
} else {
return post.file.wasPlaying = true;
}
});
if (controls) {
return ImageCommon.addControls(fullImage);
} }
}, },
videoCB: (function() { videoCB: (function() {
@ -11110,117 +11196,58 @@
}, },
mouseout: function(e) { mouseout: function(e) {
if (mousedown && e.clientX <= this.getBoundingClientRect().left) { if (mousedown && e.clientX <= this.getBoundingClientRect().left) {
return ImageExpand.contract(Get.postFromNode(this)); return ImageExpand.toggle(Get.postFromNode(this));
} }
}, },
click: function(e) { click: function(e) {
if (this.paused && !this.controls) { if (this.paused && !this.controls) {
this.play(); this.play();
return e.preventDefault(); return e.stopPropagation();
} }
} }
}; };
})(), })(),
setupVideoControls: function(post) { setupVideoCB: function(post) {
var cb, contract, eventName, file, thumb, video, _ref; var cb, eventName, file, _ref;
file = post.file; file = post.file;
thumb = file.thumb;
video = file.fullImage;
file.thumb.parentNode.removeAttribute('href');
file.thumb.parentNode.removeAttribute('target');
_ref = ImageExpand.videoCB; _ref = ImageExpand.videoCB;
for (eventName in _ref) { for (eventName in _ref) {
cb = _ref[eventName]; cb = _ref[eventName];
$.on(video, eventName, cb); $.on(file.fullImage, eventName, cb);
} }
file.videoControls = $.el('span', { if (file.videoControls) {
className: 'video-controls' return $.on(file.videoControls.firstElementChild, 'click', function() {
return ImageExpand.toggle(post);
}); });
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', function(e) {
return ImageExpand.contract(post);
});
$.add(file.videoControls, [$.tn('\u00A0'), contract]);
} }
return $.add(file.text, file.videoControls);
}, },
error: function() { error: function() {
var URL, error, post, src, timeoutID; var post;
post = Get.postFromNode(this); post = Get.postFromNode(this);
$.rm(this); $.rm(this);
delete post.file.isReady;
delete post.file.fullImage; delete post.file.fullImage;
if (!($.hasClass(post.file.thumb, 'expanding') || $.hasClass(post.nodes.root, 'expanded-image'))) { if (!(post.file.isExpanding || post.file.isExpanded)) {
return; return;
} }
if (ImageCommon.decodeError(this, post)) {
return ImageExpand.contract(post);
}
if (this.src.split('/')[2] !== 'i.4cdn.org') {
return ImageExpand.contract(post);
}
return ImageCommon.error(this, post, 10 * $.SECOND, function(URL) {
if (post.file.isExpanding || post.file.isExpanded) {
ImageExpand.contract(post); ImageExpand.contract(post);
if (this.error && this.error.code !== this.error.MEDIA_ERR_NETWORK) {
error = (function() {
switch (this.error.code) {
case 1:
return 'MEDIA_ERR_ABORTED';
case 3:
return 'MEDIA_ERR_DECODE';
case 4:
return 'MEDIA_ERR_SRC_NOT_SUPPORTED';
case 5:
return 'MEDIA_ERR_ENCRYPTED';
}
}).call(this);
post.file.error = $.el('div', {
textContent: "Playback error: " + error,
className: 'warning'
});
$.after(post.file.thumb, post.file.error);
return;
}
src = this.src.split('/');
if (src[2] === 'i.4cdn.org') {
URL = Redirect.to('file', {
boardID: src[3],
filename: src[4].replace(/\?.+$/, '')
});
if (URL) { if (URL) {
setTimeout(ImageExpand.expand, 10000, post, URL); return ImageExpand.expand(post, URL);
return;
}
if (g.DEAD || post.isDead || post.file.isDead) {
return;
}
}
timeoutID = setTimeout(ImageExpand.expand, 10000, post);
return $.ajax("//a.4cdn.org/" + post.board + "/thread/" + post.thread + ".json", {
onload: function() {
var postObj, _i, _len, _ref;
if (this.status !== 200) {
return;
}
_ref = this.response.posts;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
postObj = _ref[_i];
if (postObj.no === post.ID) {
break;
}
}
if (postObj.no !== post.ID) {
clearTimeout(timeoutID);
return post.kill();
} else if (postObj.filedeleted) {
clearTimeout(timeoutID);
return post.kill(true);
} }
} }
}); });
}, },
menu: { menu: {
init: function() { init: function() {
var conf, createSubEntry, el, name, subEntries, _ref; var conf, createSubEntry, el, name, subEntries, _ref, _ref1;
if (g.VIEW === 'catalog' || !Conf['Image Expansion']) { if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Image Expansion'])) {
return; return;
} }
el = $.el('span', { el = $.el('span', {
@ -11229,9 +11256,9 @@
}); });
createSubEntry = ImageExpand.menu.createSubEntry; createSubEntry = ImageExpand.menu.createSubEntry;
subEntries = []; subEntries = [];
_ref = Config.imageExpansion; _ref1 = Config.imageExpansion;
for (name in _ref) { for (name in _ref1) {
conf = _ref[name]; conf = _ref1[name];
subEntries.push(createSubEntry(name, conf[1])); subEntries.push(createSubEntry(name, conf[1]));
} }
return Header.menu.addEntry({ return Header.menu.addEntry({
@ -11242,15 +11269,12 @@
}, },
createSubEntry: function(name, desc) { createSubEntry: function(name, desc) {
var input, label; var input, label;
label = $.el('label', { label = UI.checkbox(name, name);
innerHTML: "<input type=checkbox name='" + name + "'> " + name, label.title = desc;
title: desc
});
input = label.firstElementChild; input = label.firstElementChild;
if (name === 'Fit width' || name === 'Fit height') { if (name === 'Fit width' || name === 'Fit height') {
$.on(input, 'change', ImageExpand.cb.setFitness); $.on(input, 'change', ImageExpand.cb.setFitness);
} }
input.checked = Conf[name];
$.event('change', null, input); $.event('change', null, input);
$.on(input, 'change', $.cb.checked); $.on(input, 'change', $.cb.checked);
return { return {

View File

@ -10908,7 +10908,8 @@
ImageExpand = { ImageExpand = {
init: function() { init: function() {
if (g.VIEW === 'catalog' || !Conf['Image Expansion']) { var _ref;
if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Image Expansion'])) {
return; return;
} }
this.EAI = $.el('a', { this.EAI = $.el('a', {
@ -10918,6 +10919,13 @@
href: 'javascript:;' href: 'javascript:;'
}); });
$.on(this.EAI, 'click', ImageExpand.cb.toggleAll); $.on(this.EAI, 'click', ImageExpand.cb.toggleAll);
$.on(d, 'scroll visibilitychange', this.cb.playVideos);
this.videoControls = $.el('span', {
className: 'video-controls'
});
$.extend(this.videoControls, {
innerHTML: " <a href=\"javascript:;\" title=\"You can also contract the video by dragging it to the left.\">contract</a>"
});
Header.addShortcut(this.EAI, true); Header.addShortcut(this.EAI, true);
return Post.callbacks.push({ return Post.callbacks.push({
name: 'Image Expansion', name: 'Image Expansion',
@ -10925,37 +10933,32 @@
}); });
}, },
node: function() { node: function() {
var clone, thumb, _ref, _ref1; var _ref;
if (!(((_ref = this.file) != null ? _ref.isImage : void 0) || ((_ref1 = this.file) != null ? _ref1.isVideo : void 0))) { if (!(this.file && (this.file.isImage || this.file.isVideo))) {
return; return;
} }
thumb = this.file.thumb; $.on(this.file.thumb.parentNode, 'click', ImageExpand.cb.toggle);
$.on(thumb.parentNode, 'click', ImageExpand.cb.toggle);
if (this.isClone) { if (this.isClone) {
if ($.hasClass(thumb, 'expanding')) { if (this.file.isExpanding) {
ImageExpand.contract(this); ImageExpand.contract(this);
ImageExpand.expand(this); return ImageExpand.expand(this);
} else if (this.file.isExpanded && this.file.isVideo) { } else if (this.file.isExpanded && this.file.isVideo) {
clone = this; ImageExpand.setupVideoCB(this);
ImageExpand.setupVideoControls(clone); return ImageExpand.setupVideo(this, !((_ref = this.origin.file.fullImage) != null ? _ref.paused : void 0) || this.origin.file.wasPlaying, this.file.fullImage.controls);
if (!clone.origin.file.fullImage.paused) {
$.queueTask(function() {
return Video.start(clone.file.fullImage);
});
} }
} } else if (ImageExpand.on && !this.isHidden && !this.isFetchedQuote && (Conf['Expand spoilers'] || !this.file.isSpoiler) && (Conf['Expand videos'] || !this.file.isVideo)) {
} else if (ImageExpand.on && !this.isHidden && (Conf['Expand spoilers'] || !this.file.isSpoiler) && (Conf['Expand videos'] || !this.file.isVideo)) { return ImageExpand.expand(this);
return ImageExpand.expand(this, null, true);
} }
}, },
cb: { cb: {
toggle: function(e) { toggle: function(e) {
var post, _ref; var file, post;
if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) {
return; return;
} }
post = Get.postFromNode(this); post = Get.postFromNode(this);
if (post.file.isExpanded && ((_ref = post.file.fullImage) != null ? _ref.controls : void 0)) { file = post.file;
if (file.isExpanded && file.isVideo && file.fullImage.controls) {
return; return;
} }
e.preventDefault(); e.preventDefault();
@ -10978,9 +10981,7 @@
if (ImageExpand.on = $.hasClass(ImageExpand.EAI, 'expand-all-shortcut')) { if (ImageExpand.on = $.hasClass(ImageExpand.EAI, 'expand-all-shortcut')) {
ImageExpand.EAI.className = 'contract-all-shortcut a-icon'; ImageExpand.EAI.className = 'contract-all-shortcut a-icon';
ImageExpand.EAI.title = 'Contract All Images'; ImageExpand.EAI.title = 'Contract All Images';
func = function(post) { func = ImageExpand.expand;
return ImageExpand.expand(post, null, true);
};
} else { } else {
ImageExpand.EAI.className = 'expand-all-shortcut a-icon'; ImageExpand.EAI.className = 'expand-all-shortcut a-icon';
ImageExpand.EAI.title = 'Expand All Images'; ImageExpand.EAI.title = 'Expand All Images';
@ -10995,122 +10996,207 @@
} }
}); });
}, },
playVideos: function(e) {
return g.posts.forEach(function(post) {
var file, video, visible, _i, _len, _ref;
_ref = [post].concat(__slice.call(post.clones));
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
post = _ref[_i];
file = post.file;
if (!(file && file.isVideo && file.isExpanded)) {
continue;
}
video = file.fullImage;
visible = Header.isNodeVisible(video);
if (visible && file.wasPlaying) {
delete file.wasPlaying;
video.play();
} else if (!visible && !video.paused) {
file.wasPlaying = true;
video.pause();
}
}
});
},
setFitness: function() { setFitness: function() {
return (this.checked ? $.addClass : $.rmClass)(doc, this.name.toLowerCase().replace(/\s+/g, '-')); return $[this.checked ? 'addClass' : 'rmClass'](doc, this.name.toLowerCase().replace(/\s+/g, '-'));
} }
}, },
toggle: function(post) { toggle: function(post) {
var headRect, left, root, top, x, y, _ref; var next;
if (!(post.file.isExpanded || $.hasClass(post.file.thumb, 'expanding'))) { if (!(post.file.isExpanding || post.file.isExpanded)) {
post.file.scrollIntoView = Conf['Scroll into view'];
ImageExpand.expand(post); ImageExpand.expand(post);
return; return;
} }
root = post.nodes.root; ImageExpand.contract(post);
_ref = (Conf['Advance on contract'] ? (function() { if (Conf['Advance on contract']) {
var next; next = post.nodes.root;
next = root;
while (next = $.x("following::div[contains(@class,'postContainer')][1]", next)) { while (next = $.x("following::div[contains(@class,'postContainer')][1]", next)) {
if ($('.stub', next) || next.offsetHeight === 0) { if (!($('.stub', next) || next.offsetHeight === 0)) {
continue; break;
}
return next;
}
return root;
})() : root).getBoundingClientRect(), top = _ref.top, left = _ref.left;
if (top < 0) {
y = top;
if (Conf['Fixed Header'] && !Conf['Bottom Header']) {
headRect = Header.bar.getBoundingClientRect();
y -= headRect.top + headRect.height;
} }
} }
if (left < 0) { if (next) {
x = -window.scrollX; return Header.scrollTo(next);
} }
if (x || y) {
window.scrollBy(x, y);
} }
return ImageExpand.contract(post);
}, },
contract: function(post) { contract: function(post) {
var cb, eventName, thumb, video, _ref; var bottom, cb, el, eventName, file, oldHeight, scrollY, top, x, _i, _len, _ref, _ref1;
thumb = post.file.thumb; file = post.file;
if (post.file.isVideo && (video = post.file.fullImage)) { if (el = file.fullImage) {
video.pause(); top = Header.getTopOf(el);
TrashQueue.add(video, post); bottom = top + el.getBoundingClientRect().height;
thumb.parentNode.href = video.src; oldHeight = d.body.clientHeight;
thumb.parentNode.target = '_blank'; scrollY = window.scrollY;
_ref = ImageExpand.videoCB;
for (eventName in _ref) {
cb = _ref[eventName];
$.off(video, eventName, cb);
}
$.rm(post.file.videoControls);
delete post.file.videoControls;
} }
$.rmClass(post.nodes.root, 'expanded-image'); $.rmClass(post.nodes.root, 'expanded-image');
$.rmClass(post.file.thumb, 'expanding'); $.rmClass(file.thumb, 'expanding');
delete post.file.isExpanding; if (file.videoControls) {
delete post.file.isExpanded; $.rm(file.videoControls);
if (post.file.isVideo && post.file.fullImage) {
return post.file.fullImage.pause();
} }
file.thumb.parentNode.href = file.URL;
file.thumb.parentNode.target = '_blank';
_ref = ['isExpanding', 'isExpanded', 'videoControls', 'wasPlaying', 'scrollIntoView'];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
x = _ref[_i];
delete file[x];
}
if (!el) {
return;
}
if (doc.contains(el)) {
if (bottom <= 0) {
window.scroll(0, scrollY + d.body.clientHeight - oldHeight);
} else {
Header.scrollToIfNeeded(post.nodes.root);
}
if (window.scrollX > 0) {
window.scroll(0, window.scrollY);
}
}
$.off(el, 'error', ImageExpand.error);
ImageCommon.pushCache(el);
if (file.isVideo) {
el.pause();
_ref1 = ImageExpand.videoCB;
for (eventName in _ref1) {
cb = _ref1[eventName];
$.off(el, eventName, cb);
}
}
if (Conf['Restart when Opened']) {
ImageCommon.rewind(file.thumb);
}
delete file.fullImage;
return $.queueTask(function() {
if (file.isExpanding || file.isExpanded) {
return;
}
$.rmClass(el, 'full-image');
if (el.id) {
return;
}
return $.rm(el);
});
}, },
expand: function(post, src, disableAutoplay) { expand: function(post, src) {
var el, isVideo, thumb, _ref; var el, file, isVideo, thumb, _ref;
_ref = post.file, thumb = _ref.thumb, isVideo = _ref.isVideo; file = post.file;
if (post.isHidden || post.file.isExpanded || $.hasClass(thumb, 'expanding')) { thumb = file.thumb, isVideo = file.isVideo;
if (post.isHidden || file.isExpanding || file.isExpanded) {
return; return;
} }
$.addClass(thumb, 'expanding'); $.addClass(thumb, 'expanding');
if (el = post.file.fullImage) { file.isExpanding = true;
el.className = 'full-image'; if (file.fullImage) {
el.style.cssText = ''; el = file.fullImage;
TrashQueue.remove(el); } else if (((_ref = ImageCommon.cache) != null ? _ref.dataset.fullID : void 0) === post.fullID) {
} else { el = file.fullImage = ImageCommon.popCache();
el = post.file.fullImage = $.el((isVideo ? 'video' : 'img'), {
className: 'full-image'
});
$.on(el, 'error', ImageExpand.error); $.on(el, 'error', ImageExpand.error);
el.src = src || post.file.URL; if (Conf['Restart when Opened'] && el.id !== 'ihover') {
if (isVideo) { ImageCommon.rewind(el);
el.loop = true;
} }
el.removeAttribute('id');
} else {
el = file.fullImage = $.el((isVideo ? 'video' : 'img'));
el.dataset.fullID = post.fullID;
$.on(el, 'error', ImageExpand.error);
el.src = src || file.URL;
} }
if (el !== thumb.nextSibling) { el.className = 'full-image';
$.after(thumb, el); $.after(thumb, el);
if (isVideo) {
if (Conf['Show Controls'] && !file.videoControls) {
file.videoControls = ImageExpand.videoControls.cloneNode(true);
$.add(file.text, file.videoControls);
} }
thumb.parentNode.removeAttribute('href');
thumb.parentNode.removeAttribute('target');
el.loop = true;
ImageExpand.setupVideoCB(post);
}
if (!isVideo) {
return $.asap((function() { return $.asap((function() {
return el.videoHeight || el.naturalHeight; return el.naturalHeight;
}), function() { }), function() {
return ImageExpand.completeExpand(post, disableAutoplay); return ImageExpand.completeExpand(post);
}); });
}, } else if (el.readyState >= el.HAVE_METADATA) {
completeExpand: function(post, disableAutoplay) { return ImageExpand.completeExpand(post);
var bottom; } else {
if (!$.hasClass(post.file.thumb, 'expanding')) { return $.on(el, 'loadedmetadata', function() {
return; return ImageExpand.completeExpand(post);
}
if (!post.nodes.root.parentNode) {
ImageExpand.completeExpand2(post);
return;
}
bottom = post.nodes.root.getBoundingClientRect().bottom;
return $.queueTask(function() {
ImageExpand.completeExpand2(post, disableAutoplay);
if (!(bottom <= 0)) {
return;
}
return window.scrollBy(0, post.nodes.root.getBoundingClientRect().bottom - bottom);
}); });
}
}, },
completeExpand2: function(post, disableAutoplay) { completeExpand: function(post) {
var bottom, file, imageBottom, oldHeight, scrollY;
file = post.file;
if (!file.isExpanding) {
return;
}
bottom = Header.getTopOf(file.thumb) + file.thumb.getBoundingClientRect().height;
oldHeight = d.body.clientHeight;
scrollY = window.scrollY;
$.addClass(post.nodes.root, 'expanded-image'); $.addClass(post.nodes.root, 'expanded-image');
$.rmClass(post.file.thumb, 'expanding'); $.rmClass(file.thumb, 'expanding');
post.file.isExpanded = true; file.isExpanded = true;
if (post.file.isVideo) { delete file.isExpanding;
ImageExpand.setupVideoControls(post); if (doc.contains(post.nodes.root) && bottom <= 0) {
return Video.configure(post.file.fullImage, disableAutoplay); window.scroll(window.scrollX, scrollY + d.body.clientHeight - oldHeight);
}
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) {
return ImageExpand.setupVideo(post, Conf['Autoplay'], Conf['Show Controls']);
}
},
setupVideo: function(post, playing, controls) {
var fullImage;
fullImage = post.file.fullImage;
if (!playing) {
fullImage.controls = controls;
return;
}
fullImage.controls = false;
$.asap((function() {
return doc.contains(fullImage);
}), function() {
if (!d.hidden && Header.isNodeVisible(fullImage)) {
return fullImage.play();
} else {
return post.file.wasPlaying = true;
}
});
if (controls) {
return ImageCommon.addControls(fullImage);
} }
}, },
videoCB: (function() { videoCB: (function() {
@ -11132,106 +11218,58 @@
}, },
mouseout: function(e) { mouseout: function(e) {
if (mousedown && e.clientX <= this.getBoundingClientRect().left) { if (mousedown && e.clientX <= this.getBoundingClientRect().left) {
return ImageExpand.contract(Get.postFromNode(this)); return ImageExpand.toggle(Get.postFromNode(this));
} }
}, },
click: function(e) { click: function(e) {
if (this.paused && !this.controls) { if (this.paused && !this.controls) {
this.play(); this.play();
return e.preventDefault(); return e.stopPropagation();
} }
} }
}; };
})(), })(),
setupVideoControls: function(post) { setupVideoCB: function(post) {
var cb, contract, eventName, file, thumb, video, _ref; var cb, eventName, file, _ref;
file = post.file; file = post.file;
thumb = file.thumb;
video = file.fullImage;
file.thumb.parentNode.removeAttribute('href');
file.thumb.parentNode.removeAttribute('target');
_ref = ImageExpand.videoCB; _ref = ImageExpand.videoCB;
for (eventName in _ref) { for (eventName in _ref) {
cb = _ref[eventName]; cb = _ref[eventName];
$.on(video, eventName, cb); $.on(file.fullImage, eventName, cb);
} }
file.videoControls = $.el('span', { if (file.videoControls) {
className: 'video-controls' return $.on(file.videoControls.firstElementChild, 'click', function() {
return ImageExpand.toggle(post);
}); });
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', function(e) {
return ImageExpand.contract(post);
});
$.add(file.videoControls, [$.tn('\u00A0'), contract]);
} }
return $.add(file.text, file.videoControls);
}, },
error: function() { error: function() {
var URL, error, post, src, timeoutID; var post;
post = Get.postFromNode(this); post = Get.postFromNode(this);
$.rm(this); $.rm(this);
delete post.file.isReady;
delete post.file.fullImage; delete post.file.fullImage;
if (!($.hasClass(post.file.thumb, 'expanding') || $.hasClass(post.nodes.root, 'expanded-image'))) { if (!(post.file.isExpanding || post.file.isExpanded)) {
return; return;
} }
if (ImageCommon.decodeError(this, post)) {
return ImageExpand.contract(post);
}
if (this.src.split('/')[2] !== 'i.4cdn.org') {
return ImageExpand.contract(post);
}
return ImageCommon.error(this, post, 10 * $.SECOND, function(URL) {
if (post.file.isExpanding || post.file.isExpanded) {
ImageExpand.contract(post); ImageExpand.contract(post);
if (this.error && this.error.code !== this.error.MEDIA_ERR_NETWORK) {
error = (function() {
switch (this.error.code) {
case 1:
return 'MEDIA_ERR_ABORTED';
case 3:
return 'MEDIA_ERR_DECODE';
case 4:
return 'MEDIA_ERR_SRC_NOT_SUPPORTED';
case 5:
return 'MEDIA_ERR_ENCRYPTED';
}
}).call(this);
post.file.error = $.el('div', {
textContent: "Playback error: " + error,
className: 'warning'
});
$.after(post.file.thumb, post.file.error);
return;
}
src = this.src.split('/');
if (src[2] === 'i.4cdn.org') {
URL = Redirect.to('file', {
boardID: src[3],
filename: src[4].replace(/\?.+$/, '')
});
if (URL) { if (URL) {
setTimeout(ImageExpand.expand, 10000, post, URL); return ImageExpand.expand(post, URL);
return;
}
if (g.DEAD || post.isDead || post.file.isDead) {
return;
} }
} }
timeoutID = setTimeout(ImageExpand.expand, 10000, post);
return $.ajax(this.src, {
onloadend: function() {
if (this.status !== 404) {
return;
}
clearTimeout(timeoutID);
return post.kill(true);
}
}, {
type: 'head'
}); });
}, },
menu: { menu: {
init: function() { init: function() {
var conf, createSubEntry, el, name, subEntries, _ref; var conf, createSubEntry, el, name, subEntries, _ref, _ref1;
if (g.VIEW === 'catalog' || !Conf['Image Expansion']) { if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Image Expansion'])) {
return; return;
} }
el = $.el('span', { el = $.el('span', {
@ -11240,9 +11278,9 @@
}); });
createSubEntry = ImageExpand.menu.createSubEntry; createSubEntry = ImageExpand.menu.createSubEntry;
subEntries = []; subEntries = [];
_ref = Config.imageExpansion; _ref1 = Config.imageExpansion;
for (name in _ref) { for (name in _ref1) {
conf = _ref[name]; conf = _ref1[name];
subEntries.push(createSubEntry(name, conf[1])); subEntries.push(createSubEntry(name, conf[1]));
} }
return Header.menu.addEntry({ return Header.menu.addEntry({
@ -11253,15 +11291,12 @@
}, },
createSubEntry: function(name, desc) { createSubEntry: function(name, desc) {
var input, label; var input, label;
label = $.el('label', { label = UI.checkbox(name, name);
innerHTML: "<input type=checkbox name='" + name + "'> " + name, label.title = desc;
title: desc
});
input = label.firstElementChild; input = label.firstElementChild;
if (name === 'Fit width' || name === 'Fit height') { if (name === 'Fit width' || name === 'Fit height') {
$.on(input, 'change', ImageExpand.cb.setFitness); $.on(input, 'change', ImageExpand.cb.setFitness);
} }
input.checked = Conf[name];
$.event('change', null, input); $.event('change', null, input);
$.on(input, 'change', $.cb.checked); $.on(input, 'change', $.cb.checked);
return { return {

View File

@ -1,6 +1,6 @@
ImageExpand = ImageExpand =
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Image Expansion'] return unless g.VIEW in ['index', 'thread'] and Conf['Image Expansion']
@EAI = $.el 'a', @EAI = $.el 'a',
id: 'img-controls' id: 'img-controls'
@ -10,6 +10,11 @@ ImageExpand =
$.on @EAI, 'click', ImageExpand.cb.toggleAll $.on @EAI, 'click', ImageExpand.cb.toggleAll
$.on d, 'scroll visibilitychange', @cb.playVideos
@videoControls = $.el 'span', className: 'video-controls'
$.extend @videoControls,
<%= html('\u00A0<a href="javascript:;" title="You can also contract the video by dragging it to the left.">contract</a>') %>
Header.addShortcut @EAI, true Header.addShortcut @EAI, true
Post.callbacks.push Post.callbacks.push
@ -17,33 +22,31 @@ ImageExpand =
cb: @node cb: @node
node: -> node: ->
return unless @file?.isImage or @file?.isVideo return unless @file and (@file.isImage or @file.isVideo)
{thumb} = @file $.on @file.thumb.parentNode, 'click', ImageExpand.cb.toggle
$.on thumb.parentNode, 'click', ImageExpand.cb.toggle
if @isClone if @isClone
if $.hasClass thumb, 'expanding' if @file.isExpanding
# If we clone a post where the image is still loading, # If we clone a post where the image is still loading,
# make it loading in the clone too. # make it loading in the clone too.
ImageExpand.contract @ ImageExpand.contract @
ImageExpand.expand @ ImageExpand.expand @
else if @file.isExpanded and @file.isVideo else if @file.isExpanded and @file.isVideo
clone = @ ImageExpand.setupVideoCB @
ImageExpand.setupVideoControls clone ImageExpand.setupVideo @, !@origin.file.fullImage?.paused or @origin.file.wasPlaying, @file.fullImage.controls
unless clone.origin.file.fullImage.paused
$.queueTask -> Video.start clone.file.fullImage
return else if ImageExpand.on and !@isHidden and !@isFetchedQuote and
else if ImageExpand.on and !@isHidden and
(Conf['Expand spoilers'] or !@file.isSpoiler) and (Conf['Expand spoilers'] or !@file.isSpoiler) and
(Conf['Expand videos'] or !@file.isVideo) (Conf['Expand videos'] or !@file.isVideo)
ImageExpand.expand @, null, true ImageExpand.expand @
cb: cb:
toggle: (e) -> toggle: (e) ->
return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0 return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0
post = Get.postFromNode @ 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() e.preventDefault()
ImageExpand.toggle post ImageExpand.toggle post
@ -62,7 +65,7 @@ ImageExpand =
if ImageExpand.on = $.hasClass ImageExpand.EAI, 'expand-all-shortcut' if ImageExpand.on = $.hasClass ImageExpand.EAI, 'expand-all-shortcut'
ImageExpand.EAI.className = 'contract-all-shortcut a-icon' ImageExpand.EAI.className = 'contract-all-shortcut a-icon'
ImageExpand.EAI.title = 'Contract All Images' ImageExpand.EAI.title = 'Contract All Images'
func = (post) -> ImageExpand.expand post, null, true func = ImageExpand.expand
else else
ImageExpand.EAI.className = 'expand-all-shortcut a-icon' ImageExpand.EAI.className = 'expand-all-shortcut a-icon'
ImageExpand.EAI.title = 'Expand All Images' ImageExpand.EAI.title = 'Expand All Images'
@ -72,96 +75,170 @@ ImageExpand =
toggle post for post in [post, post.clones...] toggle post for post in [post, post.clones...]
return return
playVideos: (e) ->
g.posts.forEach (post) ->
for post in [post, post.clones...]
{file} = post
continue unless file and file.isVideo and file.isExpanded
video = file.fullImage
visible = Header.isNodeVisible video
if visible and file.wasPlaying
delete file.wasPlaying
video.play()
else if !visible and !video.paused
file.wasPlaying = true
video.pause()
return
setFitness: -> setFitness: ->
(if @checked then $.addClass else $.rmClass) doc, @name.toLowerCase().replace /\s+/g, '-' $[if @checked then 'addClass' else 'rmClass'] doc, @name.toLowerCase().replace /\s+/g, '-'
toggle: (post) -> toggle: (post) ->
unless post.file.isExpanded or $.hasClass post.file.thumb, 'expanding' unless post.file.isExpanding or post.file.isExpanded
post.file.scrollIntoView = Conf['Scroll into view']
ImageExpand.expand post ImageExpand.expand post
return return
# Scroll back to the thumbnail when contracting the image
# to avoid being left miles away from the relevant post.
{root} = post.nodes
{top, left} = (if Conf['Advance on contract'] then do ->
next = root
while next = $.x "following::div[contains(@class,'postContainer')][1]", next
continue if $('.stub', next) or next.offsetHeight is 0
return next
root
else
root
).getBoundingClientRect()
if top < 0
y = top
if Conf['Fixed Header'] and not Conf['Bottom Header']
headRect = Header.bar.getBoundingClientRect()
y -= headRect.top + headRect.height
if left < 0
x = -window.scrollX
window.scrollBy x, y if x or y
ImageExpand.contract post ImageExpand.contract post
if Conf['Advance on contract']
next = post.nodes.root
while next = $.x "following::div[contains(@class,'postContainer')][1]", next
break unless $('.stub', next) or next.offsetHeight is 0
if next
Header.scrollTo next
contract: (post) -> contract: (post) ->
{thumb} = post.file {file} = post
if post.file.isVideo and video = post.file.fullImage
video.pause() if el = file.fullImage
TrashQueue.add video, post top = Header.getTopOf el
thumb.parentNode.href = video.src bottom = top + el.getBoundingClientRect().height
thumb.parentNode.target = '_blank' oldHeight = d.body.clientHeight
for eventName, cb of ImageExpand.videoCB {scrollY} = window
$.off video, eventName, cb
$.rm post.file.videoControls
delete post.file.videoControls
$.rmClass post.nodes.root, 'expanded-image' $.rmClass post.nodes.root, 'expanded-image'
$.rmClass post.file.thumb, 'expanding' $.rmClass file.thumb, 'expanding'
delete post.file.isExpanding $.rm file.videoControls if file.videoControls
delete post.file.isExpanded file.thumb.parentNode.href = file.URL
post.file.fullImage.pause() if post.file.isVideo and post.file.fullImage file.thumb.parentNode.target = '_blank'
for x in ['isExpanding', 'isExpanded', 'videoControls', 'wasPlaying', 'scrollIntoView']
delete file[x]
expand: (post, src, disableAutoplay) -> return unless el
# Do not expand images of hidden/filtered replies, or already expanded pictures.
{thumb, isVideo} = post.file if doc.contains el
return if post.isHidden or post.file.isExpanded or $.hasClass thumb, 'expanding' if bottom <= 0
$.addClass thumb, 'expanding' # For images entirely above us, scroll to remain in place.
if el = post.file.fullImage window.scroll 0, scrollY + d.body.clientHeight - oldHeight
# Expand already-loaded/ing picture.
el.className = 'full-image'
el.style.cssText = ''
TrashQueue.remove el
else else
el = post.file.fullImage = $.el (if isVideo then 'video' else 'img'), # For images not above us that would be moved above us, scroll to the thumbnail.
className: 'full-image' Header.scrollToIfNeeded post.nodes.root
$.on el, 'error', ImageExpand.error if window.scrollX > 0
el.src = src or post.file.URL # If we have scrolled right viewing an expanded image, return to the left.
if isVideo window.scroll 0, window.scrollY
el.loop = true
$.after thumb, el unless el is thumb.nextSibling
$.asap (-> el.videoHeight or el.naturalHeight), ->
ImageExpand.completeExpand post, disableAutoplay
completeExpand: (post, disableAutoplay) -> $.off el, 'error', ImageExpand.error
return unless $.hasClass post.file.thumb, 'expanding' # contracted before the image loaded ImageCommon.pushCache el
unless post.nodes.root.parentNode if file.isVideo
# Image might start/finish loading before the post is inserted. el.pause()
# Don't scroll when it's expanded in a QP for example. for eventName, cb of ImageExpand.videoCB
ImageExpand.completeExpand2 post $.off el, eventName, cb
return ImageCommon.rewind file.thumb if Conf['Restart when Opened']
{bottom} = post.nodes.root.getBoundingClientRect() delete file.fullImage
$.queueTask -> $.queueTask ->
ImageExpand.completeExpand2 post, disableAutoplay # XXX Work around Chrome/Chromium not firing mouseover on the thumbnail.
return unless bottom <= 0 return if file.isExpanding or file.isExpanded
window.scrollBy 0, post.nodes.root.getBoundingClientRect().bottom - bottom $.rmClass el, 'full-image'
return if el.id
$.rm el
expand: (post, src) ->
# Do not expand images of hidden/filtered replies, or already expanded pictures.
{file} = post
{thumb, isVideo} = file
return if post.isHidden or file.isExpanding or file.isExpanded
$.addClass thumb, 'expanding'
file.isExpanding = true
if file.fullImage
el = file.fullImage
else if ImageCommon.cache?.dataset.fullID is post.fullID
el = file.fullImage = ImageCommon.popCache()
$.on el, 'error', ImageExpand.error
ImageCommon.rewind el if Conf['Restart when Opened'] and el.id isnt 'ihover'
el.removeAttribute 'id'
else
el = file.fullImage = $.el (if isVideo then 'video' else 'img')
el.dataset.fullID = post.fullID
$.on el, 'error', ImageExpand.error
el.src = src or file.URL
el.className = 'full-image'
$.after thumb, 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_METADATA
ImageExpand.completeExpand post
else
$.on el, 'loadedmetadata', -> ImageExpand.completeExpand post
completeExpand: (post) ->
{file} = post
return unless file.isExpanding # contracted before the image loaded
bottom = Header.getTopOf(file.thumb) + file.thumb.getBoundingClientRect().height
oldHeight = d.body.clientHeight
{scrollY} = window
completeExpand2: (post, disableAutoplay) ->
$.addClass post.nodes.root, 'expanded-image' $.addClass post.nodes.root, 'expanded-image'
$.rmClass post.file.thumb, 'expanding' $.rmClass file.thumb, 'expanding'
post.file.isExpanded = true file.isExpanded = true
if post.file.isVideo delete file.isExpanding
ImageExpand.setupVideoControls post
Video.configure post.file.fullImage, disableAutoplay # Scroll to keep our place in the thread when images are expanded above us.
if doc.contains(post.nodes.root) and bottom <= 0
window.scroll window.scrollX, scrollY + 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
videoCB: do -> videoCB: do ->
# dragging to the left contracts the video # dragging to the left contracts the video
@ -169,98 +246,41 @@ ImageExpand =
mouseover: -> mousedown = false mouseover: -> mousedown = false
mousedown: (e) -> mousedown = true if e.button is 0 mousedown: (e) -> mousedown = true if e.button is 0
mouseup: (e) -> mousedown = false 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 mouseout: (e) -> ImageExpand.toggle(Get.postFromNode @) if mousedown and e.clientX <= @getBoundingClientRect().left
click: (e) -> click: (e) ->
if @paused and not @controls if @paused and not @controls
@play() @play()
e.preventDefault() e.stopPropagation()
setupVideoControls: (post) -> setupVideoCB: (post) ->
{file} = post {file} = post
{thumb} = file for eventName, cb of ImageExpand.videoCB
video = file.fullImage $.on file.fullImage, eventName, cb
if file.videoControls
# disable link to file so native controls can work $.on file.videoControls.firstElementChild, 'click', -> ImageExpand.toggle post
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
$.add file.videoControls, [$.tn('\u00A0'), contract]
$.add file.text, file.videoControls
error: -> error: ->
post = Get.postFromNode @ post = Get.postFromNode @
$.rm @ $.rm @
delete post.file.isReady
delete post.file.fullImage delete post.file.fullImage
# Images can error: # Images can error:
# - before the image started loading. # - before the image started loading.
# - after 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-expand if it was already contracted.
# Don't try to re-expend if it was already contracted. return unless post.file.isExpanding or post.file.isExpanded
return 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.contract post
ImageExpand.expand post, URL if URL
if @error and @error.code isnt @error.MEDIA_ERR_NETWORK # video
error = switch @error.code
when 1 then 'MEDIA_ERR_ABORTED'
when 3 then 'MEDIA_ERR_DECODE'
when 4 then 'MEDIA_ERR_SRC_NOT_SUPPORTED'
when 5 then 'MEDIA_ERR_ENCRYPTED'
post.file.error = $.el 'div',
textContent: "Playback error: #{error}"
className: 'warning'
$.after post.file.thumb, post.file.error
return
src = @src.split '/'
if src[2] is 'i.4cdn.org'
URL = Redirect.to 'file',
boardID: src[3]
filename: src[4].replace /\?.+$/, ''
if URL
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
<% } %>
menu: menu:
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Image Expansion'] return unless g.VIEW in ['index', 'thread'] and Conf['Image Expansion']
el = $.el 'span', el = $.el 'span',
textContent: 'Image Expansion' textContent: 'Image Expansion'
@ -277,13 +297,11 @@ ImageExpand =
subEntries: subEntries subEntries: subEntries
createSubEntry: (name, desc) -> createSubEntry: (name, desc) ->
label = $.el 'label', label = UI.checkbox name, name
innerHTML: "<input type=checkbox name='#{name}'> #{name}" label.title = desc
title: desc
input = label.firstElementChild input = label.firstElementChild
if name in ['Fit width', 'Fit height'] if name in ['Fit width', 'Fit height']
$.on input, 'change', ImageExpand.cb.setFitness $.on input, 'change', ImageExpand.cb.setFitness
input.checked = Conf[name]
$.event 'change', null, input $.event 'change', null, input
$.on input, 'change', $.cb.checked $.on input, 'change', $.cb.checked
el: label el: label