386 lines
12 KiB
JavaScript
386 lines
12 KiB
JavaScript
/*
|
|
* decaffeinate suggestions:
|
|
* DS101: Remove unnecessary use of Array.from
|
|
* DS102: Remove unnecessary code created because of implicit returns
|
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
|
|
*/
|
|
var ImageExpand = {
|
|
init() {
|
|
if (!(this.enabled = Conf['Image Expansion'] && ['index', 'thread'].includes(g.VIEW))) { return; }
|
|
|
|
this.EAI = $.el('a', {
|
|
className: 'expand-all-shortcut fa fa-expand',
|
|
textContent: 'EAI',
|
|
title: 'Expand All Images',
|
|
href: 'javascript:;'
|
|
}
|
|
);
|
|
|
|
$.on(this.EAI, 'click', this.cb.toggleAll);
|
|
Header.addShortcut('expand-all', this.EAI, 520);
|
|
$.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>"});
|
|
|
|
return Callbacks.Post.push({
|
|
name: 'Image Expansion',
|
|
cb: this.node
|
|
});
|
|
},
|
|
|
|
node() {
|
|
if (!this.file || (!this.file.isImage && !this.file.isVideo)) { return; }
|
|
$.on(this.file.thumbLink, 'click', ImageExpand.cb.toggle);
|
|
|
|
if (this.isClone) {
|
|
if (this.file.isExpanding) {
|
|
// If we clone a post where the image is still loading,
|
|
// make it loading in the clone too.
|
|
ImageExpand.contract(this);
|
|
return ImageExpand.expand(this);
|
|
|
|
} else if (this.file.isExpanded && this.file.isVideo) {
|
|
Volume.setup(this.file.fullImage);
|
|
ImageExpand.setupVideoCB(this);
|
|
return ImageExpand.setupVideo(this, !this.origin.file.fullImage?.paused || this.origin.file.wasPlaying, this.file.fullImage.controls);
|
|
}
|
|
|
|
} 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(e) {
|
|
if ($.modifiedClick(e)) { return; }
|
|
const post = Get.postFromNode(this);
|
|
const {file} = post;
|
|
if (file.isExpanded && ImageCommon.onControls(e)) { return; }
|
|
e.preventDefault();
|
|
if (!Conf['Autoplay'] && file.fullImage?.paused) {
|
|
return file.fullImage.play();
|
|
} else {
|
|
return ImageExpand.toggle(post);
|
|
}
|
|
},
|
|
|
|
toggleAll() {
|
|
let func;
|
|
$.event('CloseMenu');
|
|
const threadRoot = Nav.getThread();
|
|
const toggle = function(post) {
|
|
const {file} = post;
|
|
if (!file || (!file.isImage && !file.isVideo) || !doc.contains(post.nodes.root)) { return; }
|
|
if (ImageExpand.on &&
|
|
((!Conf['Expand spoilers'] && file.isSpoiler) ||
|
|
(!Conf['Expand videos'] && file.isVideo) ||
|
|
(Conf['Expand from here'] && (Header.getTopOf(file.thumb) < 0)) ||
|
|
(Conf['Expand thread only'] && (g.VIEW === 'index') && !threadRoot?.contains(file.thumb)))) {
|
|
return;
|
|
}
|
|
return $.queueTask(func, post);
|
|
};
|
|
|
|
if (ImageExpand.on = $.hasClass(ImageExpand.EAI, 'expand-all-shortcut')) {
|
|
ImageExpand.EAI.className = 'contract-all-shortcut fa fa-compress';
|
|
ImageExpand.EAI.title = 'Contract All Images';
|
|
func = ImageExpand.expand;
|
|
} else {
|
|
ImageExpand.EAI.className = 'expand-all-shortcut fa fa-expand';
|
|
ImageExpand.EAI.title = 'Expand All Images';
|
|
func = ImageExpand.contract;
|
|
}
|
|
|
|
return g.posts.forEach(function(post) {
|
|
for (post of [post, ...Array.from(post.clones)]) { toggle(post); }
|
|
});
|
|
},
|
|
|
|
playVideos() {
|
|
return g.posts.forEach(function(post) {
|
|
for (post of [post, ...Array.from(post.clones)]) {
|
|
var {file} = post;
|
|
if (!file || !file.isVideo || !file.isExpanded) { continue; }
|
|
|
|
var video = file.fullImage;
|
|
var visible = ($.hasAudio(video) && !video.muted) || Header.isNodeVisible(video);
|
|
if (visible && file.wasPlaying) {
|
|
delete file.wasPlaying;
|
|
video.play();
|
|
} else if (!visible && !video.paused) {
|
|
file.wasPlaying = true;
|
|
video.pause();
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
setFitness() {
|
|
return $[this.checked ? 'addClass' : 'rmClass'](doc, this.name.toLowerCase().replace(/\s+/g, '-'));
|
|
}
|
|
},
|
|
|
|
toggle(post) {
|
|
if (!post.file.isExpanding && !post.file.isExpanded) {
|
|
post.file.scrollIntoView = Conf['Scroll into view'];
|
|
ImageExpand.expand(post);
|
|
return;
|
|
}
|
|
|
|
ImageExpand.contract(post);
|
|
|
|
if (Conf['Advance on contract']) {
|
|
let next = post.nodes.root;
|
|
while ((next = $.x("following::div[contains(@class,'postContainer')][1]", next))) {
|
|
if (!$('.stub', next) && (next.offsetHeight !== 0)) { break; }
|
|
}
|
|
if (next) {
|
|
return Header.scrollTo(next);
|
|
}
|
|
}
|
|
},
|
|
|
|
contract(post) {
|
|
let bottom, el, oldHeight, scrollY;
|
|
const {file} = post;
|
|
|
|
if (el = file.fullImage) {
|
|
const top = Header.getTopOf(el);
|
|
bottom = top + el.getBoundingClientRect().height;
|
|
oldHeight = d.body.clientHeight;
|
|
({scrollY} = window);
|
|
}
|
|
|
|
$.rmClass(post.nodes.root, 'expanded-image');
|
|
$.rmClass(file.thumb, 'expanding');
|
|
$.rm(file.videoControls);
|
|
file.thumbLink.href = file.url;
|
|
file.thumbLink.target = '_blank';
|
|
for (var x of ['isExpanding', 'isExpanded', 'videoControls', 'wasPlaying', 'scrollIntoView']) {
|
|
delete file[x];
|
|
}
|
|
|
|
if (!el) { return; }
|
|
|
|
if (doc.contains(el)) {
|
|
if (bottom <= 0) {
|
|
// For images entirely above us, scroll to remain in place.
|
|
window.scrollBy(0, ((scrollY - window.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.scrollBy(-window.scrollX, 0);
|
|
}
|
|
}
|
|
|
|
$.off(el, 'error', ImageExpand.error);
|
|
ImageCommon.pushCache(el);
|
|
if (file.isVideo) {
|
|
ImageCommon.pause(el);
|
|
for (var eventName in ImageExpand.videoCB) {
|
|
var cb = ImageExpand.videoCB[eventName];
|
|
$.off(el, eventName, cb);
|
|
}
|
|
}
|
|
if (Conf['Restart when Opened']) { ImageCommon.rewind(file.thumb); }
|
|
delete file.fullImage;
|
|
return $.queueTask(function() {
|
|
// XXX Work around Chrome/Chromium not firing mouseover on the thumbnail.
|
|
if (file.isExpanding || file.isExpanded) { return; }
|
|
$.rmClass(el, 'full-image');
|
|
if (el.id) { return; }
|
|
return $.rm(el);
|
|
});
|
|
},
|
|
|
|
expand(post, src) {
|
|
// Do not expand images of hidden/filtered replies, or already expanded pictures.
|
|
let el;
|
|
const {file} = post;
|
|
const {thumb, thumbLink, isVideo} = file;
|
|
if (post.isHidden || file.isExpanding || file.isExpanded) { return; }
|
|
|
|
$.addClass(thumb, 'expanding');
|
|
file.isExpanding = true;
|
|
|
|
if (file.fullImage) {
|
|
el = file.fullImage;
|
|
} else if (ImageCommon.cache?.dataset.fileID === `${post.fullID}.${file.index}`) {
|
|
el = (file.fullImage = ImageCommon.popCache());
|
|
$.on(el, 'error', ImageExpand.error);
|
|
if (Conf['Restart when Opened'] && (el.id !== 'ihover')) { ImageCommon.rewind(el); }
|
|
el.removeAttribute('id');
|
|
} else {
|
|
el = (file.fullImage = $.el((isVideo ? 'video' : 'img')));
|
|
el.dataset.fileID = `${post.fullID}.${file.index}`;
|
|
$.on(el, 'error', ImageExpand.error);
|
|
el.src = src || file.url;
|
|
}
|
|
|
|
el.className = 'full-image';
|
|
$.after(thumb, el);
|
|
|
|
if (isVideo) {
|
|
// add contract link to file info
|
|
if (!file.videoControls) {
|
|
file.videoControls = ImageExpand.videoControls.cloneNode(true);
|
|
$.add(file.text, file.videoControls);
|
|
}
|
|
|
|
// disable link to file so native controls can work
|
|
thumbLink.removeAttribute('href');
|
|
thumbLink.removeAttribute('target');
|
|
|
|
el.loop = true;
|
|
Volume.setup(el);
|
|
ImageExpand.setupVideoCB(post);
|
|
}
|
|
|
|
if (!isVideo) {
|
|
return $.asap((() => el.naturalHeight), () => ImageExpand.completeExpand(post));
|
|
} else if (el.readyState >= el.HAVE_METADATA) {
|
|
return ImageExpand.completeExpand(post);
|
|
} else {
|
|
return $.on(el, 'loadedmetadata', () => ImageExpand.completeExpand(post));
|
|
}
|
|
},
|
|
|
|
completeExpand(post) {
|
|
const {file} = post;
|
|
if (!file.isExpanding) { return; } // contracted before the image loaded
|
|
|
|
const bottom = Header.getTopOf(file.thumb) + file.thumb.getBoundingClientRect().height;
|
|
const oldHeight = d.body.clientHeight;
|
|
const {scrollY} = window;
|
|
|
|
$.addClass(post.nodes.root, 'expanded-image');
|
|
$.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) && (bottom <= 0)) {
|
|
window.scrollBy(0, ((scrollY - window.scrollY) + d.body.clientHeight) - oldHeight);
|
|
}
|
|
|
|
// Scroll to display full image.
|
|
if (file.scrollIntoView) {
|
|
delete file.scrollIntoView;
|
|
const imageBottom = Math.min(doc.clientHeight - file.fullImage.getBoundingClientRect().bottom - 25, Header.getBottomOf(file.fullImage));
|
|
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(post, playing, controls) {
|
|
const {fullImage} = post.file;
|
|
if (!playing) {
|
|
fullImage.controls = controls;
|
|
return;
|
|
}
|
|
fullImage.controls = false;
|
|
$.asap((() => 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() {
|
|
// dragging to the left contracts the video
|
|
let mousedown = false;
|
|
return {
|
|
mouseover() { return mousedown = false; },
|
|
mousedown(e) { if (e.button === 0) { return mousedown = true; } },
|
|
mouseup(e) { if (e.button === 0) { return mousedown = false; } },
|
|
mouseout(e) { if (((e.buttons & 1) || mousedown) && (e.clientX <= this.getBoundingClientRect().left)) { return ImageExpand.toggle(Get.postFromNode(this)); } }
|
|
};
|
|
})(),
|
|
|
|
setupVideoCB(post) {
|
|
for (var eventName in ImageExpand.videoCB) {
|
|
var cb = ImageExpand.videoCB[eventName];
|
|
$.on(post.file.fullImage, eventName, cb);
|
|
}
|
|
if (post.file.videoControls) {
|
|
return $.on(post.file.videoControls.firstElementChild, 'click', () => ImageExpand.toggle(post));
|
|
}
|
|
},
|
|
|
|
error() {
|
|
const post = Get.postFromNode(this);
|
|
$.rm(this);
|
|
delete post.file.fullImage;
|
|
// Images can error:
|
|
// - before the image started loading.
|
|
// - after the image started loading.
|
|
// Don't try to re-expand if it was already contracted.
|
|
if (!post.file.isExpanding && !post.file.isExpanded) { return; }
|
|
if (ImageCommon.decodeError(this, post.file)) {
|
|
return ImageExpand.contract(post);
|
|
}
|
|
// Don't autoretry images from the archive.
|
|
if (ImageCommon.isFromArchive(this)) {
|
|
return ImageExpand.contract(post);
|
|
}
|
|
return ImageCommon.error(this, post, post.file, 10 * $.SECOND, function(URL) {
|
|
if (post.file.isExpanding || post.file.isExpanded) {
|
|
ImageExpand.contract(post);
|
|
if (URL) { return ImageExpand.expand(post, URL); }
|
|
}
|
|
});
|
|
},
|
|
|
|
menu: {
|
|
init() {
|
|
if (!ImageExpand.enabled) { return; }
|
|
|
|
const el = $.el('span', {
|
|
textContent: 'Image Expansion',
|
|
className: 'image-expansion-link'
|
|
}
|
|
);
|
|
|
|
const {createSubEntry} = ImageExpand.menu;
|
|
const subEntries = [];
|
|
for (var name in Config.imageExpansion) {
|
|
var conf = Config.imageExpansion[name];
|
|
subEntries.push(createSubEntry(name, conf[1]));
|
|
}
|
|
|
|
return Header.menu.addEntry({
|
|
el,
|
|
order: 105,
|
|
subEntries
|
|
});
|
|
},
|
|
|
|
createSubEntry(name, desc) {
|
|
const label = UI.checkbox(name, name);
|
|
label.title = desc;
|
|
const input = label.firstElementChild;
|
|
if (['Fit width', 'Fit height'].includes(name)) {
|
|
$.on(input, 'change', ImageExpand.cb.setFitness);
|
|
}
|
|
$.event('change', null, input);
|
|
$.on(input, 'change', $.cb.checked);
|
|
return {el: label};
|
|
}
|
|
}
|
|
};
|