Gallery.coffee and ImageCommon.coffee

This commit is contained in:
Zixaphir 2015-01-09 17:44:03 -07:00
parent 28f694591d
commit bc67db84af
5 changed files with 986 additions and 397 deletions

View File

@ -115,7 +115,7 @@
'use strict';
(function() {
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CrossOrigin, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, E, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, Linkify, Main, MarkNewIPs, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, TrashQueue, UI, Unread, Video, c, d, doc, editMascot, editTheme, g, userNavigation,
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CrossOrigin, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, E, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageCommon, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, Linkify, Main, MarkNewIPs, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, TrashQueue, UI, Unread, Video, c, d, doc, editMascot, editTheme, g, userNavigation,
__slice = [].slice,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
__hasProp = {}.hasOwnProperty,
@ -10337,8 +10337,8 @@
Gallery = {
init: function() {
var el;
if (g.BOARD === 'f' || !Conf['Gallery']) {
var el, _ref;
if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Gallery']) || g.BOARD === 'f') {
return;
}
el = $.el('a', {
@ -10360,7 +10360,7 @@
return;
}
if (Gallery.nodes) {
Gallery.generateThumb($('.file', this.nodes.root));
Gallery.generateThumb(this);
Gallery.nodes.total.textContent = Gallery.images.length;
}
if (!Conf['Image Expansion']) {
@ -10368,14 +10368,30 @@
}
},
build: function(image) {
var cb, createSubEntry, dialog, el, file, i, key, menuButton, name, nodes, value, _i, _len, _ref, _ref1;
var candidate, cb, dialog, entry, file, key, menuButton, nodes, post, thumb, value, _i, _j, _len, _len1, _ref, _ref1, _ref2;
if (Conf['Fullscreen Gallery']) {
$.one(d, 'fullscreenchange mozfullscreenchange webkitfullscreenchange', function() {
return $.on(d, 'fullscreenchange mozfullscreenchange webkitfullscreenchange', cb.close);
});
if (typeof doc.mozRequestFullScreen === "function") {
doc.mozRequestFullScreen();
}
if (typeof doc.webkitRequestFullScreen === "function") {
doc.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
}
}
Gallery.images = [];
nodes = Gallery.nodes = {};
Gallery.fullIDs = {};
Gallery.slideshow = false;
nodes.el = dialog = $.el('div', {
id: 'a-gallery',
innerHTML: "<div class=gal-viewport>\n <span class=gal-buttons>\n <a class=\"menu-button\" href=\"javascript:;\"><i></i></a>\n <a href=javascript:; class=gal-close>×</a>\n </span>\n <a class=gal-name target=\"_blank\"></a>\n <span class=gal-count>\n <span class='count'></span> / <span class='total'></span>\n </span>\n <div class=gal-prev></div>\n <div class=gal-image>\n <a href=javascript:;><img></a>\n </div>\n <div class=gal-next></div>\n</div>\n<div class=gal-thumbnails></div>"
id: 'a-gallery'
});
$.extend(dialog, {
innerHTML: "<div class=\"gal-viewport\">\r<span class=\"gal-buttons\">\r<a href=\"javascript:;\" class=\"gal-start\" title=\"Start slideshow (S to toggle)\"><i></i></a>\r<a href=\"javascript:;\" class=\"gal-stop\" title=\"Stop slideshow (S to toggle)\"><i></i></a>\r<a href=\"javascript:;\" class=\"menu-button\"><i></i></a>\r<a href=\"javascript:;\" class=\"gal-close\">×</a>\r</span>\r<a class=\"gal-name\" target=\"_blank\"></a>\r<span class=\"gal-count\">\r<span class=\"count\"></span> / <span class=\"total\"></span>\r</span>\r<div class=\"gal-prev\"></div>\r<div class=\"gal-image\">\r<a href=\"javascript:;\"><img></a>\r</div>\r<div class=\"gal-next\"></div>\r</div>\r<div class=\"gal-thumbnails\"></div>"
});
_ref = {
buttons: '.gal-buttons',
frame: '.gal-image',
name: '.gal-name',
count: '.count',
@ -10389,58 +10405,71 @@
nodes[key] = $(value, dialog);
}
menuButton = $('.menu-button', dialog);
nodes.menu = new UI.Menu();
nodes.menu = new UI.Menu('gallery');
cb = Gallery.cb;
$.on(nodes.frame, 'click', cb.blank);
$.on(nodes.next, 'click', cb.advance);
$.on(nodes.next, 'click', cb.click);
$.on($('.gal-prev', dialog), 'click', cb.prev);
$.on($('.gal-next', dialog), 'click', cb.next);
$.on($('.gal-start', dialog), 'click', cb.start);
$.on($('.gal-stop', dialog), 'click', cb.stop);
$.on($('.gal-close', dialog), 'click', cb.close);
$.on(menuButton, 'click', function(e) {
return nodes.menu.toggle(e, this, g);
});
createSubEntry = Gallery.menu.createSubEntry;
for (name in Config.gallery) {
el = createSubEntry(name).el;
nodes.menu.addEntry({
el: el,
order: 0
});
_ref1 = Gallery.menu.createSubEntries();
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
entry = _ref1[_i];
entry.order = 0;
nodes.menu.addEntry(entry);
}
$.on(d, 'keydown', cb.keybinds);
$.off(d, 'keydown', Keybinds.keydown);
_ref1 = $$('.post .file');
for (i = _i = 0, _len = _ref1.length; _i < _len; i = ++_i) {
file = _ref1[i];
if (!$('.fileDeletedRes, .fileDeleted', file)) {
Gallery.generateThumb(file);
if (Conf['Keybinds']) {
$.off(d, 'keydown', Keybinds.keydown);
}
_ref2 = $$('.post .file');
for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
file = _ref2[_j];
post = Get.postFromNode(file);
if (post.file.isDead) {
continue;
}
Gallery.generateThumb(post);
if (!image && Gallery.fullIDs[post.fullID]) {
candidate = post.file.thumb.parentNode;
if (Header.getTopOf(candidate) + candidate.getBoundingClientRect().height >= 0) {
image = candidate;
}
}
}
$.addClass(doc, 'gallery-open');
$.add(d.body, dialog);
nodes.thumbs.scrollTop = 0;
nodes.current.parentElement.scrollTop = 0;
Gallery.cb.open.call(image ? $("[href*='" + image.pathname + "']", nodes.thumbs) : Gallery.images[0]);
d.body.style.overflow = 'hidden';
return nodes.total.textContent = i;
if (image) {
thumb = $("[href='" + image.href + "']", nodes.thumbs);
}
thumb || (thumb = Gallery.images[Gallery.images.length - 1]);
if (thumb) {
Gallery.open(thumb);
}
doc.style.overflow = 'hidden';
return nodes.total.textContent = Gallery.images.length;
},
generateThumb: function(file) {
var post, thumb, thumbImg, title;
post = Get.postFromNode(file);
if (!(post.file && (post.file.isImage || post.file.isVideo || Conf['PDF in Gallery']))) {
generateThumb: function(post) {
var thumb, thumbImg, _ref, _ref1;
if (post.isClone || post.isHidden && !(((_ref = post.file) != null ? _ref.isImage : void 0) || ((_ref1 = post.file) != null ? _ref1.isVideo : void 0) || Conf['PDF in Gallery'])) {
return;
}
title = ($('.fileText a', file)).textContent;
Gallery.fullIDs[post.fullID] = true;
thumb = $.el('a', {
className: 'gal-thumb',
href: post.file.URL,
target: '_blank',
title: title
title: post.file.name
});
thumb.dataset.id = Gallery.images.length;
thumb.dataset.post = post.fullID;
if (post.file.isVideo) {
thumb.dataset.isVideo = true;
}
thumbImg = post.file.thumb.cloneNode(false);
thumbImg.style.cssText = '';
$.add(thumb, thumbImg);
@ -10448,6 +10477,110 @@
Gallery.images.push(thumb);
return $.add(Gallery.nodes.thumbs, thumb);
},
open: function(thumb) {
var el, elType, file, name, newID, nodes, oldID, post, slideshow, _base, _ref;
nodes = Gallery.nodes;
name = nodes.name;
oldID = +nodes.current.dataset.id;
newID = +thumb.dataset.id;
slideshow = Gallery.slideshow && (newID > oldID || (oldID === Gallery.images.length - 1 && newID === 0));
if (el = $('.gal-highlight', nodes.thumbs)) {
$.rmClass(el, 'gal-highlight');
}
$.addClass(thumb, 'gal-highlight');
elType = /\.webm$/.test(thumb.href) ? 'video' : /\.pdf$/.test(thumb.href) ? 'iframe' : 'image';
$[elType === 'iframe' ? 'addClass' : 'rmClass'](doc, 'gal-pdf');
file = $.el(elType, {
title: name.download = name.textContent = thumb.title
});
$.on(file, 'error', (function(_this) {
return function() {
return Gallery.error(file, thumb);
};
})(this));
file.src = name.href = thumb.href;
$.extend(file.dataset, thumb.dataset);
if (!nodes.current.error) {
if (typeof (_base = nodes.current).pause === "function") {
_base.pause();
}
}
$.replace(nodes.current, file);
if (elType === 'video') {
file.loop = true;
if (Conf['Autoplay']) {
file.play();
}
if (Conf['Show Controls']) {
ImageCommon.addControls(file);
}
}
nodes.count.textContent = +thumb.dataset.id + 1;
nodes.current = file;
nodes.frame.scrollTop = 0;
nodes.next.focus();
if (slideshow) {
Gallery.setupTimer();
} else {
Gallery.cb.stop();
}
if (Conf['Scroll to Post'] && (post = (_ref = (post = g.posts[file.dataset.post])) != null ? _ref.nodes.root : void 0)) {
Header.scrollTo(post);
}
return nodes.thumbs.scrollTop = thumb.offsetTop + thumb.offsetHeight / 2 - nodes.thumbs.clientHeight / 2;
},
error: function(file, thumb) {
var _ref;
if (((_ref = file.error) != null ? _ref.code : void 0) === MediaError.MEDIA_ERR_DECODE) {
return new Notice('error', 'Corrupt or unplayable video', 30);
}
if (file.src.split('/')[2] !== 'i.4cdn.org') {
return;
}
return ImageCommon.error(file, g.posts[file.dataset.post], null, function(URL) {
if (!URL) {
return;
}
thumb.href = URL;
if (Gallery.nodes.current === file) {
return file.src = URL;
}
});
},
cleanupTimer: function() {
var current;
clearTimeout(Gallery.timeoutID);
current = Gallery.nodes.current;
$.off(current, 'canplaythrough load', Gallery.startTimer);
return $.off(current, 'ended', Gallery.cb.next);
},
startTimer: function() {
return Gallery.timeoutID = setTimeout(Gallery.checkTimer, Gallery.delay * $.SECOND);
},
setupTimer: function() {
var current, isVideo;
Gallery.cleanupTimer();
current = Gallery.nodes.current;
isVideo = current.nodeName === 'VIDEO';
if (isVideo) {
current.play();
}
if ((isVideo ? current.readyState >= 4 : current.complete) || current.nodeName === 'IFRAME') {
return Gallery.startTimer();
} else {
return $.on(current, (isVideo ? 'canplaythrough' : 'load'), Gallery.startTimer);
}
},
checkTimer: function() {
var current;
current = Gallery.nodes.current;
if (current.nodeName === 'VIDEO' && !current.paused) {
$.on(current, 'ended', Gallery.cb.next);
return current.loop = false;
} else {
return Gallery.cb.next();
}
},
cb: {
keybinds: function(e) {
var cb, key;
@ -10456,7 +10589,7 @@
}
cb = (function() {
switch (key) {
case 'Esc':
case Conf['Close']:
case Conf['Open Gallery']:
return Gallery.cb.close;
case 'Right':
@ -10466,6 +10599,10 @@
case 'Left':
case '':
return Gallery.cb.prev;
case Conf['Pause']:
return Gallery.cb.pause;
case Conf['Slideshow']:
return Gallery.cb.toggleSlideshow;
}
})();
if (!cb) {
@ -10476,110 +10613,34 @@
return cb();
},
open: function(e) {
var el, elType, file, name, nodes, post, rect, top, _base, _ref;
if (e) {
e.preventDefault();
}
if (!this) {
return;
if (this) {
return Gallery.open(this);
}
nodes = Gallery.nodes;
name = nodes.name;
if (el = $('.gal-highlight', nodes.thumbs)) {
$.rmClass(el, 'gal-highlight');
}
$.addClass(this, 'gal-highlight');
elType = this.dataset.isVideo ? 'video' : /\.pdf$/.test(this.href) ? 'iframe' : 'img';
$[elType === 'iframe' ? 'addClass' : 'rmClass'](nodes.el, 'gal-pdf');
file = $.el(elType, {
src: name.href = this.href,
title: name.download = name.textContent = this.title
});
$.extend(file.dataset, this.dataset);
if (typeof (_base = nodes.current).pause === "function") {
_base.pause();
}
$.replace(nodes.current, file);
if (this.dataset.isVideo) {
Video.configure(file);
}
nodes.count.textContent = +this.dataset.id + 1;
nodes.current = file;
nodes.frame.scrollTop = 0;
nodes.next.focus();
if (Conf['Scroll to Post'] && (post = (_ref = (post = g.posts[file.dataset.post])) != null ? _ref.nodes.root : void 0)) {
Header.scrollTo(post);
}
$.on(file, 'error', function() {
return Gallery.cb.error(file, thumb);
});
rect = this.getBoundingClientRect();
top = rect.top;
if (top > 0) {
top += rect.height - doc.clientHeight;
if (top < 0) {
return;
}
}
return nodes.thumbs.scrollTop += top;
},
image: function(e) {
e.preventDefault();
e.stopPropagation();
return Gallery.build(this);
},
error: function(img, thumb) {
var URL, post, src;
post = Get.postFromLink($.el('a', {
href: img.dataset.post
}));
delete post.file.fullImage;
src = this.src.split('/');
if (src[2] === 'i.4cdn.org') {
URL = Redirect.to('file', {
boardID: src[3],
filename: src[src.length - 1]
});
if (URL) {
thumb.href = URL;
if (Gallery.nodes.current !== img) {
return;
}
img.src = URL;
return;
}
if (g.DEAD || post.isDead || post.file.isDead) {
return;
}
}
return $.ajax("//a.4cdn.org/" + post.board + "/thread/" + post.thread + ".json", {
onload: function() {
var i, postObj, posts;
if (this.status !== 200) {
return;
}
i = 0;
posts = this.response.posts;
while (postObj = posts[i++]) {
if (postObj.no === post.ID) {
break;
}
}
if (!postObj.no) {
return post.kill();
}
if (postObj.filedeleted) {
return post.kill(true);
}
}
});
},
prev: function() {
return Gallery.cb.open.call(Gallery.images[+Gallery.nodes.current.dataset.id - 1] || Gallery.images[Gallery.images.length - 1]);
},
next: function() {
return Gallery.cb.open.call(Gallery.images[+Gallery.nodes.current.dataset.id + 1] || Gallery.images[0]);
},
enterKey: function() {
if (Gallery.nodes.current.paused) {
return Gallery.nodes.current.play();
} else {
return Gallery.cb.next();
}
},
click: function() {
return Gallery.cb[Gallery.nodes.current.controls ? 'stop' : 'enterKey']();
},
toggle: function() {
return (Gallery.nodes ? Gallery.cb.close : Gallery.build)();
},
@ -10588,41 +10649,66 @@
return Gallery.cb.close();
}
},
advance: function() {
if (Gallery.nodes.current.controls) {
return;
}
if (Gallery.nodes.current.paused) {
return Gallery.nodes.current.play();
}
return Gallery.cb.next();
},
pause: function() {
var current;
Gallery.cb.stop();
current = Gallery.nodes.current;
if (current.nodeType === 'VIDEO') {
if (current.nodeName === 'VIDEO') {
return current[current.paused ? 'play' : 'pause']();
}
},
start: function() {
$.addClass(Gallery.nodes.buttons, 'gal-playing');
Gallery.slideshow = true;
return Gallery.setupTimer();
},
stop: function() {
var current;
if (!Gallery.slideshow) {
return;
}
Gallery.cleanupTimer();
current = Gallery.nodes.current;
current.loop = current.nodeName === 'VIDEO';
$.rmClass(Gallery.nodes.buttons, 'gal-playing');
return Gallery.slideshow = false;
},
close: function() {
var _base;
if (typeof (_base = Gallery.nodes.current).pause === "function") {
_base.pause();
}
$.rm(Gallery.nodes.el);
$.rmClass(doc, 'gallery-open');
if (Conf['Fullscreen Gallery']) {
$.off(d, 'fullscreenchange mozfullscreenchange webkitfullscreenchange', Gallery.cb.close);
if (typeof d.mozCancelFullScreen === "function") {
d.mozCancelFullScreen();
}
if (typeof d.webkitExitFullscreen === "function") {
d.webkitExitFullscreen();
}
}
delete Gallery.nodes;
d.body.style.overflow = '';
delete Gallery.fullIDs;
doc.style.overflow = '';
$.off(d, 'keydown', Gallery.cb.keybinds);
return $.on(d, 'keydown', Keybinds.keydown);
if (Conf['Keybinds']) {
$.on(d, 'keydown', Keybinds.keydown);
}
return clearTimeout(Gallery.timeoutID);
},
setFitness: function() {
return (this.checked ? $.addClass : $.rmClass)(doc, "gal-" + (this.name.toLowerCase().replace(/\s+/g, '-')));
},
setDelay: function() {
return Gallery.delay = +this.value;
}
},
menu: {
init: function() {
var createSubEntry, el, name, subEntries;
if (!Conf['Gallery']) {
var createSubEntry, el, name, subEntries, _ref;
if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Gallery'])) {
return;
}
el = $.el('span', {
@ -10655,10 +10741,149 @@
return {
el: label
};
},
createSubEntries: function() {
var delayInput, delayLabel, item, subEntries;
subEntries = (function() {
var _i, _len, _ref, _results;
_ref = ['Hide Thumbnails', 'Fit Width', 'Fit Height', 'Scroll to Post'];
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
item = _ref[_i];
_results.push(Gallery.menu.createSubEntry(item));
}
return _results;
})();
delayLabel = $.el('label', {
innerHTML: "Slide Delay: <input type=\"number\" name=\"Slide Delay\" min=\"0\" step=\"any\" class=\"field\">"
});
delayInput = delayLabel.firstElementChild;
delayInput.value = Gallery.delay;
$.on(delayInput, 'change', Gallery.cb.setDelay);
$.on(delayInput, 'change', $.cb.value);
subEntries.push({
el: delayLabel
});
return subEntries;
}
}
};
ImageCommon = {
rewind: function(el) {
if (el.nodeName === 'VIDEO') {
if (el.readyState >= el.HAVE_METADATA) {
return el.currentTime = 0;
}
} else if (/\.gif$/.test(el.src)) {
return $.queueTask(function() {
return el.src = el.src;
});
}
},
pushCache: function(el) {
ImageCommon.cache = el;
return $.on(el, 'error', ImageCommon.cacheError);
},
popCache: function() {
var el;
el = ImageCommon.cache;
$.off(el, 'error', ImageCommon.cacheError);
delete ImageCommon.cache;
return el;
},
cacheError: function() {
if (ImageCommon.cache === this) {
return delete ImageCommon.cache;
}
},
decodeError: function(file, post) {
var message, _ref;
if (((_ref = file.error) != null ? _ref.code : void 0) !== MediaError.MEDIA_ERR_DECODE) {
return false;
}
if (!(message = $('.warning', post.file.thumb.parentNode))) {
message = $.el('div', {
className: 'warning'
});
$.after(post.file.thumb, message);
}
message.textContent = 'Error: Corrupt or unplayable video';
return true;
},
error: function(file, post, delay, cb) {
var URL, redirect, src, timeoutID;
src = post.file.URL.split('/');
URL = Redirect.to('file', {
boardID: post.board.ID,
filename: src[src.length - 1]
});
if (!(Conf['404 Redirect'] && URL && Redirect.securityCheck(URL))) {
URL = null;
}
if ((post.isDead || post.file.isDead) && file.src.split('/')[2] === 'i.4cdn.org') {
return cb(URL);
}
if (delay != null) {
timeoutID = setTimeout((function() {
return cb(URL);
}), delay);
}
if (post.isDead || post.file.isDead) {
return;
}
redirect = function() {
if (file.src.split('/')[2] === 'i.4cdn.org') {
if (delay != null) {
clearTimeout(timeoutID);
}
return cb(URL);
}
};
return $.ajax("//a.4cdn.org/" + post.board + "/thread/" + post.thread + ".json", {
onload: function() {
var postObj, _i, _len, _ref;
if (this.status === 404) {
post.kill();
}
if (this.status !== 200) {
return redirect();
}
_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) {
post.kill();
return redirect();
} else if (postObj.filedeleted) {
post.kill(true);
return redirect();
} else {
return URL = post.file.URL;
}
}
});
},
addControls: function(video) {
var handler;
handler = function() {
var t;
$.off(video, 'mouseover', handler);
t = new Date().getTime();
return $.asap((function() {
return (typeof chrome !== "undefined" && chrome !== null) || (video.readyState >= 3 && video.currentTime <= Math.max(0.1, video.duration - 0.5)) || new Date().getTime() >= t + 1000;
}), function() {
return video.controls = true;
});
};
return $.on(video, 'mouseover', handler);
}
};
ImageExpand = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Image Expansion']) {

View File

@ -88,7 +88,7 @@
'use strict';
(function() {
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CrossOrigin, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, E, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, Linkify, Main, MarkNewIPs, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, TrashQueue, UI, Unread, Video, c, d, doc, editMascot, editTheme, g, userNavigation,
var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CrossOrigin, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, E, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageCommon, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, Linkify, Main, MarkNewIPs, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, TrashQueue, UI, Unread, Video, c, d, doc, editMascot, editTheme, g, userNavigation,
__slice = [].slice,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
__hasProp = {}.hasOwnProperty,
@ -10372,8 +10372,8 @@
Gallery = {
init: function() {
var el;
if (g.BOARD === 'f' || !Conf['Gallery']) {
var el, _ref;
if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Gallery']) || g.BOARD === 'f') {
return;
}
el = $.el('a', {
@ -10395,7 +10395,7 @@
return;
}
if (Gallery.nodes) {
Gallery.generateThumb($('.file', this.nodes.root));
Gallery.generateThumb(this);
Gallery.nodes.total.textContent = Gallery.images.length;
}
if (!Conf['Image Expansion']) {
@ -10403,14 +10403,30 @@
}
},
build: function(image) {
var cb, createSubEntry, dialog, el, file, i, key, menuButton, name, nodes, value, _i, _len, _ref, _ref1;
var candidate, cb, dialog, entry, file, key, menuButton, nodes, post, thumb, value, _i, _j, _len, _len1, _ref, _ref1, _ref2;
if (Conf['Fullscreen Gallery']) {
$.one(d, 'fullscreenchange mozfullscreenchange webkitfullscreenchange', function() {
return $.on(d, 'fullscreenchange mozfullscreenchange webkitfullscreenchange', cb.close);
});
if (typeof doc.mozRequestFullScreen === "function") {
doc.mozRequestFullScreen();
}
if (typeof doc.webkitRequestFullScreen === "function") {
doc.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
}
}
Gallery.images = [];
nodes = Gallery.nodes = {};
Gallery.fullIDs = {};
Gallery.slideshow = false;
nodes.el = dialog = $.el('div', {
id: 'a-gallery',
innerHTML: "<div class=gal-viewport>\n <span class=gal-buttons>\n <a class=\"menu-button\" href=\"javascript:;\"><i></i></a>\n <a href=javascript:; class=gal-close>×</a>\n </span>\n <a class=gal-name target=\"_blank\"></a>\n <span class=gal-count>\n <span class='count'></span> / <span class='total'></span>\n </span>\n <div class=gal-prev></div>\n <div class=gal-image>\n <a href=javascript:;><img></a>\n </div>\n <div class=gal-next></div>\n</div>\n<div class=gal-thumbnails></div>"
id: 'a-gallery'
});
$.extend(dialog, {
innerHTML: "<div class=\"gal-viewport\">\r<span class=\"gal-buttons\">\r<a href=\"javascript:;\" class=\"gal-start\" title=\"Start slideshow (S to toggle)\"><i></i></a>\r<a href=\"javascript:;\" class=\"gal-stop\" title=\"Stop slideshow (S to toggle)\"><i></i></a>\r<a href=\"javascript:;\" class=\"menu-button\"><i></i></a>\r<a href=\"javascript:;\" class=\"gal-close\">×</a>\r</span>\r<a class=\"gal-name\" target=\"_blank\"></a>\r<span class=\"gal-count\">\r<span class=\"count\"></span> / <span class=\"total\"></span>\r</span>\r<div class=\"gal-prev\"></div>\r<div class=\"gal-image\">\r<a href=\"javascript:;\"><img></a>\r</div>\r<div class=\"gal-next\"></div>\r</div>\r<div class=\"gal-thumbnails\"></div>"
});
_ref = {
buttons: '.gal-buttons',
frame: '.gal-image',
name: '.gal-name',
count: '.count',
@ -10424,58 +10440,71 @@
nodes[key] = $(value, dialog);
}
menuButton = $('.menu-button', dialog);
nodes.menu = new UI.Menu();
nodes.menu = new UI.Menu('gallery');
cb = Gallery.cb;
$.on(nodes.frame, 'click', cb.blank);
$.on(nodes.next, 'click', cb.advance);
$.on(nodes.next, 'click', cb.click);
$.on($('.gal-prev', dialog), 'click', cb.prev);
$.on($('.gal-next', dialog), 'click', cb.next);
$.on($('.gal-start', dialog), 'click', cb.start);
$.on($('.gal-stop', dialog), 'click', cb.stop);
$.on($('.gal-close', dialog), 'click', cb.close);
$.on(menuButton, 'click', function(e) {
return nodes.menu.toggle(e, this, g);
});
createSubEntry = Gallery.menu.createSubEntry;
for (name in Config.gallery) {
el = createSubEntry(name).el;
nodes.menu.addEntry({
el: el,
order: 0
});
_ref1 = Gallery.menu.createSubEntries();
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
entry = _ref1[_i];
entry.order = 0;
nodes.menu.addEntry(entry);
}
$.on(d, 'keydown', cb.keybinds);
$.off(d, 'keydown', Keybinds.keydown);
_ref1 = $$('.post .file');
for (i = _i = 0, _len = _ref1.length; _i < _len; i = ++_i) {
file = _ref1[i];
if (!$('.fileDeletedRes, .fileDeleted', file)) {
Gallery.generateThumb(file);
if (Conf['Keybinds']) {
$.off(d, 'keydown', Keybinds.keydown);
}
_ref2 = $$('.post .file');
for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
file = _ref2[_j];
post = Get.postFromNode(file);
if (post.file.isDead) {
continue;
}
Gallery.generateThumb(post);
if (!image && Gallery.fullIDs[post.fullID]) {
candidate = post.file.thumb.parentNode;
if (Header.getTopOf(candidate) + candidate.getBoundingClientRect().height >= 0) {
image = candidate;
}
}
}
$.addClass(doc, 'gallery-open');
$.add(d.body, dialog);
nodes.thumbs.scrollTop = 0;
nodes.current.parentElement.scrollTop = 0;
Gallery.cb.open.call(image ? $("[href*='" + image.pathname + "']", nodes.thumbs) : Gallery.images[0]);
d.body.style.overflow = 'hidden';
return nodes.total.textContent = i;
if (image) {
thumb = $("[href='" + image.href + "']", nodes.thumbs);
}
thumb || (thumb = Gallery.images[Gallery.images.length - 1]);
if (thumb) {
Gallery.open(thumb);
}
doc.style.overflow = 'hidden';
return nodes.total.textContent = Gallery.images.length;
},
generateThumb: function(file) {
var post, thumb, thumbImg, title;
post = Get.postFromNode(file);
if (!(post.file && (post.file.isImage || post.file.isVideo || Conf['PDF in Gallery']))) {
generateThumb: function(post) {
var thumb, thumbImg, _ref, _ref1;
if (post.isClone || post.isHidden && !(((_ref = post.file) != null ? _ref.isImage : void 0) || ((_ref1 = post.file) != null ? _ref1.isVideo : void 0) || Conf['PDF in Gallery'])) {
return;
}
title = ($('.fileText a', file)).textContent;
Gallery.fullIDs[post.fullID] = true;
thumb = $.el('a', {
className: 'gal-thumb',
href: post.file.URL,
target: '_blank',
title: title
title: post.file.name
});
thumb.dataset.id = Gallery.images.length;
thumb.dataset.post = post.fullID;
if (post.file.isVideo) {
thumb.dataset.isVideo = true;
}
thumbImg = post.file.thumb.cloneNode(false);
thumbImg.style.cssText = '';
$.add(thumb, thumbImg);
@ -10483,6 +10512,110 @@
Gallery.images.push(thumb);
return $.add(Gallery.nodes.thumbs, thumb);
},
open: function(thumb) {
var el, elType, file, name, newID, nodes, oldID, post, slideshow, _base, _ref;
nodes = Gallery.nodes;
name = nodes.name;
oldID = +nodes.current.dataset.id;
newID = +thumb.dataset.id;
slideshow = Gallery.slideshow && (newID > oldID || (oldID === Gallery.images.length - 1 && newID === 0));
if (el = $('.gal-highlight', nodes.thumbs)) {
$.rmClass(el, 'gal-highlight');
}
$.addClass(thumb, 'gal-highlight');
elType = /\.webm$/.test(thumb.href) ? 'video' : /\.pdf$/.test(thumb.href) ? 'iframe' : 'image';
$[elType === 'iframe' ? 'addClass' : 'rmClass'](doc, 'gal-pdf');
file = $.el(elType, {
title: name.download = name.textContent = thumb.title
});
$.on(file, 'error', (function(_this) {
return function() {
return Gallery.error(file, thumb);
};
})(this));
file.src = name.href = thumb.href;
$.extend(file.dataset, thumb.dataset);
if (!nodes.current.error) {
if (typeof (_base = nodes.current).pause === "function") {
_base.pause();
}
}
$.replace(nodes.current, file);
if (elType === 'video') {
file.loop = true;
if (Conf['Autoplay']) {
file.play();
}
if (Conf['Show Controls']) {
ImageCommon.addControls(file);
}
}
nodes.count.textContent = +thumb.dataset.id + 1;
nodes.current = file;
nodes.frame.scrollTop = 0;
nodes.next.focus();
if (slideshow) {
Gallery.setupTimer();
} else {
Gallery.cb.stop();
}
if (Conf['Scroll to Post'] && (post = (_ref = (post = g.posts[file.dataset.post])) != null ? _ref.nodes.root : void 0)) {
Header.scrollTo(post);
}
return nodes.thumbs.scrollTop = thumb.offsetTop + thumb.offsetHeight / 2 - nodes.thumbs.clientHeight / 2;
},
error: function(file, thumb) {
var _ref;
if (((_ref = file.error) != null ? _ref.code : void 0) === MediaError.MEDIA_ERR_DECODE) {
return new Notice('error', 'Corrupt or unplayable video', 30);
}
if (file.src.split('/')[2] !== 'i.4cdn.org') {
return;
}
return ImageCommon.error(file, g.posts[file.dataset.post], null, function(URL) {
if (!URL) {
return;
}
thumb.href = URL;
if (Gallery.nodes.current === file) {
return file.src = URL;
}
});
},
cleanupTimer: function() {
var current;
clearTimeout(Gallery.timeoutID);
current = Gallery.nodes.current;
$.off(current, 'canplaythrough load', Gallery.startTimer);
return $.off(current, 'ended', Gallery.cb.next);
},
startTimer: function() {
return Gallery.timeoutID = setTimeout(Gallery.checkTimer, Gallery.delay * $.SECOND);
},
setupTimer: function() {
var current, isVideo;
Gallery.cleanupTimer();
current = Gallery.nodes.current;
isVideo = current.nodeName === 'VIDEO';
if (isVideo) {
current.play();
}
if ((isVideo ? current.readyState >= 4 : current.complete) || current.nodeName === 'IFRAME') {
return Gallery.startTimer();
} else {
return $.on(current, (isVideo ? 'canplaythrough' : 'load'), Gallery.startTimer);
}
},
checkTimer: function() {
var current;
current = Gallery.nodes.current;
if (current.nodeName === 'VIDEO' && !current.paused) {
$.on(current, 'ended', Gallery.cb.next);
return current.loop = false;
} else {
return Gallery.cb.next();
}
},
cb: {
keybinds: function(e) {
var cb, key;
@ -10491,7 +10624,7 @@
}
cb = (function() {
switch (key) {
case 'Esc':
case Conf['Close']:
case Conf['Open Gallery']:
return Gallery.cb.close;
case 'Right':
@ -10501,6 +10634,10 @@
case 'Left':
case '':
return Gallery.cb.prev;
case Conf['Pause']:
return Gallery.cb.pause;
case Conf['Slideshow']:
return Gallery.cb.toggleSlideshow;
}
})();
if (!cb) {
@ -10511,110 +10648,34 @@
return cb();
},
open: function(e) {
var el, elType, file, name, nodes, post, rect, top, _base, _ref;
if (e) {
e.preventDefault();
}
if (!this) {
return;
if (this) {
return Gallery.open(this);
}
nodes = Gallery.nodes;
name = nodes.name;
if (el = $('.gal-highlight', nodes.thumbs)) {
$.rmClass(el, 'gal-highlight');
}
$.addClass(this, 'gal-highlight');
elType = this.dataset.isVideo ? 'video' : /\.pdf$/.test(this.href) ? 'iframe' : 'img';
$[elType === 'iframe' ? 'addClass' : 'rmClass'](nodes.el, 'gal-pdf');
file = $.el(elType, {
src: name.href = this.href,
title: name.download = name.textContent = this.title
});
$.extend(file.dataset, this.dataset);
if (typeof (_base = nodes.current).pause === "function") {
_base.pause();
}
$.replace(nodes.current, file);
if (this.dataset.isVideo) {
Video.configure(file);
}
nodes.count.textContent = +this.dataset.id + 1;
nodes.current = file;
nodes.frame.scrollTop = 0;
nodes.next.focus();
if (Conf['Scroll to Post'] && (post = (_ref = (post = g.posts[file.dataset.post])) != null ? _ref.nodes.root : void 0)) {
Header.scrollTo(post);
}
$.on(file, 'error', function() {
return Gallery.cb.error(file, thumb);
});
rect = this.getBoundingClientRect();
top = rect.top;
if (top > 0) {
top += rect.height - doc.clientHeight;
if (top < 0) {
return;
}
}
return nodes.thumbs.scrollTop += top;
},
image: function(e) {
e.preventDefault();
e.stopPropagation();
return Gallery.build(this);
},
error: function(img, thumb) {
var URL, post, src;
post = Get.postFromLink($.el('a', {
href: img.dataset.post
}));
delete post.file.fullImage;
src = this.src.split('/');
if (src[2] === 'i.4cdn.org') {
URL = Redirect.to('file', {
boardID: src[3],
filename: src[src.length - 1]
});
if (URL) {
thumb.href = URL;
if (Gallery.nodes.current !== img) {
return;
}
img.src = URL;
return;
}
if (g.DEAD || post.isDead || post.file.isDead) {
return;
}
}
return $.ajax("//a.4cdn.org/" + post.board + "/thread/" + post.thread + ".json", {
onload: function() {
var i, postObj, posts;
if (this.status !== 200) {
return;
}
i = 0;
posts = this.response.posts;
while (postObj = posts[i++]) {
if (postObj.no === post.ID) {
break;
}
}
if (!postObj.no) {
return post.kill();
}
if (postObj.filedeleted) {
return post.kill(true);
}
}
});
},
prev: function() {
return Gallery.cb.open.call(Gallery.images[+Gallery.nodes.current.dataset.id - 1] || Gallery.images[Gallery.images.length - 1]);
},
next: function() {
return Gallery.cb.open.call(Gallery.images[+Gallery.nodes.current.dataset.id + 1] || Gallery.images[0]);
},
enterKey: function() {
if (Gallery.nodes.current.paused) {
return Gallery.nodes.current.play();
} else {
return Gallery.cb.next();
}
},
click: function() {
return Gallery.cb[Gallery.nodes.current.controls ? 'stop' : 'enterKey']();
},
toggle: function() {
return (Gallery.nodes ? Gallery.cb.close : Gallery.build)();
},
@ -10623,41 +10684,66 @@
return Gallery.cb.close();
}
},
advance: function() {
if (Gallery.nodes.current.controls) {
return;
}
if (Gallery.nodes.current.paused) {
return Gallery.nodes.current.play();
}
return Gallery.cb.next();
},
pause: function() {
var current;
Gallery.cb.stop();
current = Gallery.nodes.current;
if (current.nodeType === 'VIDEO') {
if (current.nodeName === 'VIDEO') {
return current[current.paused ? 'play' : 'pause']();
}
},
start: function() {
$.addClass(Gallery.nodes.buttons, 'gal-playing');
Gallery.slideshow = true;
return Gallery.setupTimer();
},
stop: function() {
var current;
if (!Gallery.slideshow) {
return;
}
Gallery.cleanupTimer();
current = Gallery.nodes.current;
current.loop = current.nodeName === 'VIDEO';
$.rmClass(Gallery.nodes.buttons, 'gal-playing');
return Gallery.slideshow = false;
},
close: function() {
var _base;
if (typeof (_base = Gallery.nodes.current).pause === "function") {
_base.pause();
}
$.rm(Gallery.nodes.el);
$.rmClass(doc, 'gallery-open');
if (Conf['Fullscreen Gallery']) {
$.off(d, 'fullscreenchange mozfullscreenchange webkitfullscreenchange', Gallery.cb.close);
if (typeof d.mozCancelFullScreen === "function") {
d.mozCancelFullScreen();
}
if (typeof d.webkitExitFullscreen === "function") {
d.webkitExitFullscreen();
}
}
delete Gallery.nodes;
d.body.style.overflow = '';
delete Gallery.fullIDs;
doc.style.overflow = '';
$.off(d, 'keydown', Gallery.cb.keybinds);
return $.on(d, 'keydown', Keybinds.keydown);
if (Conf['Keybinds']) {
$.on(d, 'keydown', Keybinds.keydown);
}
return clearTimeout(Gallery.timeoutID);
},
setFitness: function() {
return (this.checked ? $.addClass : $.rmClass)(doc, "gal-" + (this.name.toLowerCase().replace(/\s+/g, '-')));
},
setDelay: function() {
return Gallery.delay = +this.value;
}
},
menu: {
init: function() {
var createSubEntry, el, name, subEntries;
if (!Conf['Gallery']) {
var createSubEntry, el, name, subEntries, _ref;
if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Gallery'])) {
return;
}
el = $.el('span', {
@ -10690,10 +10776,136 @@
return {
el: label
};
},
createSubEntries: function() {
var delayInput, delayLabel, item, subEntries;
subEntries = (function() {
var _i, _len, _ref, _results;
_ref = ['Hide Thumbnails', 'Fit Width', 'Fit Height', 'Scroll to Post'];
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
item = _ref[_i];
_results.push(Gallery.menu.createSubEntry(item));
}
return _results;
})();
delayLabel = $.el('label', {
innerHTML: "Slide Delay: <input type=\"number\" name=\"Slide Delay\" min=\"0\" step=\"any\" class=\"field\">"
});
delayInput = delayLabel.firstElementChild;
delayInput.value = Gallery.delay;
$.on(delayInput, 'change', Gallery.cb.setDelay);
$.on(delayInput, 'change', $.cb.value);
subEntries.push({
el: delayLabel
});
return subEntries;
}
}
};
ImageCommon = {
rewind: function(el) {
if (el.nodeName === 'VIDEO') {
if (el.readyState >= el.HAVE_METADATA) {
return el.currentTime = 0;
}
} else if (/\.gif$/.test(el.src)) {
return $.queueTask(function() {
return el.src = el.src;
});
}
},
pushCache: function(el) {
ImageCommon.cache = el;
return $.on(el, 'error', ImageCommon.cacheError);
},
popCache: function() {
var el;
el = ImageCommon.cache;
$.off(el, 'error', ImageCommon.cacheError);
delete ImageCommon.cache;
return el;
},
cacheError: function() {
if (ImageCommon.cache === this) {
return delete ImageCommon.cache;
}
},
decodeError: function(file, post) {
var message, _ref;
if (((_ref = file.error) != null ? _ref.code : void 0) !== MediaError.MEDIA_ERR_DECODE) {
return false;
}
if (!(message = $('.warning', post.file.thumb.parentNode))) {
message = $.el('div', {
className: 'warning'
});
$.after(post.file.thumb, message);
}
message.textContent = 'Error: Corrupt or unplayable video';
return true;
},
error: function(file, post, delay, cb) {
var URL, redirect, src, timeoutID;
src = post.file.URL.split('/');
URL = Redirect.to('file', {
boardID: post.board.ID,
filename: src[src.length - 1]
});
if (!(Conf['404 Redirect'] && URL && Redirect.securityCheck(URL))) {
URL = null;
}
if ((post.isDead || post.file.isDead) && file.src.split('/')[2] === 'i.4cdn.org') {
return cb(URL);
}
if (delay != null) {
timeoutID = setTimeout((function() {
return cb(URL);
}), delay);
}
if (post.isDead || post.file.isDead) {
return;
}
redirect = function() {
if (file.src.split('/')[2] === 'i.4cdn.org') {
if (delay != null) {
clearTimeout(timeoutID);
}
return cb(URL);
}
};
return $.ajax(post.file.URL, {
onloadend: function() {
if (this.status === 200) {
return URL = post.file.URL;
} else {
if (this.status === 404) {
post.kill(true);
}
return redirect();
}
}
}, {
type: 'head'
});
},
addControls: function(video) {
var handler;
handler = function() {
var t;
$.off(video, 'mouseover', handler);
t = new Date().getTime();
return $.asap((function() {
return (typeof chrome !== "undefined" && chrome !== null) || (video.readyState >= 3 && video.currentTime <= Math.max(0.1, video.duration - 0.5)) || new Date().getTime() >= t + 1000;
}), function() {
return video.controls = true;
});
};
return $.on(video, 'mouseover', handler);
}
};
ImageExpand = {
init: function() {
if (g.VIEW === 'catalog' || !Conf['Image Expansion']) {

View File

@ -0,0 +1,18 @@
<div class="gal-viewport">
<span class="gal-buttons">
<a href="javascript:;" class="gal-start" title="Start slideshow (S to toggle)"><i></i></a>
<a href="javascript:;" class="gal-stop" title="Stop slideshow (S to toggle)"><i></i></a>
<a href="javascript:;" class="menu-button"><i></i></a>
<a href="javascript:;" class="gal-close">×</a>
</span>
<a class="gal-name" target="_blank"></a>
<span class="gal-count">
<span class="count"></span> / <span class="total"></span>
</span>
<div class="gal-prev"></div>
<div class="gal-image">
<a href="javascript:;"><img></a>
</div>
<div class="gal-next"></div>
</div>
<div class="gal-thumbnails"></div>

View File

@ -1,6 +1,6 @@
Gallery =
init: ->
return if g.BOARD is 'f' or !Conf['Gallery']
return if not (g.VIEW in ['index', 'thread'] and Conf['Gallery']) or g.BOARD is 'f'
el = $.el 'a',
href: 'javascript:;'
@ -20,38 +20,30 @@ Gallery =
node: ->
return unless @file
if Gallery.nodes
Gallery.generateThumb $ '.file', @nodes.root
Gallery.generateThumb @
Gallery.nodes.total.textContent = Gallery.images.length
unless Conf['Image Expansion']
$.on @file.thumb.parentNode, 'click', Gallery.cb.image
build: (image) ->
if Conf['Fullscreen Gallery']
$.one d, 'fullscreenchange mozfullscreenchange webkitfullscreenchange', ->
$.on d, 'fullscreenchange mozfullscreenchange webkitfullscreenchange', cb.close
doc.mozRequestFullScreen?()
doc.webkitRequestFullScreen?(Element.ALLOW_KEYBOARD_INPUT)
Gallery.images = []
nodes = Gallery.nodes = {}
Gallery.fullIDs = {}
Gallery.slideshow = false
nodes.el = dialog = $.el 'div',
id: 'a-gallery'
innerHTML: """
<div class=gal-viewport>
<span class=gal-buttons>
<a class="menu-button" href="javascript:;"><i></i></a>
<a href=javascript:; class=gal-close>×</a>
</span>
<a class=gal-name target="_blank"></a>
<span class=gal-count>
<span class='count'></span> / <span class='total'></span>
</span>
<div class=gal-prev></div>
<div class=gal-image>
<a href=javascript:;><img></a>
</div>
<div class=gal-next></div>
</div>
<div class=gal-thumbnails></div>
"""
$.extend dialog, <%= importHTML('Features/Gallery') %>
nodes[key] = $ value, dialog for key, value of {
buttons: '.gal-buttons'
frame: '.gal-image'
name: '.gal-name'
count: '.count'
@ -62,56 +54,64 @@ Gallery =
}
menuButton = $ '.menu-button', dialog
nodes.menu = new UI.Menu()
nodes.menu = new UI.Menu 'gallery'
{cb} = Gallery
$.on nodes.frame, 'click', cb.blank
$.on nodes.next, 'click', cb.advance
$.on nodes.next, 'click', cb.click
$.on $('.gal-prev', dialog), 'click', cb.prev
$.on $('.gal-next', dialog), 'click', cb.next
$.on $('.gal-start', dialog), 'click', cb.start
$.on $('.gal-stop', dialog), 'click', cb.stop
$.on $('.gal-close', dialog), 'click', cb.close
$.on menuButton, 'click', (e) ->
nodes.menu.toggle e, @, g
{createSubEntry} = Gallery.menu
for name of Config.gallery
{el} = createSubEntry name
nodes.menu.addEntry
el: el
order: 0
for entry in Gallery.menu.createSubEntries()
entry.order = 0
nodes.menu.addEntry entry
$.on d, 'keydown', cb.keybinds
$.off d, 'keydown', Keybinds.keydown
Gallery.generateThumb file for file, i in $$ '.post .file' when !$ '.fileDeletedRes, .fileDeleted', file
$.off d, 'keydown', Keybinds.keydown if Conf['Keybinds']
for file in $$ '.post .file'
post = Get.postFromNode file
continue if post.file.isDead
Gallery.generateThumb post
# If no image to open is given, pick image we have scrolled to.
if !image and Gallery.fullIDs[post.fullID]
candidate = post.file.thumb.parentNode
if Header.getTopOf(candidate) + candidate.getBoundingClientRect().height >= 0
image = candidate
$.addClass doc, 'gallery-open'
$.add d.body, dialog
nodes.thumbs.scrollTop = 0
nodes.current.parentElement.scrollTop = 0
Gallery.cb.open.call if image
$ "[href*='#{image.pathname}']", nodes.thumbs
else
Gallery.images[0]
thumb = $ "[href='#{image.href}']", nodes.thumbs if image
thumb or= Gallery.images[Gallery.images.length-1]
Gallery.open thumb if thumb
d.body.style.overflow = 'hidden'
nodes.total.textContent = i
doc.style.overflow = 'hidden'
nodes.total.textContent = Gallery.images.length
generateThumb: (file) ->
post = Get.postFromNode file
return unless post.file and (post.file.isImage or post.file.isVideo or Conf['PDF in Gallery'])
title = ($ '.fileText a', file).textContent
generateThumb: (post) ->
return if post.isClone or post.isHidden and
not (post.file?.isImage or post.file?.isVideo or Conf['PDF in Gallery'])
Gallery.fullIDs[post.fullID] = true
thumb = $.el 'a',
className: 'gal-thumb'
href: post.file.URL
target: '_blank'
title: title
title: post.file.name
thumb.dataset.id = Gallery.images.length
thumb.dataset.post = post.fullID
thumb.dataset.isVideo = true if post.file.isVideo
thumb.dataset.id = Gallery.images.length
thumb.dataset.post = post.fullID
thumbImg = post.file.thumb.cloneNode false
thumbImg.style.cssText = ''
@ -122,12 +122,95 @@ Gallery =
Gallery.images.push thumb
$.add Gallery.nodes.thumbs, thumb
open: (thumb) ->
{nodes} = Gallery
{name} = nodes
oldID = +nodes.current.dataset.id
newID = +thumb.dataset.id
slideshow = Gallery.slideshow and (newID > oldID or (oldID is Gallery.images.length-1 and newID is 0))
$.rmClass el, 'gal-highlight' if el = $ '.gal-highlight', nodes.thumbs
$.addClass thumb, 'gal-highlight'
elType = if /\.webm$/.test(thumb.href)
'video'
else if /\.pdf$/.test(thumb.href)
'iframe'
else
'image'
$[if elType is 'iframe' then 'addClass' else 'rmClass'] doc, 'gal-pdf'
file = $.el elType,
title: name.download = name.textContent = thumb.title
$.on file, 'error', =>
Gallery.error file, thumb
file.src = name.href = thumb.href
$.extend file.dataset, thumb.dataset
nodes.current.pause?() unless nodes.current.error
$.replace nodes.current, file
if elType is 'video'
file.loop = true
file.play() if Conf['Autoplay']
ImageCommon.addControls file if Conf['Show Controls']
nodes.count.textContent = +thumb.dataset.id + 1
nodes.current = file
nodes.frame.scrollTop = 0
nodes.next.focus()
if slideshow
Gallery.setupTimer()
else
Gallery.cb.stop()
# Scroll to post
if Conf['Scroll to Post'] and post = (post = g.posts[file.dataset.post])?.nodes.root
Header.scrollTo post
# Center selected thumbnail
nodes.thumbs.scrollTop = thumb.offsetTop + thumb.offsetHeight/2 - nodes.thumbs.clientHeight/2
error: (file, thumb) ->
if file.error?.code is MediaError.MEDIA_ERR_DECODE
return new Notice 'error', 'Corrupt or unplayable video', 30
return unless file.src.split('/')[2] is 'i.4cdn.org'
ImageCommon.error file, g.posts[file.dataset.post], null, (URL) ->
return unless URL
thumb.href = URL
file.src = URL if Gallery.nodes.current is file
cleanupTimer: ->
clearTimeout Gallery.timeoutID
{current} = Gallery.nodes
$.off current, 'canplaythrough load', Gallery.startTimer
$.off current, 'ended', Gallery.cb.next
startTimer: ->
Gallery.timeoutID = setTimeout Gallery.checkTimer, Gallery.delay * $.SECOND
setupTimer: ->
Gallery.cleanupTimer()
{current} = Gallery.nodes
isVideo = current.nodeName is 'VIDEO'
current.play() if isVideo
if (if isVideo then current.readyState >= 4 else current.complete) or current.nodeName is 'IFRAME'
Gallery.startTimer()
else
$.on current, (if isVideo then 'canplaythrough' else 'load'), Gallery.startTimer
checkTimer: ->
{current} = Gallery.nodes
if current.nodeName is 'VIDEO' and !current.paused
$.on current, 'ended', Gallery.cb.next
current.loop = false
else
Gallery.cb.next()
cb:
keybinds: (e) ->
return unless key = Keybinds.keyCode e
cb = switch key
when 'Esc', Conf['Open Gallery']
when Conf['Close'], Conf['Open Gallery']
Gallery.cb.close
when 'Right'
Gallery.cb.next
@ -135,6 +218,10 @@ Gallery =
Gallery.cb.advance
when 'Left', ''
Gallery.cb.prev
when Conf['Pause']
Gallery.cb.pause
when Conf['Slideshow']
Gallery.cb.toggleSlideshow
return unless cb
e.stopPropagation()
@ -143,80 +230,13 @@ Gallery =
open: (e) ->
e.preventDefault() if e
return unless @
{nodes} = Gallery
{name} = nodes
$.rmClass el, 'gal-highlight' if el = $ '.gal-highlight', nodes.thumbs
$.addClass @, 'gal-highlight'
elType = if @dataset.isVideo then 'video' else if /\.pdf$/.test(@href) then 'iframe' else 'img'
$[if elType is 'iframe' then 'addClass' else 'rmClass'] nodes.el, 'gal-pdf'
file = $.el elType,
src: name.href = @href
title: name.download = name.textContent = @title
$.extend file.dataset, @dataset
nodes.current.pause?()
$.replace nodes.current, file
Video.configure file if @dataset.isVideo
nodes.count.textContent = +@dataset.id + 1
nodes.current = file
nodes.frame.scrollTop = 0
nodes.next.focus()
# Scroll to post
if Conf['Scroll to Post'] and post = (post = g.posts[file.dataset.post])?.nodes.root
Header.scrollTo post
$.on file, 'error', ->
Gallery.cb.error file, thumb
# Scroll
rect = @getBoundingClientRect()
{top} = rect
if top > 0
top += rect.height - doc.clientHeight
return if top < 0
nodes.thumbs.scrollTop += top
if @ then Gallery.open @
image: (e) ->
e.preventDefault()
e.stopPropagation()
Gallery.build @
error: (img, thumb) ->
post = Get.postFromLink $.el 'a', href: img.dataset.post
delete post.file.fullImage
src = @src.split '/'
if src[2] is 'i.4cdn.org'
URL = Redirect.to 'file',
boardID: src[3]
filename: src[src.length - 1]
if URL
thumb.href = URL
return unless Gallery.nodes.current is img
img.src = URL
return
if g.DEAD or post.isDead or post.file.isDead
return
# XXX CORS for i.4cdn.org WHEN?
$.ajax "//a.4cdn.org/#{post.board}/thread/#{post.thread}.json", onload: ->
return if @status isnt 200
i = 0
{posts} = @response
while postObj = posts[i++]
break if postObj.no is post.ID
unless postObj.no
return post.kill()
if postObj.filedeleted
post.kill true
prev: ->
Gallery.cb.open.call(
Gallery.images[+Gallery.nodes.current.dataset.id - 1] or Gallery.images[Gallery.images.length - 1]
@ -225,33 +245,54 @@ Gallery =
Gallery.cb.open.call(
Gallery.images[+Gallery.nodes.current.dataset.id + 1] or Gallery.images[0]
)
enterKey: -> if Gallery.nodes.current.paused then Gallery.nodes.current.play() else Gallery.cb.next()
click: -> Gallery.cb[if Gallery.nodes.current.controls then 'stop' else 'enterKey']()
toggle: -> (if Gallery.nodes then Gallery.cb.close else Gallery.build)()
blank: (e) -> Gallery.cb.close() if e.target is @
advance: ->
if Gallery.nodes.current.controls then return
if Gallery.nodes.current.paused then return Gallery.nodes.current.play()
Gallery.cb.next()
pause: ->
Gallery.cb.stop()
{current} = Gallery.nodes
current[if current.paused then 'play' else 'pause']() if current.nodeType is 'VIDEO'
current[if current.paused then 'play' else 'pause']() if current.nodeName is 'VIDEO'
start: ->
$.addClass Gallery.nodes.buttons, 'gal-playing'
Gallery.slideshow = true
Gallery.setupTimer()
stop: ->
return unless Gallery.slideshow
Gallery.cleanupTimer()
{current} = Gallery.nodes
current.loop = current.nodeName is 'VIDEO'
$.rmClass Gallery.nodes.buttons, 'gal-playing'
Gallery.slideshow = false
close: ->
Gallery.nodes.current.pause?()
$.rm Gallery.nodes.el
$.rmClass doc, 'gallery-open'
if Conf['Fullscreen Gallery']
$.off d, 'fullscreenchange mozfullscreenchange webkitfullscreenchange', Gallery.cb.close
d.mozCancelFullScreen?()
d.webkitExitFullscreen?()
delete Gallery.nodes
d.body.style.overflow = ''
delete Gallery.fullIDs
doc.style.overflow = ''
$.off d, 'keydown', Gallery.cb.keybinds
$.on d, 'keydown', Keybinds.keydown
$.on d, 'keydown', Keybinds.keydown if Conf['Keybinds']
clearTimeout Gallery.timeoutID
setFitness: ->
(if @checked then $.addClass else $.rmClass) doc, "gal-#{@name.toLowerCase().replace /\s+/g, '-'}"
setDelay: -> Gallery.delay = +@value
menu:
init: ->
return if !Conf['Gallery']
return unless g.VIEW in ['index', 'thread'] and Conf['Gallery']
el = $.el 'span',
textContent: 'Gallery'
@ -277,3 +318,15 @@ Gallery =
$.event 'change', null, input
$.on input, 'change', $.cb.checked
el: label
createSubEntries: ->
subEntries = (Gallery.menu.createSubEntry item for item in ['Hide Thumbnails', 'Fit Width', 'Fit Height', 'Scroll to Post'])
delayLabel = $.el 'label', <%= html('Slide Delay: <input type="number" name="Slide Delay" min="0" step="any" class="field">') %>
delayInput = delayLabel.firstElementChild
delayInput.value = Gallery.delay
$.on delayInput, 'change', Gallery.cb.setDelay
$.on delayInput, 'change', $.cb.value
subEntries.push el: delayLabel
subEntries

View File

@ -0,0 +1,81 @@
ImageCommon =
rewind: (el) ->
if el.nodeName is 'VIDEO'
el.currentTime = 0 if el.readyState >= el.HAVE_METADATA
else if /\.gif$/.test el.src
$.queueTask -> el.src = el.src
pushCache: (el) ->
ImageCommon.cache = el
$.on el, 'error', ImageCommon.cacheError
popCache: ->
el = ImageCommon.cache
$.off el, 'error', ImageCommon.cacheError
delete ImageCommon.cache
el
cacheError: ->
delete ImageCommon.cache if ImageCommon.cache is @
decodeError: (file, post) ->
return false unless file.error?.code is MediaError.MEDIA_ERR_DECODE
unless message = $ '.warning', post.file.thumb.parentNode
message = $.el 'div', className: 'warning'
$.after post.file.thumb, message
message.textContent = 'Error: Corrupt or unplayable video'
return true
error: (file, post, delay, cb) ->
src = post.file.URL.split '/'
URL = Redirect.to 'file',
boardID: post.board.ID
filename: src[src.length - 1]
unless Conf['404 Redirect'] and URL and Redirect.securityCheck URL
URL = null
return cb URL if (post.isDead or post.file.isDead) and file.src.split('/')[2] is 'i.4cdn.org'
timeoutID = setTimeout (-> cb URL), delay if delay?
return if post.isDead or post.file.isDead
redirect = ->
if file.src.split('/')[2] is 'i.4cdn.org'
clearTimeout timeoutID if delay?
cb URL
<% if (type === 'crx') { %>
$.ajax post.file.URL,
onloadend: ->
if @status is 200
URL = post.file.URL
else
post.kill true if @status is 404
redirect()
,
type: 'head'
<% } else { %>
# XXX CORS for i.4cdn.org WHEN?
$.ajax "//a.4cdn.org/#{post.board}/thread/#{post.thread}.json", onload: ->
post.kill() if @status is 404
return redirect() if @status isnt 200
for postObj in @response.posts
break if postObj.no is post.ID
if postObj.no isnt post.ID
post.kill()
redirect()
else if postObj.filedeleted
post.kill true
redirect()
else
URL = post.file.URL
<% } %>
# Add controls, but not until the mouse is moved over the video.
addControls: (video) ->
handler = ->
$.off video, 'mouseover', handler
# Hacky workaround for Firefox forever-loading bug for very short videos
t = new Date().getTime()
$.asap (-> chrome? or (video.readyState >= 3 and video.currentTime <= Math.max 0.1, (video.duration - 0.5)) or new Date().getTime() >= t + 1000), ->
video.controls = true
$.on video, 'mouseover', handler