From 9f532ef34ebd227062759a78f67177993f6bd3da Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Feb 28 2018 14:59:50 +0000 Subject: [PATCH 1/3] Customize the markdown image processor With our customized processor, we set 'src' in the img tag to be empty while its old value is placed into a 'data-src'. In addition, we're making the img tag be of the class 'lazyload'. Signed-off-by: Pierre-Yves Chibon --- diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py index cc657b5..7fb8fc0 100644 --- a/pagure/lib/__init__.py +++ b/pagure/lib/__init__.py @@ -3718,7 +3718,8 @@ def text2markdown(text, extended=True, readme=False): 'markdown.extensions.codehilite': { 'guess_lang': False, } - } + }, + output_format='xhtml5', ) if text: @@ -3735,7 +3736,7 @@ def text2markdown(text, extended=True, readme=False): def filter_img_src(name, value): ''' Filter in img html tags images coming from a different domain. ''' - if name in ('alt', 'height', 'width', 'class'): + if name in ('alt', 'height', 'width', 'class', 'data-src'): return True if name == 'src': parsed = urlparse.urlparse(value) diff --git a/pagure/pfmarkdown.py b/pagure/pfmarkdown.py index 414175e..8a012f4 100644 --- a/pagure/pfmarkdown.py +++ b/pagure/pfmarkdown.py @@ -321,6 +321,17 @@ class AutolinkPattern2(markdown.inlinepatterns.Pattern): return el +class ImagePatternLazyLoad(markdown.inlinepatterns.ImagePattern): + """ Customize the image element matched for lazyloading. """ + + def handleMatch(self, m): + el = super(ImagePatternLazyLoad, self).handleMatch(m) + el.set('class', 'lazyload') + el.set('data-src', el.get('src')) + el.set('src', '') + return el + + class PagureExtension(markdown.extensions.Extension): def extendMarkdown(self, md, md_globals): @@ -337,6 +348,10 @@ class PagureExtension(markdown.extensions.Extension): md.inlinePatterns['mention'] = MentionPattern(MENTION_RE) + # Customize the image linking to support lazy loading + md.inlinePatterns["image_link"] = ImagePatternLazyLoad( + markdown.inlinepatterns.IMAGE_LINK_RE, md) + md.inlinePatterns['implicit_commit'] = ImplicitCommitPattern( IMPLICIT_COMMIT_RE) md.inlinePatterns['commit_links'] = CommitLinkPattern( From 8f8ef5ed7584891fdaed964435cbfe649eb7337d Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Feb 28 2018 14:59:50 +0000 Subject: [PATCH 2/3] Make images be lazy loaded via javascript. Fixes https://pagure.io/pagure/issue/2975 Signed-off-by: Pierre-Yves Chibon --- diff --git a/pagure/static/vendor/lazyload/lazyload.2.0.0-beta2.js b/pagure/static/vendor/lazyload/lazyload.2.0.0-beta2.js new file mode 100644 index 0000000..fc7fdf7 --- /dev/null +++ b/pagure/static/vendor/lazyload/lazyload.2.0.0-beta2.js @@ -0,0 +1,173 @@ +/*! + * Lazy Load - JavaScript plugin for lazy loading images + * + * Copyright (c) 2007-2017 Mika Tuupola + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * Project home: + * https://appelsiini.net/projects/lazyload + * + * Version: 2.0.0-beta.2 + * + */ + +(function (root, factory) { + if (typeof exports === "object") { + module.exports = factory(root); + } else if (typeof define === "function" && define.amd) { + define([], factory(root)); + } else { + root.LazyLoad = factory(root); + } +}) (typeof global !== "undefined" ? global : this.window || this.global, function (root) { + + "use strict"; + + const defaults = { + src: "data-src", + srcset: "data-srcset", + selector: ".lazyload" + }; + + /** + * Merge two or more objects. Returns a new object. + * @private + * @param {Boolean} deep If true, do a deep (or recursive) merge [optional] + * @param {Object} objects The objects to merge together + * @returns {Object} Merged values of defaults and options + */ + const extend = function () { + + let extended = {}; + let deep = false; + let i = 0; + let length = arguments.length; + + /* Check if a deep merge */ + if (Object.prototype.toString.call(arguments[0]) === "[object Boolean]") { + deep = arguments[0]; + i++; + } + + /* Merge the object into the extended object */ + let merge = function (obj) { + for (let prop in obj) { + if (Object.prototype.hasOwnProperty.call(obj, prop)) { + /* If deep merge and property is an object, merge properties */ + if (deep && Object.prototype.toString.call(obj[prop]) === "[object Object]") { + extended[prop] = extend(true, extended[prop], obj[prop]); + } else { + extended[prop] = obj[prop]; + } + } + } + }; + + /* Loop through each object and conduct a merge */ + for (; i < length; i++) { + let obj = arguments[i]; + merge(obj); + } + + return extended; + }; + + function LazyLoad(images, options) { + this.settings = extend(defaults, options || {}); + this.images = images || document.querySelectorAll(this.settings.selector); + this.observer = null; + this.init(); + } + + LazyLoad.prototype = { + init: function() { + + /* Without observers load everything and bail out early. */ + if (!root.IntersectionObserver) { + this.loadImages(); + return; + } + + let self = this; + let observerConfig = { + root: null, + rootMargin: "0px", + threshold: [0] + }; + + this.observer = new IntersectionObserver(function(entries) { + entries.forEach(function (entry) { + if (entry.intersectionRatio > 0) { + self.observer.unobserve(entry.target); + let src = entry.target.getAttribute(self.settings.src); + let srcset = entry.target.getAttribute(self.settings.srcset); + if ("img" === entry.target.tagName.toLowerCase()) { + if (src) { + entry.target.src = src; + } + if (srcset) { + entry.target.srcset = srcset; + } + } else { + entry.target.style.backgroundImage = "url(" + src + ")"; + } + } + }); + }, observerConfig); + + this.images.forEach(function (image) { + self.observer.observe(image); + }); + }, + + loadAndDestroy: function () { + if (!this.settings) { return; } + this.loadImages(); + this.destroy(); + }, + + loadImages: function () { + if (!this.settings) { return; } + + let self = this; + this.images.forEach(function (image) { + let src = image.getAttribute(self.settings.src); + let srcset = image.getAttribute(self.settings.srcset); + if ("img" === image.tagName.toLowerCase()) { + if (src) { + image.src = src; + } + if (srcset) { + image.srcset = srcset; + } + } else { + image.style.backgroundImage = "url(" + src + ")"; + } + }); + }, + + destroy: function () { + if (!this.settings) { return; } + this.observer.disconnect(); + this.settings = null; + } + }; + + root.lazyload = function(images, options) { + return new LazyLoad(images, options); + }; + + if (root.jQuery) { + const $ = root.jQuery; + $.fn.lazyload = function (options) { + options = options || {}; + options.attribute = options.attribute || "data-src"; + new LazyLoad($.makeArray(this), options); + return this; + }; + } + + return LazyLoad; +}); diff --git a/pagure/static/vendor/lazyload/lazyload.2.0.0-beta2.min.js b/pagure/static/vendor/lazyload/lazyload.2.0.0-beta2.min.js new file mode 100644 index 0000000..31be8df --- /dev/null +++ b/pagure/static/vendor/lazyload/lazyload.2.0.0-beta2.min.js @@ -0,0 +1,2 @@ +/*! Lazy Load 2.0.0-beta.2 - MIT license - Copyright 2007-2017 Mika Tuupola */ +!function(t,e){"object"==typeof exports?module.exports=e(t):"function"==typeof define&&define.amd?define([],e(t)):t.LazyLoad=e(t)}("undefined"!=typeof global?global:this.window||this.global,function(t){"use strict";function e(t,e){this.settings=r(s,e||{}),this.images=t||document.querySelectorAll(this.settings.selector),this.observer=null,this.init()}const s={src:"data-src",srcset:"data-srcset",selector:".lazyload"},r=function(){let t={},e=!1,s=0,o=arguments.length;"[object Boolean]"===Object.prototype.toString.call(arguments[0])&&(e=arguments[0],s++);for(;s0){e.observer.unobserve(t.target);let s=t.target.getAttribute(e.settings.src),r=t.target.getAttribute(e.settings.srcset);"img"===t.target.tagName.toLowerCase()?(s&&(t.target.src=s),r&&(t.target.srcset=r)):t.target.style.backgroundImage="url("+s+")"}})},s),this.images.forEach(function(t){e.observer.observe(t)})},loadAndDestroy:function(){this.settings&&(this.loadImages(),this.destroy())},loadImages:function(){if(!this.settings)return;let t=this;this.images.forEach(function(e){let s=e.getAttribute(t.settings.src),r=e.getAttribute(t.settings.srcset);"img"===e.tagName.toLowerCase()?(s&&(e.src=s),r&&(e.srcset=r)):e.style.backgroundImage="url("+s+")"})},destroy:function(){this.settings&&(this.observer.disconnect(),this.settings=null)}},t.lazyload=function(t,s){return new e(t,s)},t.jQuery){const s=t.jQuery;s.fn.lazyload=function(t){return t=t||{},t.attribute=t.attribute||"data-src",new e(s.makeArray(this),t),this}}return e}); diff --git a/pagure/static/vendor/lazyload/lazyload.js b/pagure/static/vendor/lazyload/lazyload.js new file mode 120000 index 0000000..84ba09a --- /dev/null +++ b/pagure/static/vendor/lazyload/lazyload.js @@ -0,0 +1 @@ +lazyload.2.0.0-beta2.js \ No newline at end of file diff --git a/pagure/static/vendor/lazyload/lazyload.min.js b/pagure/static/vendor/lazyload/lazyload.min.js new file mode 120000 index 0000000..e5de9ac --- /dev/null +++ b/pagure/static/vendor/lazyload/lazyload.min.js @@ -0,0 +1 @@ +lazyload.2.0.0-beta2.min.js \ No newline at end of file diff --git a/pagure/templates/repo_master.html b/pagure/templates/repo_master.html index f3f18c4..2c29968 100644 --- a/pagure/templates/repo_master.html +++ b/pagure/templates/repo_master.html @@ -378,7 +378,15 @@ {% block jscripts %} {{ super() }} +