diff --git a/CHANGELOG.md b/CHANGELOG.md index c39d350cb..4555914bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ **ccd0**: - Support expansion of .webm videos. +- Support hover for .webm videos. +- Add .webm to supported posting types. +- Add option to enable/disable sound. - Update archives with data from MayhemYDG fork. ### v2.9.11 diff --git a/builds/4chan-X.meta.js b/builds/4chan-X.meta.js index dad0b6be8..636db037a 100755 --- a/builds/4chan-X.meta.js +++ b/builds/4chan-X.meta.js @@ -1,6 +1,6 @@ // ==UserScript== // @name 4chan X -// @version 1.5.0 +// @version 1.5.1 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X diff --git a/builds/appchan-x.user.js b/builds/appchan-x.user.js index 145456160..b31984f18 100644 --- a/builds/appchan-x.user.js +++ b/builds/appchan-x.user.js @@ -172,7 +172,7 @@ }, 'Images and Videos': { 'Image Expansion': [true, 'Expand images / videos.'], - 'Image Hover': [true, 'Show full image on mouseover.'], + 'Image Hover': [true, 'Show full image / video on mouseover.'], 'Image Hover in Catalog': [false, 'Show a floating expanded image on hover in the catalog.'], 'Gallery': [true, 'Adds a simple and cute image gallery.'], 'Sauce': [true, 'Add sauce links to images.'], @@ -184,7 +184,8 @@ 'Fappe Tyme': [false, 'Hide posts without images when toggled. *hint* *hint*'], 'Werk Tyme': [false, 'Hide all post images when toggled.'], 'Autoplay': [true, 'Videos begin playing immediately when opened inline.'], - 'Show Controls': [true, 'Show native seek and volume controls on videos. Contract videos when dragged to the left.'] + 'Show Controls': [true, 'Show native seek and volume controls on videos. Contract videos when dragged to the left.'], + 'Allow Sound': [true, 'Allow sound in inline videos.'] }, 'Menu': { 'Menu': [true, 'Add a drop-down menu to posts.'], @@ -6684,7 +6685,9 @@ if (e.type === 'keydown' && e.keyCode !== 13 || e.target.nodeName === "TEXTAREA") { return; } - $.rm(this.el); + if (this.el.parentNode === Header.hover) { + $.rm(this.el); + } $.off(this.root, this.endEvents, this.hoverend); $.off(d, 'keydown', this.hoverend); $.off(this.root, 'mousemove', this.hover); @@ -8597,7 +8600,7 @@ }; QR = { - mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/x-shockwave-flash', ''], + mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/x-shockwave-flash', 'video/webm', ''], init: function() { var con, sc; this.db = new DataBoard('yourPosts'); @@ -9030,21 +9033,25 @@ }, checkDimensions: function(file, isSingle, max) { var img; - img = new Image(); - img.onload = (function(_this) { - return function() { - var height, width; - height = img.height, width = img.width; - if (height > QR.max_heigth || width > QR.max_heigth) { - return QR.error("" + file.name + ": Image too large (image: " + img.height + "x" + img.width + "px, max: " + QR.max_heigth + "x" + QR.max_width + "px)"); - } - if (height < QR.min_heigth || width < QR.min_heigth) { - return QR.error("" + file.name + ": Image too small (image: " + img.height + "x" + img.width + "px, min: " + QR.min_heigth + "x" + QR.min_width + "px)"); - } - return QR.handleFile(file, isSingle, max); - }; - })(this); - return img.src = URL.createObjectURL(file); + if (/^image\//.test(file.type)) { + img = new Image(); + img.onload = (function(_this) { + return function() { + var height, width; + height = img.height, width = img.width; + if (height > QR.max_heigth || width > QR.max_heigth) { + return QR.error("" + file.name + ": Image too large (image: " + img.height + "x" + img.width + "px, max: " + QR.max_heigth + "x" + QR.max_width + "px)"); + } + if (height < QR.min_heigth || width < QR.min_heigth) { + return QR.error("" + file.name + ": Image too small (image: " + img.height + "x" + img.width + "px, min: " + QR.min_heigth + "x" + QR.min_width + "px)"); + } + return QR.handleFile(file, isSingle, max); + }; + })(this); + return img.src = URL.createObjectURL(file); + } else { + return QR.handleFile(file, isSingle, max); + } }, handleFile: function(file, isSingle, max) { var post, _ref; @@ -10046,7 +10053,7 @@ return; } this.file.newName = this.filename.replace(/[/\\]/g, '-'); - if (!/\.(jpe?g|png|gif|pdf|swf)$/i.test(this.filename)) { + if (!/\.(jpe?g|png|gif|pdf|swf|webm)$/i.test(this.filename)) { this.file.newName += '.jpg'; } return this.updateFilename(); @@ -10765,9 +10772,12 @@ } $.addClass(thumb, 'expanding'); naturalHeight = isVideo ? 'videoHeight' : 'naturalHeight'; - if (post.file.fullImage) { + if (img = post.file.fullImage) { + $.rmClass(img, 'ihover'); + $.addClass(img, 'full-image'); + img.controls = img.parentNode !== thumb.parentNode; $.asap((function() { - return post.file.fullImage[naturalHeight]; + return img[naturalHeight]; }), function() { return ImageExpand.completeExpand(post); }); @@ -10819,7 +10829,7 @@ file = post.file; video = file.fullImage; file.videoControls = []; - video.muted = true; + video.muted = !Conf['Allow Sound']; if (video.controls) { contract = $.el('a', { textContent: 'contract', @@ -10972,8 +10982,8 @@ } }, node: function() { - var _ref; - if (!((_ref = this.file) != null ? _ref.isImage : void 0)) { + var _ref, _ref1; + if (!(((_ref = this.file) != null ? _ref.isImage : void 0) || ((_ref1 = this.file) != null ? _ref1.isVideo : void 0))) { return; } return $.on(this.file.thumb, 'mouseover', ImageHover.mouseover); @@ -10986,21 +10996,46 @@ return $.on(this.nodes.thumb, 'mouseover', ImageHover.mouseover); }, mouseover: function(e) { - var el, post; + var el, isVideo, naturalHeight, post, thumb; post = $.hasClass(this, 'thumb') ? g.posts[this.parentNode.dataset.fullID] : Get.postFromNode(this); - el = $.el('img', { - id: 'ihover', - src: post.file.URL - }); + isVideo = post.file.isVideo; + if (post.file.fullImage) { + el = post.file.fullImage; + $.rmClass(el, 'full-image'); + $.addClass(el, 'ihover'); + } else { + el = $.el((isVideo ? 'video' : 'img'), { + className: 'ihover', + src: post.file.URL + }); + post.file.fullImage = el; + thumb = post.file.thumb; + $.after((isVideo && Conf['Show Controls'] ? thumb.parentNode : thumb), el); + } el.dataset.fullID = post.fullID; - $.add(Header.hover, el); + if (isVideo) { + el.loop = true; + el.controls = false; + el.muted = !Conf['Allow Sound']; + if (Conf['Autoplay']) { + el.play(); + } + } + naturalHeight = post.file.isVideo ? 'videoHeight' : 'naturalHeight'; UI.hover({ root: this, el: el, latestEvent: e, endEvents: 'mouseout click', asapTest: function() { - return el.naturalHeight; + return el[naturalHeight]; + }, + cb: function() { + if (isVideo) { + el.pause(); + } + $.rmClass(el, 'ihover'); + return $.addClass(el, 'full-image'); } }); return $.on(el, 'error', ImageHover.error); diff --git a/builds/crx.crx b/builds/crx.crx index a1f50ec4d..a2a647a0a 100644 Binary files a/builds/crx.crx and b/builds/crx.crx differ diff --git a/builds/crx/script.js b/builds/crx/script.js index 049a1b670..da44bcaca 100644 --- a/builds/crx/script.js +++ b/builds/crx/script.js @@ -147,7 +147,7 @@ }, 'Images and Videos': { 'Image Expansion': [true, 'Expand images / videos.'], - 'Image Hover': [true, 'Show full image on mouseover.'], + 'Image Hover': [true, 'Show full image / video on mouseover.'], 'Image Hover in Catalog': [false, 'Show a floating expanded image on hover in the catalog.'], 'Gallery': [true, 'Adds a simple and cute image gallery.'], 'Sauce': [true, 'Add sauce links to images.'], @@ -159,7 +159,8 @@ 'Fappe Tyme': [false, 'Hide posts without images when toggled. *hint* *hint*'], 'Werk Tyme': [false, 'Hide all post images when toggled.'], 'Autoplay': [true, 'Videos begin playing immediately when opened inline.'], - 'Show Controls': [true, 'Show native seek and volume controls on videos. Contract videos when dragged to the left.'] + 'Show Controls': [true, 'Show native seek and volume controls on videos. Contract videos when dragged to the left.'], + 'Allow Sound': [true, 'Allow sound in inline videos.'] }, 'Menu': { 'Menu': [true, 'Add a drop-down menu to posts.'], @@ -6738,7 +6739,9 @@ if (e.type === 'keydown' && e.keyCode !== 13 || e.target.nodeName === "TEXTAREA") { return; } - $.rm(this.el); + if (this.el.parentNode === Header.hover) { + $.rm(this.el); + } $.off(this.root, this.endEvents, this.hoverend); $.off(d, 'keydown', this.hoverend); $.off(this.root, 'mousemove', this.hover); @@ -8650,7 +8653,7 @@ }; QR = { - mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/x-shockwave-flash', ''], + mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/x-shockwave-flash', 'video/webm', ''], init: function() { var con, sc; this.db = new DataBoard('yourPosts'); @@ -9085,21 +9088,25 @@ }, checkDimensions: function(file, isSingle, max) { var img; - img = new Image(); - img.onload = (function(_this) { - return function() { - var height, width; - height = img.height, width = img.width; - if (height > QR.max_heigth || width > QR.max_heigth) { - return QR.error("" + file.name + ": Image too large (image: " + img.height + "x" + img.width + "px, max: " + QR.max_heigth + "x" + QR.max_width + "px)"); - } - if (height < QR.min_heigth || width < QR.min_heigth) { - return QR.error("" + file.name + ": Image too small (image: " + img.height + "x" + img.width + "px, min: " + QR.min_heigth + "x" + QR.min_width + "px)"); - } - return QR.handleFile(file, isSingle, max); - }; - })(this); - return img.src = URL.createObjectURL(file); + if (/^image\//.test(file.type)) { + img = new Image(); + img.onload = (function(_this) { + return function() { + var height, width; + height = img.height, width = img.width; + if (height > QR.max_heigth || width > QR.max_heigth) { + return QR.error("" + file.name + ": Image too large (image: " + img.height + "x" + img.width + "px, max: " + QR.max_heigth + "x" + QR.max_width + "px)"); + } + if (height < QR.min_heigth || width < QR.min_heigth) { + return QR.error("" + file.name + ": Image too small (image: " + img.height + "x" + img.width + "px, min: " + QR.min_heigth + "x" + QR.min_width + "px)"); + } + return QR.handleFile(file, isSingle, max); + }; + })(this); + return img.src = URL.createObjectURL(file); + } else { + return QR.handleFile(file, isSingle, max); + } }, handleFile: function(file, isSingle, max) { var post, _ref; @@ -10084,7 +10091,7 @@ return; } this.file.newName = this.filename.replace(/[/\\]/g, '-'); - if (!/\.(jpe?g|png|gif|pdf|swf)$/i.test(this.filename)) { + if (!/\.(jpe?g|png|gif|pdf|swf|webm)$/i.test(this.filename)) { this.file.newName += '.jpg'; } return this.updateFilename(); @@ -10803,9 +10810,12 @@ } $.addClass(thumb, 'expanding'); naturalHeight = isVideo ? 'videoHeight' : 'naturalHeight'; - if (post.file.fullImage) { + if (img = post.file.fullImage) { + $.rmClass(img, 'ihover'); + $.addClass(img, 'full-image'); + img.controls = img.parentNode !== thumb.parentNode; $.asap((function() { - return post.file.fullImage[naturalHeight]; + return img[naturalHeight]; }), function() { return ImageExpand.completeExpand(post); }); @@ -10857,7 +10867,7 @@ file = post.file; video = file.fullImage; file.videoControls = []; - video.muted = true; + video.muted = !Conf['Allow Sound']; if (video.controls) { contract = $.el('a', { textContent: 'contract', @@ -10999,8 +11009,8 @@ } }, node: function() { - var _ref; - if (!((_ref = this.file) != null ? _ref.isImage : void 0)) { + var _ref, _ref1; + if (!(((_ref = this.file) != null ? _ref.isImage : void 0) || ((_ref1 = this.file) != null ? _ref1.isVideo : void 0))) { return; } return $.on(this.file.thumb, 'mouseover', ImageHover.mouseover); @@ -11013,21 +11023,46 @@ return $.on(this.nodes.thumb, 'mouseover', ImageHover.mouseover); }, mouseover: function(e) { - var el, post; + var el, isVideo, naturalHeight, post, thumb; post = $.hasClass(this, 'thumb') ? g.posts[this.parentNode.dataset.fullID] : Get.postFromNode(this); - el = $.el('img', { - id: 'ihover', - src: post.file.URL - }); + isVideo = post.file.isVideo; + if (post.file.fullImage) { + el = post.file.fullImage; + $.rmClass(el, 'full-image'); + $.addClass(el, 'ihover'); + } else { + el = $.el((isVideo ? 'video' : 'img'), { + className: 'ihover', + src: post.file.URL + }); + post.file.fullImage = el; + thumb = post.file.thumb; + $.after((isVideo && Conf['Show Controls'] ? thumb.parentNode : thumb), el); + } el.dataset.fullID = post.fullID; - $.add(Header.hover, el); + if (isVideo) { + el.loop = true; + el.controls = false; + el.muted = !Conf['Allow Sound']; + if (Conf['Autoplay']) { + el.play(); + } + } + naturalHeight = post.file.isVideo ? 'videoHeight' : 'naturalHeight'; UI.hover({ root: this, el: el, latestEvent: e, endEvents: 'mouseout click', asapTest: function() { - return el.naturalHeight; + return el[naturalHeight]; + }, + cb: function() { + if (isVideo) { + el.pause(); + } + $.rmClass(el, 'ihover'); + return $.addClass(el, 'full-image'); } }); return $.on(el, 'error', ImageHover.error); diff --git a/builds/updates.xml b/builds/updates.xml new file mode 100644 index 000000000..b25f40d4d --- /dev/null +++ b/builds/updates.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/General/Config.coffee b/src/General/Config.coffee index 183960169..7a0b79695 100644 --- a/src/General/Config.coffee +++ b/src/General/Config.coffee @@ -121,7 +121,7 @@ Config = ] 'Image Hover': [ true - 'Show full image on mouseover.' + 'Show full image / video on mouseover.' ] 'Image Hover in Catalog': [ false @@ -171,6 +171,10 @@ Config = true 'Show native seek and volume controls on videos. Contract videos when dragged to the left.' ] + 'Allow Sound': [ + true + 'Allow sound in inline videos.' + ] 'Menu': 'Menu': [ diff --git a/src/General/UI.coffee b/src/General/UI.coffee index 0e9ebf418..6983adafb 100755 --- a/src/General/UI.coffee +++ b/src/General/UI.coffee @@ -366,7 +366,7 @@ UI = do -> hoverend = (e) -> return if e.type is 'keydown' and e.keyCode isnt 13 or e.target.nodeName is "TEXTAREA" - $.rm @el + $.rm @el if @el.parentNode is Header.hover $.off @root, @endEvents, @hoverend $.off d, 'keydown', @hoverend $.off @root, 'mousemove', @hover diff --git a/src/General/css/style.css b/src/General/css/style.css index 28bc9791d..51208e44a 100755 --- a/src/General/css/style.css +++ b/src/General/css/style.css @@ -108,7 +108,7 @@ div.center:not(.ad-cnt) { /* fixed, z-index */ #overlay, #fourchanx-settings, -#qp, #ihover, +#qp, .ihover, #navlinks, .fixed #header-bar, :root.float #updater, :root.float #thread-stats, @@ -124,7 +124,7 @@ div.center:not(.ad-cnt) { #notifications { z-index: 70; } -#qp, #ihover { +#qp, .ihover { z-index: 60; } #menu { @@ -840,7 +840,7 @@ span.hide-announcement { :root.gecko.fit-width .full-image { width: 100%; } -#ihover { +.ihover { -moz-box-sizing: border-box; box-sizing: border-box; max-height: 100%; diff --git a/src/Images/ImageExpand.coffee b/src/Images/ImageExpand.coffee index f54345a0d..bdf2aaf7c 100644 --- a/src/Images/ImageExpand.coffee +++ b/src/Images/ImageExpand.coffee @@ -103,9 +103,12 @@ ImageExpand = return if post.isHidden or post.file.isExpanded or $.hasClass thumb, 'expanding' $.addClass thumb, 'expanding' naturalHeight = if isVideo then 'videoHeight' else 'naturalHeight' - if post.file.fullImage + if img = post.file.fullImage # Expand already-loaded/ing picture. - $.asap (-> post.file.fullImage[naturalHeight]), -> + $.rmClass img, 'ihover' + $.addClass img, 'full-image' + img.controls = (img.parentNode isnt thumb.parentNode) + $.asap (-> img[naturalHeight]), -> ImageExpand.completeExpand post return post.file.fullImage = img = $.el (if isVideo then 'video' else 'img'), @@ -141,7 +144,7 @@ ImageExpand = {file} = post video = file.fullImage file.videoControls = [] - video.muted = true + video.muted = not Conf['Allow Sound'] if video.controls # contract link in file info contract = $.el 'a', diff --git a/src/Images/ImageHover.coffee b/src/Images/ImageHover.coffee index 808bb9e85..f5816f210 100755 --- a/src/Images/ImageHover.coffee +++ b/src/Images/ImageHover.coffee @@ -9,7 +9,7 @@ ImageHover = name: 'Image Hover' cb: @catalogNode node: -> - return unless @file?.isImage + return unless @file?.isImage or @file?.isVideo $.on @file.thumb, 'mouseover', ImageHover.mouseover catalogNode: -> return unless @thread.OP.file?.isImage @@ -19,17 +19,35 @@ ImageHover = g.posts[@parentNode.dataset.fullID] else Get.postFromNode @ - el = $.el 'img', - id: 'ihover' - src: post.file.URL + {isVideo} = post.file + if post.file.fullImage + el = post.file.fullImage + $.rmClass el, 'full-image' + $.addClass el, 'ihover' + else + el = $.el (if isVideo then 'video' else 'img'), + className: 'ihover' + src: post.file.URL + post.file.fullImage = el + {thumb} = post.file + $.after (if isVideo and Conf['Show Controls'] then thumb.parentNode else thumb), el el.dataset.fullID = post.fullID - $.add Header.hover, el + if isVideo + el.loop = true + el.controls = false + el.muted = not Conf['Allow Sound'] + el.play() if Conf['Autoplay'] + naturalHeight = if post.file.isVideo then 'videoHeight' else 'naturalHeight' UI.hover root: @ el: el latestEvent: e endEvents: 'mouseout click' - asapTest: -> el.naturalHeight + asapTest: -> el[naturalHeight] + cb: -> + el.pause() if isVideo + $.rmClass el, 'ihover' + $.addClass el, 'full-image' $.on el, 'error', ImageHover.error error: -> return unless doc.contains @ diff --git a/src/Posting/QR.coffee b/src/Posting/QR.coffee index ad504c7a1..165016a70 100644 --- a/src/Posting/QR.coffee +++ b/src/Posting/QR.coffee @@ -1,6 +1,6 @@ QR = # Add empty mimeType to avoid errors with URLs selected in Window's file dialog. - mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/x-shockwave-flash', ''] + mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/x-shockwave-flash', 'video/webm', ''] init: -> @db = new DataBoard 'yourPosts' @@ -371,13 +371,16 @@ QR = $.addClass QR.nodes.el, 'dump' unless isSingle checkDimensions: (file, isSingle, max) -> - img = new Image() - img.onload = => - {height, width} = img - return QR.error "#{file.name}: Image too large (image: #{img.height}x#{img.width}px, max: #{QR.max_heigth}x#{QR.max_width}px)" if height > QR.max_heigth or width > QR.max_heigth - return QR.error "#{file.name}: Image too small (image: #{img.height}x#{img.width}px, min: #{QR.min_heigth}x#{QR.min_width}px)" if height < QR.min_heigth or width < QR.min_heigth + if /^image\//.test file.type + img = new Image() + img.onload = => + {height, width} = img + return QR.error "#{file.name}: Image too large (image: #{img.height}x#{img.width}px, max: #{QR.max_heigth}x#{QR.max_width}px)" if height > QR.max_heigth or width > QR.max_heigth + return QR.error "#{file.name}: Image too small (image: #{img.height}x#{img.width}px, min: #{QR.min_heigth}x#{QR.min_width}px)" if height < QR.min_heigth or width < QR.min_heigth + QR.handleFile file, isSingle, max + img.src = URL.createObjectURL file + else QR.handleFile file, isSingle, max - img.src = URL.createObjectURL file handleFile: (file, isSingle, max) -> if file.size > max diff --git a/src/Posting/QR.post.coffee b/src/Posting/QR.post.coffee index 089503b9e..799c17e9f 100644 --- a/src/Posting/QR.post.coffee +++ b/src/Posting/QR.post.coffee @@ -146,7 +146,7 @@ QR.post = class when 'filename' return unless @file @file.newName = @filename.replace /[/\\]/g, '-' - unless /\.(jpe?g|png|gif|pdf|swf)$/i.test @filename + unless /\.(jpe?g|png|gif|pdf|swf|webm)$/i.test @filename # 4chan will truncate the filename if it has no extension, # but it will always replace the extension by the correct one, # so we suffix it with '.jpg' when needed.