diff --git a/builds/appchan-x.user.js b/builds/appchan-x.user.js
index 7d494f7dc..7ec9a870a 100644
--- a/builds/appchan-x.user.js
+++ b/builds/appchan-x.user.js
@@ -10886,7 +10886,8 @@
ImageExpand = {
init: function() {
- if (g.VIEW === 'catalog' || !Conf['Image Expansion']) {
+ var _ref;
+ if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Image Expansion'])) {
return;
}
this.EAI = $.el('a', {
@@ -10896,6 +10897,13 @@
href: 'javascript:;'
});
$.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: " contract"
+ });
Header.addShortcut(this.EAI, true);
return Post.callbacks.push({
name: 'Image Expansion',
@@ -10903,37 +10911,32 @@
});
},
node: function() {
- var clone, thumb, _ref, _ref1;
- if (!(((_ref = this.file) != null ? _ref.isImage : void 0) || ((_ref1 = this.file) != null ? _ref1.isVideo : void 0))) {
+ var _ref;
+ if (!(this.file && (this.file.isImage || this.file.isVideo))) {
return;
}
- thumb = this.file.thumb;
- $.on(thumb.parentNode, 'click', ImageExpand.cb.toggle);
+ $.on(this.file.thumb.parentNode, 'click', ImageExpand.cb.toggle);
if (this.isClone) {
- if ($.hasClass(thumb, 'expanding')) {
+ if (this.file.isExpanding) {
ImageExpand.contract(this);
- ImageExpand.expand(this);
+ return ImageExpand.expand(this);
} else if (this.file.isExpanded && this.file.isVideo) {
- clone = this;
- ImageExpand.setupVideoControls(clone);
- if (!clone.origin.file.fullImage.paused) {
- $.queueTask(function() {
- return Video.start(clone.file.fullImage);
- });
- }
+ ImageExpand.setupVideoCB(this);
+ return ImageExpand.setupVideo(this, !((_ref = this.origin.file.fullImage) != null ? _ref.paused : void 0) || this.origin.file.wasPlaying, this.file.fullImage.controls);
}
- } else if (ImageExpand.on && !this.isHidden && (Conf['Expand spoilers'] || !this.file.isSpoiler) && (Conf['Expand videos'] || !this.file.isVideo)) {
- return ImageExpand.expand(this, null, true);
+ } else if (ImageExpand.on && !this.isHidden && !this.isFetchedQuote && (Conf['Expand spoilers'] || !this.file.isSpoiler) && (Conf['Expand videos'] || !this.file.isVideo)) {
+ return ImageExpand.expand(this);
}
},
cb: {
toggle: function(e) {
- var post, _ref;
+ var file, post;
if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) {
return;
}
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;
}
e.preventDefault();
@@ -10956,9 +10959,7 @@
if (ImageExpand.on = $.hasClass(ImageExpand.EAI, 'expand-all-shortcut')) {
ImageExpand.EAI.className = 'contract-all-shortcut a-icon';
ImageExpand.EAI.title = 'Contract All Images';
- func = function(post) {
- return ImageExpand.expand(post, null, true);
- };
+ func = ImageExpand.expand;
} else {
ImageExpand.EAI.className = 'expand-all-shortcut a-icon';
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() {
- 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) {
- var headRect, left, root, top, x, y, _ref;
- if (!(post.file.isExpanded || $.hasClass(post.file.thumb, 'expanding'))) {
+ var next;
+ if (!(post.file.isExpanding || post.file.isExpanded)) {
+ post.file.scrollIntoView = Conf['Scroll into view'];
ImageExpand.expand(post);
return;
}
- root = post.nodes.root;
- _ref = (Conf['Advance on contract'] ? (function() {
- var next;
- next = root;
+ ImageExpand.contract(post);
+ if (Conf['Advance on contract']) {
+ next = post.nodes.root;
while (next = $.x("following::div[contains(@class,'postContainer')][1]", next)) {
- if ($('.stub', next) || next.offsetHeight === 0) {
- continue;
+ if (!($('.stub', next) || next.offsetHeight === 0)) {
+ 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 (next) {
+ return Header.scrollTo(next);
}
}
- if (left < 0) {
- x = -window.scrollX;
- }
- if (x || y) {
- window.scrollBy(x, y);
- }
- return ImageExpand.contract(post);
},
contract: function(post) {
- var cb, eventName, thumb, video, _ref;
- thumb = post.file.thumb;
- if (post.file.isVideo && (video = post.file.fullImage)) {
- video.pause();
- TrashQueue.add(video, post);
- thumb.parentNode.href = video.src;
- thumb.parentNode.target = '_blank';
- _ref = ImageExpand.videoCB;
- for (eventName in _ref) {
- cb = _ref[eventName];
- $.off(video, eventName, cb);
- }
- $.rm(post.file.videoControls);
- delete post.file.videoControls;
+ var bottom, cb, el, eventName, file, oldHeight, scrollY, top, x, _i, _len, _ref, _ref1;
+ file = post.file;
+ if (el = file.fullImage) {
+ top = Header.getTopOf(el);
+ bottom = top + el.getBoundingClientRect().height;
+ oldHeight = d.body.clientHeight;
+ scrollY = window.scrollY;
}
$.rmClass(post.nodes.root, 'expanded-image');
- $.rmClass(post.file.thumb, 'expanding');
- delete post.file.isExpanding;
- delete post.file.isExpanded;
- if (post.file.isVideo && post.file.fullImage) {
- return post.file.fullImage.pause();
+ $.rmClass(file.thumb, 'expanding');
+ if (file.videoControls) {
+ $.rm(file.videoControls);
}
+ 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) {
- var el, isVideo, thumb, _ref;
- _ref = post.file, thumb = _ref.thumb, isVideo = _ref.isVideo;
- if (post.isHidden || post.file.isExpanded || $.hasClass(thumb, 'expanding')) {
+ expand: function(post, src) {
+ var el, file, isVideo, thumb, _ref;
+ file = post.file;
+ thumb = file.thumb, isVideo = file.isVideo;
+ if (post.isHidden || file.isExpanding || file.isExpanded) {
return;
}
$.addClass(thumb, 'expanding');
- if (el = post.file.fullImage) {
- el.className = 'full-image';
- el.style.cssText = '';
- TrashQueue.remove(el);
- } else {
- el = post.file.fullImage = $.el((isVideo ? 'video' : 'img'), {
- className: 'full-image'
- });
+ file.isExpanding = true;
+ if (file.fullImage) {
+ el = file.fullImage;
+ } else if (((_ref = ImageCommon.cache) != null ? _ref.dataset.fullID : void 0) === post.fullID) {
+ el = file.fullImage = ImageCommon.popCache();
$.on(el, 'error', ImageExpand.error);
- el.src = src || post.file.URL;
- if (isVideo) {
- el.loop = true;
+ if (Conf['Restart when Opened'] && el.id !== 'ihover') {
+ ImageCommon.rewind(el);
}
+ 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) {
- $.after(thumb, el);
+ el.className = 'full-image';
+ $.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 el.naturalHeight;
+ }), function() {
+ return ImageExpand.completeExpand(post);
+ });
+ } else if (el.readyState >= el.HAVE_METADATA) {
+ return ImageExpand.completeExpand(post);
+ } else {
+ return $.on(el, 'loadedmetadata', function() {
+ return ImageExpand.completeExpand(post);
+ });
}
- return $.asap((function() {
- return el.videoHeight || el.naturalHeight;
- }), function() {
- return ImageExpand.completeExpand(post, disableAutoplay);
- });
},
- completeExpand: function(post, disableAutoplay) {
- var bottom;
- if (!$.hasClass(post.file.thumb, 'expanding')) {
+ completeExpand: function(post) {
+ var bottom, file, imageBottom, oldHeight, scrollY;
+ file = post.file;
+ if (!file.isExpanding) {
return;
}
- 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) {
+ bottom = Header.getTopOf(file.thumb) + file.thumb.getBoundingClientRect().height;
+ oldHeight = d.body.clientHeight;
+ scrollY = window.scrollY;
$.addClass(post.nodes.root, 'expanded-image');
- $.rmClass(post.file.thumb, 'expanding');
- post.file.isExpanded = true;
- if (post.file.isVideo) {
- ImageExpand.setupVideoControls(post);
- return Video.configure(post.file.fullImage, disableAutoplay);
+ $.rmClass(file.thumb, 'expanding');
+ file.isExpanded = true;
+ delete file.isExpanding;
+ if (doc.contains(post.nodes.root) && bottom <= 0) {
+ 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() {
@@ -11110,117 +11196,58 @@
},
mouseout: function(e) {
if (mousedown && e.clientX <= this.getBoundingClientRect().left) {
- return ImageExpand.contract(Get.postFromNode(this));
+ return ImageExpand.toggle(Get.postFromNode(this));
}
},
click: function(e) {
if (this.paused && !this.controls) {
this.play();
- return e.preventDefault();
+ return e.stopPropagation();
}
}
};
})(),
- setupVideoControls: function(post) {
- var cb, contract, eventName, file, thumb, video, _ref;
+ setupVideoCB: function(post) {
+ var cb, eventName, file, _ref;
file = post.file;
- thumb = file.thumb;
- video = file.fullImage;
- file.thumb.parentNode.removeAttribute('href');
- file.thumb.parentNode.removeAttribute('target');
_ref = ImageExpand.videoCB;
for (eventName in _ref) {
cb = _ref[eventName];
- $.on(video, eventName, cb);
+ $.on(file.fullImage, eventName, cb);
}
- 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.'
+ if (file.videoControls) {
+ return $.on(file.videoControls.firstElementChild, 'click', function() {
+ return ImageExpand.toggle(post);
});
- $.on(contract, 'click', function(e) {
- return ImageExpand.contract(post);
- });
- $.add(file.videoControls, [$.tn('\u00A0'), contract]);
}
- return $.add(file.text, file.videoControls);
},
error: function() {
- var URL, error, post, src, timeoutID;
+ var post;
post = Get.postFromNode(this);
$.rm(this);
- delete post.file.isReady;
delete post.file.fullImage;
- if (!($.hasClass(post.file.thumb, 'expanding') || $.hasClass(post.nodes.root, 'expanded-image'))) {
+ if (!(post.file.isExpanding || post.file.isExpanded)) {
return;
}
- 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;
+ if (ImageCommon.decodeError(this, post)) {
+ return ImageExpand.contract(post);
}
- src = this.src.split('/');
- if (src[2] === '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 || post.isDead || post.file.isDead) {
- return;
- }
+ if (this.src.split('/')[2] !== 'i.4cdn.org') {
+ return ImageExpand.contract(post);
}
- 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);
+ return ImageCommon.error(this, post, 10 * $.SECOND, function(URL) {
+ if (post.file.isExpanding || post.file.isExpanded) {
+ ImageExpand.contract(post);
+ if (URL) {
+ return ImageExpand.expand(post, URL);
}
}
});
},
menu: {
init: function() {
- var conf, createSubEntry, el, name, subEntries, _ref;
- if (g.VIEW === 'catalog' || !Conf['Image Expansion']) {
+ var conf, createSubEntry, el, name, subEntries, _ref, _ref1;
+ if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Image Expansion'])) {
return;
}
el = $.el('span', {
@@ -11229,9 +11256,9 @@
});
createSubEntry = ImageExpand.menu.createSubEntry;
subEntries = [];
- _ref = Config.imageExpansion;
- for (name in _ref) {
- conf = _ref[name];
+ _ref1 = Config.imageExpansion;
+ for (name in _ref1) {
+ conf = _ref1[name];
subEntries.push(createSubEntry(name, conf[1]));
}
return Header.menu.addEntry({
@@ -11242,15 +11269,12 @@
},
createSubEntry: function(name, desc) {
var input, label;
- label = $.el('label', {
- innerHTML: " " + name,
- title: desc
- });
+ label = UI.checkbox(name, name);
+ label.title = desc;
input = label.firstElementChild;
if (name === 'Fit width' || name === 'Fit height') {
$.on(input, 'change', ImageExpand.cb.setFitness);
}
- input.checked = Conf[name];
$.event('change', null, input);
$.on(input, 'change', $.cb.checked);
return {
diff --git a/builds/crx/script.js b/builds/crx/script.js
index 07b05489c..86528efd6 100644
--- a/builds/crx/script.js
+++ b/builds/crx/script.js
@@ -10908,7 +10908,8 @@
ImageExpand = {
init: function() {
- if (g.VIEW === 'catalog' || !Conf['Image Expansion']) {
+ var _ref;
+ if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Image Expansion'])) {
return;
}
this.EAI = $.el('a', {
@@ -10918,6 +10919,13 @@
href: 'javascript:;'
});
$.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: " contract"
+ });
Header.addShortcut(this.EAI, true);
return Post.callbacks.push({
name: 'Image Expansion',
@@ -10925,37 +10933,32 @@
});
},
node: function() {
- var clone, thumb, _ref, _ref1;
- if (!(((_ref = this.file) != null ? _ref.isImage : void 0) || ((_ref1 = this.file) != null ? _ref1.isVideo : void 0))) {
+ var _ref;
+ if (!(this.file && (this.file.isImage || this.file.isVideo))) {
return;
}
- thumb = this.file.thumb;
- $.on(thumb.parentNode, 'click', ImageExpand.cb.toggle);
+ $.on(this.file.thumb.parentNode, 'click', ImageExpand.cb.toggle);
if (this.isClone) {
- if ($.hasClass(thumb, 'expanding')) {
+ if (this.file.isExpanding) {
ImageExpand.contract(this);
- ImageExpand.expand(this);
+ return ImageExpand.expand(this);
} else if (this.file.isExpanded && this.file.isVideo) {
- clone = this;
- ImageExpand.setupVideoControls(clone);
- if (!clone.origin.file.fullImage.paused) {
- $.queueTask(function() {
- return Video.start(clone.file.fullImage);
- });
- }
+ ImageExpand.setupVideoCB(this);
+ return ImageExpand.setupVideo(this, !((_ref = this.origin.file.fullImage) != null ? _ref.paused : void 0) || this.origin.file.wasPlaying, this.file.fullImage.controls);
}
- } else if (ImageExpand.on && !this.isHidden && (Conf['Expand spoilers'] || !this.file.isSpoiler) && (Conf['Expand videos'] || !this.file.isVideo)) {
- return ImageExpand.expand(this, null, true);
+ } else if (ImageExpand.on && !this.isHidden && !this.isFetchedQuote && (Conf['Expand spoilers'] || !this.file.isSpoiler) && (Conf['Expand videos'] || !this.file.isVideo)) {
+ return ImageExpand.expand(this);
}
},
cb: {
toggle: function(e) {
- var post, _ref;
+ var file, post;
if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) {
return;
}
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;
}
e.preventDefault();
@@ -10978,9 +10981,7 @@
if (ImageExpand.on = $.hasClass(ImageExpand.EAI, 'expand-all-shortcut')) {
ImageExpand.EAI.className = 'contract-all-shortcut a-icon';
ImageExpand.EAI.title = 'Contract All Images';
- func = function(post) {
- return ImageExpand.expand(post, null, true);
- };
+ func = ImageExpand.expand;
} else {
ImageExpand.EAI.className = 'expand-all-shortcut a-icon';
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() {
- 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) {
- var headRect, left, root, top, x, y, _ref;
- if (!(post.file.isExpanded || $.hasClass(post.file.thumb, 'expanding'))) {
+ var next;
+ if (!(post.file.isExpanding || post.file.isExpanded)) {
+ post.file.scrollIntoView = Conf['Scroll into view'];
ImageExpand.expand(post);
return;
}
- root = post.nodes.root;
- _ref = (Conf['Advance on contract'] ? (function() {
- var next;
- next = root;
+ ImageExpand.contract(post);
+ if (Conf['Advance on contract']) {
+ next = post.nodes.root;
while (next = $.x("following::div[contains(@class,'postContainer')][1]", next)) {
- if ($('.stub', next) || next.offsetHeight === 0) {
- continue;
+ if (!($('.stub', next) || next.offsetHeight === 0)) {
+ 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 (next) {
+ return Header.scrollTo(next);
}
}
- if (left < 0) {
- x = -window.scrollX;
- }
- if (x || y) {
- window.scrollBy(x, y);
- }
- return ImageExpand.contract(post);
},
contract: function(post) {
- var cb, eventName, thumb, video, _ref;
- thumb = post.file.thumb;
- if (post.file.isVideo && (video = post.file.fullImage)) {
- video.pause();
- TrashQueue.add(video, post);
- thumb.parentNode.href = video.src;
- thumb.parentNode.target = '_blank';
- _ref = ImageExpand.videoCB;
- for (eventName in _ref) {
- cb = _ref[eventName];
- $.off(video, eventName, cb);
- }
- $.rm(post.file.videoControls);
- delete post.file.videoControls;
+ var bottom, cb, el, eventName, file, oldHeight, scrollY, top, x, _i, _len, _ref, _ref1;
+ file = post.file;
+ if (el = file.fullImage) {
+ top = Header.getTopOf(el);
+ bottom = top + el.getBoundingClientRect().height;
+ oldHeight = d.body.clientHeight;
+ scrollY = window.scrollY;
}
$.rmClass(post.nodes.root, 'expanded-image');
- $.rmClass(post.file.thumb, 'expanding');
- delete post.file.isExpanding;
- delete post.file.isExpanded;
- if (post.file.isVideo && post.file.fullImage) {
- return post.file.fullImage.pause();
+ $.rmClass(file.thumb, 'expanding');
+ if (file.videoControls) {
+ $.rm(file.videoControls);
}
+ 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) {
- var el, isVideo, thumb, _ref;
- _ref = post.file, thumb = _ref.thumb, isVideo = _ref.isVideo;
- if (post.isHidden || post.file.isExpanded || $.hasClass(thumb, 'expanding')) {
+ expand: function(post, src) {
+ var el, file, isVideo, thumb, _ref;
+ file = post.file;
+ thumb = file.thumb, isVideo = file.isVideo;
+ if (post.isHidden || file.isExpanding || file.isExpanded) {
return;
}
$.addClass(thumb, 'expanding');
- if (el = post.file.fullImage) {
- el.className = 'full-image';
- el.style.cssText = '';
- TrashQueue.remove(el);
- } else {
- el = post.file.fullImage = $.el((isVideo ? 'video' : 'img'), {
- className: 'full-image'
- });
+ file.isExpanding = true;
+ if (file.fullImage) {
+ el = file.fullImage;
+ } else if (((_ref = ImageCommon.cache) != null ? _ref.dataset.fullID : void 0) === post.fullID) {
+ el = file.fullImage = ImageCommon.popCache();
$.on(el, 'error', ImageExpand.error);
- el.src = src || post.file.URL;
- if (isVideo) {
- el.loop = true;
+ if (Conf['Restart when Opened'] && el.id !== 'ihover') {
+ ImageCommon.rewind(el);
}
+ 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) {
- $.after(thumb, el);
+ el.className = 'full-image';
+ $.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 el.naturalHeight;
+ }), function() {
+ return ImageExpand.completeExpand(post);
+ });
+ } else if (el.readyState >= el.HAVE_METADATA) {
+ return ImageExpand.completeExpand(post);
+ } else {
+ return $.on(el, 'loadedmetadata', function() {
+ return ImageExpand.completeExpand(post);
+ });
}
- return $.asap((function() {
- return el.videoHeight || el.naturalHeight;
- }), function() {
- return ImageExpand.completeExpand(post, disableAutoplay);
- });
},
- completeExpand: function(post, disableAutoplay) {
- var bottom;
- if (!$.hasClass(post.file.thumb, 'expanding')) {
+ completeExpand: function(post) {
+ var bottom, file, imageBottom, oldHeight, scrollY;
+ file = post.file;
+ if (!file.isExpanding) {
return;
}
- 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) {
+ bottom = Header.getTopOf(file.thumb) + file.thumb.getBoundingClientRect().height;
+ oldHeight = d.body.clientHeight;
+ scrollY = window.scrollY;
$.addClass(post.nodes.root, 'expanded-image');
- $.rmClass(post.file.thumb, 'expanding');
- post.file.isExpanded = true;
- if (post.file.isVideo) {
- ImageExpand.setupVideoControls(post);
- return Video.configure(post.file.fullImage, disableAutoplay);
+ $.rmClass(file.thumb, 'expanding');
+ file.isExpanded = true;
+ delete file.isExpanding;
+ if (doc.contains(post.nodes.root) && bottom <= 0) {
+ 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() {
@@ -11132,106 +11218,58 @@
},
mouseout: function(e) {
if (mousedown && e.clientX <= this.getBoundingClientRect().left) {
- return ImageExpand.contract(Get.postFromNode(this));
+ return ImageExpand.toggle(Get.postFromNode(this));
}
},
click: function(e) {
if (this.paused && !this.controls) {
this.play();
- return e.preventDefault();
+ return e.stopPropagation();
}
}
};
})(),
- setupVideoControls: function(post) {
- var cb, contract, eventName, file, thumb, video, _ref;
+ setupVideoCB: function(post) {
+ var cb, eventName, file, _ref;
file = post.file;
- thumb = file.thumb;
- video = file.fullImage;
- file.thumb.parentNode.removeAttribute('href');
- file.thumb.parentNode.removeAttribute('target');
_ref = ImageExpand.videoCB;
for (eventName in _ref) {
cb = _ref[eventName];
- $.on(video, eventName, cb);
+ $.on(file.fullImage, eventName, cb);
}
- 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.'
+ if (file.videoControls) {
+ return $.on(file.videoControls.firstElementChild, 'click', function() {
+ return ImageExpand.toggle(post);
});
- $.on(contract, 'click', function(e) {
- return ImageExpand.contract(post);
- });
- $.add(file.videoControls, [$.tn('\u00A0'), contract]);
}
- return $.add(file.text, file.videoControls);
},
error: function() {
- var URL, error, post, src, timeoutID;
+ var post;
post = Get.postFromNode(this);
$.rm(this);
- delete post.file.isReady;
delete post.file.fullImage;
- if (!($.hasClass(post.file.thumb, 'expanding') || $.hasClass(post.nodes.root, 'expanded-image'))) {
+ if (!(post.file.isExpanding || post.file.isExpanded)) {
return;
}
- 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;
+ if (ImageCommon.decodeError(this, post)) {
+ return ImageExpand.contract(post);
}
- src = this.src.split('/');
- if (src[2] === '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 || post.isDead || post.file.isDead) {
- return;
- }
+ if (this.src.split('/')[2] !== 'i.4cdn.org') {
+ return ImageExpand.contract(post);
}
- timeoutID = setTimeout(ImageExpand.expand, 10000, post);
- return $.ajax(this.src, {
- onloadend: function() {
- if (this.status !== 404) {
- return;
+ return ImageCommon.error(this, post, 10 * $.SECOND, function(URL) {
+ if (post.file.isExpanding || post.file.isExpanded) {
+ ImageExpand.contract(post);
+ if (URL) {
+ return ImageExpand.expand(post, URL);
}
- clearTimeout(timeoutID);
- return post.kill(true);
}
- }, {
- type: 'head'
});
},
menu: {
init: function() {
- var conf, createSubEntry, el, name, subEntries, _ref;
- if (g.VIEW === 'catalog' || !Conf['Image Expansion']) {
+ var conf, createSubEntry, el, name, subEntries, _ref, _ref1;
+ if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Image Expansion'])) {
return;
}
el = $.el('span', {
@@ -11240,9 +11278,9 @@
});
createSubEntry = ImageExpand.menu.createSubEntry;
subEntries = [];
- _ref = Config.imageExpansion;
- for (name in _ref) {
- conf = _ref[name];
+ _ref1 = Config.imageExpansion;
+ for (name in _ref1) {
+ conf = _ref1[name];
subEntries.push(createSubEntry(name, conf[1]));
}
return Header.menu.addEntry({
@@ -11253,15 +11291,12 @@
},
createSubEntry: function(name, desc) {
var input, label;
- label = $.el('label', {
- innerHTML: " " + name,
- title: desc
- });
+ label = UI.checkbox(name, name);
+ label.title = desc;
input = label.firstElementChild;
if (name === 'Fit width' || name === 'Fit height') {
$.on(input, 'change', ImageExpand.cb.setFitness);
}
- input.checked = Conf[name];
$.event('change', null, input);
$.on(input, 'change', $.cb.checked);
return {
diff --git a/src/Images/ImageExpand.coffee b/src/Images/ImageExpand.coffee
index 8fde698fb..b46d879ef 100644
--- a/src/Images/ImageExpand.coffee
+++ b/src/Images/ImageExpand.coffee
@@ -1,15 +1,20 @@
ImageExpand =
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',
- id: 'img-controls'
+ id: 'img-controls'
className: 'expand-all-shortcut a-icon'
title: 'Expand All Images'
href: 'javascript:;'
$.on @EAI, 'click', ImageExpand.cb.toggleAll
+ $.on d, 'scroll visibilitychange', @cb.playVideos
+ @videoControls = $.el 'span', className: 'video-controls'
+ $.extend @videoControls,
+ <%= html('\u00A0contract') %>
+
Header.addShortcut @EAI, true
Post.callbacks.push
@@ -17,33 +22,31 @@ ImageExpand =
cb: @node
node: ->
- return unless @file?.isImage or @file?.isVideo
- {thumb} = @file
- $.on thumb.parentNode, 'click', ImageExpand.cb.toggle
+ return unless @file and (@file.isImage or @file.isVideo)
+ $.on @file.thumb.parentNode, 'click', ImageExpand.cb.toggle
+
if @isClone
- if $.hasClass thumb, 'expanding'
+ if @file.isExpanding
# If we clone a post where the image is still loading,
# make it loading in the clone too.
ImageExpand.contract @
ImageExpand.expand @
else if @file.isExpanded and @file.isVideo
- clone = @
- ImageExpand.setupVideoControls clone
- unless clone.origin.file.fullImage.paused
- $.queueTask -> Video.start clone.file.fullImage
+ ImageExpand.setupVideoCB @
+ ImageExpand.setupVideo @, !@origin.file.fullImage?.paused or @origin.file.wasPlaying, @file.fullImage.controls
- return
-
- else if ImageExpand.on and !@isHidden and
+ else if ImageExpand.on and !@isHidden and !@isFetchedQuote and
(Conf['Expand spoilers'] or !@file.isSpoiler) and
(Conf['Expand videos'] or !@file.isVideo)
- ImageExpand.expand @, null, true
+ ImageExpand.expand @
+
cb:
toggle: (e) ->
return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0
post = Get.postFromNode @
- return if post.file.isExpanded and post.file.fullImage?.controls
+ {file} = post
+ return if file.isExpanded and file.isVideo and file.fullImage.controls
e.preventDefault()
ImageExpand.toggle post
@@ -62,7 +65,7 @@ ImageExpand =
if ImageExpand.on = $.hasClass ImageExpand.EAI, 'expand-all-shortcut'
ImageExpand.EAI.className = 'contract-all-shortcut a-icon'
ImageExpand.EAI.title = 'Contract All Images'
- func = (post) -> ImageExpand.expand post, null, true
+ func = ImageExpand.expand
else
ImageExpand.EAI.className = 'expand-all-shortcut a-icon'
ImageExpand.EAI.title = 'Expand All Images'
@@ -72,96 +75,170 @@ ImageExpand =
toggle post for post in [post, post.clones...]
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: ->
- (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) ->
- 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
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
+ 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) ->
- {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
+ {file} = post
+
+ if el = file.fullImage
+ top = Header.getTopOf el
+ bottom = top + el.getBoundingClientRect().height
+ oldHeight = d.body.clientHeight
+ {scrollY} = window
+
$.rmClass post.nodes.root, 'expanded-image'
- $.rmClass post.file.thumb, 'expanding'
- delete post.file.isExpanding
- delete post.file.isExpanded
- post.file.fullImage.pause() if post.file.isVideo and post.file.fullImage
+ $.rmClass file.thumb, 'expanding'
+ $.rm file.videoControls if file.videoControls
+ file.thumb.parentNode.href = file.URL
+ file.thumb.parentNode.target = '_blank'
+ for x in ['isExpanding', 'isExpanded', 'videoControls', 'wasPlaying', 'scrollIntoView']
+ delete file[x]
- 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 el = post.file.fullImage
- # Expand already-loaded/ing picture.
- el.className = 'full-image'
- el.style.cssText = ''
- TrashQueue.remove el
- else
- el = post.file.fullImage = $.el (if isVideo then 'video' else 'img'),
- className: 'full-image'
- $.on el, 'error', ImageExpand.error
- el.src = src or post.file.URL
- if isVideo
- el.loop = true
- $.after thumb, el unless el is thumb.nextSibling
- $.asap (-> el.videoHeight or el.naturalHeight), ->
- ImageExpand.completeExpand post, disableAutoplay
+ return unless el
- 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()
+ if doc.contains el
+ if bottom <= 0
+ # For images entirely above us, scroll to remain in place.
+ window.scroll 0, scrollY + d.body.clientHeight - oldHeight
+ else
+ # For images not above us that would be moved above us, scroll to the thumbnail.
+ Header.scrollToIfNeeded post.nodes.root
+ if window.scrollX > 0
+ # If we have scrolled right viewing an expanded image, return to the left.
+ window.scroll 0, window.scrollY
+
+ $.off el, 'error', ImageExpand.error
+ ImageCommon.pushCache el
+ if file.isVideo
+ el.pause()
+ for eventName, cb of ImageExpand.videoCB
+ $.off el, eventName, cb
+ ImageCommon.rewind file.thumb if Conf['Restart when Opened']
+ delete file.fullImage
$.queueTask ->
- ImageExpand.completeExpand2 post, disableAutoplay
- return unless bottom <= 0
- window.scrollBy 0, post.nodes.root.getBoundingClientRect().bottom - bottom
+ # XXX Work around Chrome/Chromium not firing mouseover on the thumbnail.
+ return if file.isExpanding or file.isExpanded
+ $.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'
- $.rmClass post.file.thumb, 'expanding'
- post.file.isExpanded = true
- if post.file.isVideo
- ImageExpand.setupVideoControls post
- Video.configure post.file.fullImage, disableAutoplay
+ $.rmClass file.thumb, 'expanding'
+ file.isExpanded = true
+ delete file.isExpanding
+
+ # 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 ->
# dragging to the left contracts the video
@@ -169,98 +246,41 @@ ImageExpand =
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
+ mouseout: (e) -> ImageExpand.toggle(Get.postFromNode @) if mousedown and e.clientX <= @getBoundingClientRect().left
click: (e) ->
if @paused and not @controls
@play()
- e.preventDefault()
+ e.stopPropagation()
- 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
- $.add file.videoControls, [$.tn('\u00A0'), contract]
- $.add file.text, file.videoControls
+ setupVideoCB: (post) ->
+ {file} = post
+ for eventName, cb of ImageExpand.videoCB
+ $.on file.fullImage, eventName, cb
+ if file.videoControls
+ $.on file.videoControls.firstElementChild, 'click', -> ImageExpand.toggle post
error: ->
post = Get.postFromNode @
$.rm @
- delete post.file.isReady
delete post.file.fullImage
# Images can error:
# - before the image started loading.
# - after the image started loading.
- unless $.hasClass(post.file.thumb, 'expanding') or $.hasClass post.nodes.root, 'expanded-image'
- # Don't try to re-expend if it was already contracted.
- return
- ImageExpand.contract post
-
- 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
- <% } %>
+ # Don't try to re-expand if it was already contracted.
+ return unless post.file.isExpanding or post.file.isExpanded
+ if ImageCommon.decodeError @, post
+ return ImageExpand.contract post
+ # Don't autoretry images from the archive.
+ unless @src.split('/')[2] is 'i.4cdn.org'
+ return ImageExpand.contract post
+ ImageCommon.error @, post, 10 * $.SECOND, (URL) ->
+ if post.file.isExpanding or post.file.isExpanded
+ ImageExpand.contract post
+ ImageExpand.expand post, URL if URL
menu:
init: ->
- return if g.VIEW is 'catalog' or !Conf['Image Expansion']
+ return unless g.VIEW in ['index', 'thread'] and Conf['Image Expansion']
el = $.el 'span',
textContent: 'Image Expansion'
@@ -277,13 +297,11 @@ ImageExpand =
subEntries: subEntries
createSubEntry: (name, desc) ->
- label = $.el 'label',
- innerHTML: " #{name}"
- title: desc
+ label = UI.checkbox name, name
+ label.title = desc
input = label.firstElementChild
if name in ['Fit width', 'Fit height']
$.on input, 'change', ImageExpand.cb.setFitness
- input.checked = Conf[name]
$.event 'change', null, input
$.on input, 'change', $.cb.checked
el: label