Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
52b8a0db
Commit
52b8a0db
authored
Jul 24, 2017
by
Tim Zallmann
Committed by
Jacob Schatz
Jul 24, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Resolve "Lazy load images on the Frontend"
parent
3a26bce8
Changes
31
Hide whitespace changes
Inline
Side-by-side
Showing
31 changed files
with
287 additions
and
68 deletions
+287
-68
app/assets/javascripts/copy_as_gfm.js
app/assets/javascripts/copy_as_gfm.js
+9
-1
app/assets/javascripts/lazy_loader.js
app/assets/javascripts/lazy_loader.js
+76
-0
app/assets/javascripts/main.js
app/assets/javascripts/main.js
+6
-0
app/assets/stylesheets/framework/avatar.scss
app/assets/stylesheets/framework/avatar.scss
+2
-0
app/assets/stylesheets/framework/typography.scss
app/assets/stylesheets/framework/typography.scss
+10
-1
app/assets/stylesheets/framework/variables.scss
app/assets/stylesheets/framework/variables.scss
+3
-1
app/helpers/avatars_helper.rb
app/helpers/avatars_helper.rb
+2
-7
app/helpers/emails_helper.rb
app/helpers/emails_helper.rb
+2
-2
app/helpers/lazy_image_tag_helper.rb
app/helpers/lazy_image_tag_helper.rb
+24
-0
app/helpers/version_check_helper.rb
app/helpers/version_check_helper.rb
+1
-1
app/models/concerns/cache_markdown_field.rb
app/models/concerns/cache_markdown_field.rb
+1
-1
app/views/projects/blob/viewers/_image.html.haml
app/views/projects/blob/viewers/_image.html.haml
+1
-1
app/views/projects/diffs/viewers/_image.html.haml
app/views/projects/diffs/viewers/_image.html.haml
+7
-7
changelogs/unreleased/34361-lazy-load-images-on-the-frontend.yml
...ogs/unreleased/34361-lazy-load-images-on-the-frontend.yml
+4
-0
doc/development/fe_guide/performance.md
doc/development/fe_guide/performance.md
+12
-0
features/steps/project/wiki.rb
features/steps/project/wiki.rb
+1
-1
lib/banzai/filter/gollum_tags_filter.rb
lib/banzai/filter/gollum_tags_filter.rb
+1
-1
lib/banzai/filter/image_lazy_load_filter.rb
lib/banzai/filter/image_lazy_load_filter.rb
+16
-0
lib/banzai/filter/image_link_filter.rb
lib/banzai/filter/image_link_filter.rb
+1
-1
lib/banzai/filter/relative_link_filter.rb
lib/banzai/filter/relative_link_filter.rb
+1
-0
lib/banzai/pipeline/gfm_pipeline.rb
lib/banzai/pipeline/gfm_pipeline.rb
+1
-0
spec/features/admin/admin_appearance_spec.rb
spec/features/admin/admin_appearance_spec.rb
+2
-2
spec/features/markdown_spec.rb
spec/features/markdown_spec.rb
+1
-1
spec/features/uploads/user_uploads_avatar_to_group_spec.rb
spec/features/uploads/user_uploads_avatar_to_group_spec.rb
+1
-1
spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
+1
-1
spec/helpers/application_helper_spec.rb
spec/helpers/application_helper_spec.rb
+4
-3
spec/helpers/avatars_helper_spec.rb
spec/helpers/avatars_helper_spec.rb
+17
-31
spec/javascripts/lazy_loader_spec.js
spec/javascripts/lazy_loader_spec.js
+57
-0
spec/lib/banzai/filter/gollum_tags_filter_spec.rb
spec/lib/banzai/filter/gollum_tags_filter_spec.rb
+2
-2
spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
+19
-0
spec/support/matchers/markdown_matchers.rb
spec/support/matchers/markdown_matchers.rb
+2
-2
No files found.
app/assets/javascripts/copy_as_gfm.js
View file @
52b8a0db
/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
import
'
./lib/utils/common_utils
'
;
import
'
./lib/utils/common_utils
'
;
import
{
placeholderImage
}
from
'
./lazy_loader
'
;
const
gfmRules
=
{
const
gfmRules
=
{
// The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
// The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
...
@@ -56,6 +57,11 @@ const gfmRules = {
...
@@ -56,6 +57,11 @@ const gfmRules = {
return
text
;
return
text
;
},
},
},
},
ImageLazyLoadFilter
:
{
'
img
'
(
el
,
text
)
{
return
`![
${
el
.
getAttribute
(
'
alt
'
)}
](
${
el
.
getAttribute
(
'
src
'
)}
)`
;
},
},
VideoLinkFilter
:
{
VideoLinkFilter
:
{
'
.video-container
'
(
el
)
{
'
.video-container
'
(
el
)
{
const
videoEl
=
el
.
querySelector
(
'
video
'
);
const
videoEl
=
el
.
querySelector
(
'
video
'
);
...
@@ -163,7 +169,9 @@ const gfmRules = {
...
@@ -163,7 +169,9 @@ const gfmRules = {
return
text
.
trim
().
split
(
'
\n
'
).
map
(
s
=>
`>
${
s
}
`
.
trim
()).
join
(
'
\n
'
);
return
text
.
trim
().
split
(
'
\n
'
).
map
(
s
=>
`>
${
s
}
`
.
trim
()).
join
(
'
\n
'
);
},
},
'
img
'
(
el
)
{
'
img
'
(
el
)
{
return
`![
${
el
.
getAttribute
(
'
alt
'
)}
](
${
el
.
getAttribute
(
'
src
'
)}
)`
;
const
imageSrc
=
el
.
src
;
const
imageUrl
=
imageSrc
&&
imageSrc
!==
placeholderImage
?
imageSrc
:
(
el
.
dataset
.
src
||
''
);
return
`![
${
el
.
getAttribute
(
'
alt
'
)}
](
${
imageUrl
}
)`
;
},
},
'
a.anchor
'
(
el
,
text
)
{
'
a.anchor
'
(
el
,
text
)
{
// Don't render a Markdown link for the anchor link inside a heading
// Don't render a Markdown link for the anchor link inside a heading
...
...
app/assets/javascripts/lazy_loader.js
0 → 100644
View file @
52b8a0db
/* eslint-disable one-export, one-var, one-var-declaration-per-line */
import
_
from
'
underscore
'
;
export
const
placeholderImage
=
'
data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
'
;
const
SCROLL_THRESHOLD
=
300
;
export
default
class
LazyLoader
{
constructor
(
options
=
{})
{
this
.
lazyImages
=
[];
this
.
observerNode
=
options
.
observerNode
||
'
#content-body
'
;
const
throttledScrollCheck
=
_
.
throttle
(()
=>
this
.
scrollCheck
(),
300
);
const
debouncedElementsInView
=
_
.
debounce
(()
=>
this
.
checkElementsInView
(),
300
);
window
.
addEventListener
(
'
scroll
'
,
throttledScrollCheck
);
window
.
addEventListener
(
'
resize
'
,
debouncedElementsInView
);
const
scrollContainer
=
options
.
scrollContainer
||
window
;
scrollContainer
.
addEventListener
(
'
load
'
,
()
=>
this
.
loadCheck
());
}
searchLazyImages
()
{
this
.
lazyImages
=
[].
slice
.
call
(
document
.
querySelectorAll
(
'
.lazy
'
));
this
.
checkElementsInView
();
}
startContentObserver
()
{
const
contentNode
=
document
.
querySelector
(
this
.
observerNode
)
||
document
.
querySelector
(
'
body
'
);
if
(
contentNode
)
{
const
observer
=
new
MutationObserver
(()
=>
this
.
searchLazyImages
());
observer
.
observe
(
contentNode
,
{
childList
:
true
,
subtree
:
true
,
});
}
}
loadCheck
()
{
this
.
searchLazyImages
();
this
.
startContentObserver
();
}
scrollCheck
()
{
requestAnimationFrame
(()
=>
this
.
checkElementsInView
());
}
checkElementsInView
()
{
const
scrollTop
=
pageYOffset
;
const
visHeight
=
scrollTop
+
innerHeight
+
SCROLL_THRESHOLD
;
let
imgBoundRect
,
imgTop
,
imgBound
;
// Loading Images which are in the current viewport or close to them
this
.
lazyImages
=
this
.
lazyImages
.
filter
((
selectedImage
)
=>
{
if
(
selectedImage
.
getAttribute
(
'
data-src
'
))
{
imgBoundRect
=
selectedImage
.
getBoundingClientRect
();
imgTop
=
scrollTop
+
imgBoundRect
.
top
;
imgBound
=
imgTop
+
imgBoundRect
.
height
;
if
(
scrollTop
<
imgBound
&&
visHeight
>
imgTop
)
{
LazyLoader
.
loadImage
(
selectedImage
);
return
false
;
}
return
true
;
}
return
false
;
});
}
static
loadImage
(
img
)
{
if
(
img
.
getAttribute
(
'
data-src
'
))
{
img
.
setAttribute
(
'
src
'
,
img
.
getAttribute
(
'
data-src
'
));
img
.
removeAttribute
(
'
data-src
'
);
img
.
classList
.
remove
(
'
lazy
'
);
img
.
classList
.
add
(
'
js-lazy-loaded
'
);
}
}
}
app/assets/javascripts/main.js
View file @
52b8a0db
...
@@ -109,6 +109,7 @@ import './label_manager';
...
@@ -109,6 +109,7 @@ import './label_manager';
import
'
./labels
'
;
import
'
./labels
'
;
import
'
./labels_select
'
;
import
'
./labels_select
'
;
import
'
./layout_nav
'
;
import
'
./layout_nav
'
;
import
LazyLoader
from
'
./lazy_loader
'
;
import
'
./line_highlighter
'
;
import
'
./line_highlighter
'
;
import
'
./logo
'
;
import
'
./logo
'
;
import
'
./member_expiration_date
'
;
import
'
./member_expiration_date
'
;
...
@@ -166,6 +167,11 @@ window.addEventListener('load', function onLoad() {
...
@@ -166,6 +167,11 @@ window.addEventListener('load', function onLoad() {
gl
.
utils
.
handleLocationHash
();
gl
.
utils
.
handleLocationHash
();
},
false
);
},
false
);
gl
.
lazyLoader
=
new
LazyLoader
({
scrollContainer
:
window
,
observerNode
:
'
#content-body
'
});
$
(
function
()
{
$
(
function
()
{
var
$body
=
$
(
'
body
'
);
var
$body
=
$
(
'
body
'
);
var
$document
=
$
(
document
);
var
$document
=
$
(
document
);
...
...
app/assets/stylesheets/framework/avatar.scss
View file @
52b8a0db
...
@@ -35,6 +35,8 @@
...
@@ -35,6 +35,8 @@
width
:
40px
;
width
:
40px
;
height
:
40px
;
height
:
40px
;
padding
:
0
;
padding
:
0
;
background
:
$avatar-background
;
overflow
:
hidden
;
&
.avatar-inline
{
&
.avatar-inline
{
float
:
none
;
float
:
none
;
...
...
app/assets/stylesheets/framework/typography.scss
View file @
52b8a0db
...
@@ -11,8 +11,17 @@
...
@@ -11,8 +11,17 @@
}
}
img
{
img
{
max-width
:
100%
;
/*max-width: 100%;*/
margin
:
0
0
8px
;
margin
:
0
0
8px
;
min-width
:
200px
;
min-height
:
100px
;
background-color
:
$gray-lightest
;
}
img
.js-lazy-loaded
{
min-width
:
none
;
min-height
:
none
;
background-color
:
none
;
}
}
p
a
:not
(
.no-attachment-icon
)
img
{
p
a
:not
(
.no-attachment-icon
)
img
{
...
...
app/assets/stylesheets/framework/variables.scss
View file @
52b8a0db
...
@@ -379,7 +379,9 @@ $issue-boards-card-shadow: rgba(186, 186, 186, 0.5);
...
@@ -379,7 +379,9 @@ $issue-boards-card-shadow: rgba(186, 186, 186, 0.5);
* Avatar
* Avatar
*/
*/
$avatar_radius
:
50%
;
$avatar_radius
:
50%
;
$avatar-border
:
$border-color
;
$avatar-border
:
$gray-normal
;
$avatar-border-hover
:
$gray-darker
;
$avatar-background
:
$gray-lightest
;
$gl-avatar-size
:
40px
;
$gl-avatar-size
:
40px
;
/*
/*
...
...
app/helpers/avatars_helper.rb
View file @
52b8a0db
...
@@ -11,17 +11,12 @@ module AvatarsHelper
...
@@ -11,17 +11,12 @@ module AvatarsHelper
def
user_avatar_without_link
(
options
=
{})
def
user_avatar_without_link
(
options
=
{})
avatar_size
=
options
[
:size
]
||
16
avatar_size
=
options
[
:size
]
||
16
user_name
=
options
[
:user
].
try
(
:name
)
||
options
[
:user_name
]
user_name
=
options
[
:user
].
try
(
:name
)
||
options
[
:user_name
]
css_class
=
options
[
:css_class
]
||
''
avatar_url
=
options
[
:url
]
||
avatar_icon
(
options
[
:user
]
||
options
[
:user_email
],
avatar_size
)
avatar_url
=
options
[
:url
]
||
avatar_icon
(
options
[
:user
]
||
options
[
:user_email
],
avatar_size
)
data_attributes
=
{
container:
'body'
}
data_attributes
=
{
container:
'body'
}
if
options
[
:lazy
]
data_attributes
[
:src
]
=
avatar_url
end
image_tag
(
image_tag
(
options
[
:lazy
]
?
''
:
avatar_url
,
avatar_url
,
class:
"avatar has-tooltip s
#{
avatar_size
}
#{
css_class
}
"
,
class:
%W[avatar has-tooltip s
#{
avatar_size
}
]
.
push
(
*
options
[
:css_class
])
,
alt:
"
#{
user_name
}
's avatar"
,
alt:
"
#{
user_name
}
's avatar"
,
title:
user_name
,
title:
user_name
,
data:
data_attributes
data:
data_attributes
...
...
app/helpers/emails_helper.rb
View file @
52b8a0db
...
@@ -61,8 +61,8 @@ module EmailsHelper
...
@@ -61,8 +61,8 @@ module EmailsHelper
else
else
image_tag
(
image_tag
(
image_url
(
'mailers/gitlab_header_logo.gif'
),
image_url
(
'mailers/gitlab_header_logo.gif'
),
size:
"55x50"
,
size:
'55x50'
,
alt:
"GitLab"
alt:
'GitLab'
)
)
end
end
end
end
...
...
app/helpers/lazy_image_tag_helper.rb
0 → 100644
View file @
52b8a0db
module
LazyImageTagHelper
def
placeholder_image
"data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
end
# Override the default ActionView `image_tag` helper to support lazy-loading
def
image_tag
(
source
,
options
=
{})
options
=
options
.
symbolize_keys
unless
options
.
delete
(
:lazy
)
==
false
options
[
:data
]
||=
{}
options
[
:data
][
:src
]
=
path_to_image
(
source
)
options
[
:class
]
||=
""
options
[
:class
]
<<
" lazy"
source
=
placeholder_image
end
super
(
source
,
options
)
end
# Required for Banzai::Filter::ImageLazyLoadFilter
module_function
:placeholder_image
end
app/helpers/version_check_helper.rb
View file @
52b8a0db
...
@@ -2,7 +2,7 @@ module VersionCheckHelper
...
@@ -2,7 +2,7 @@ module VersionCheckHelper
def
version_status_badge
def
version_status_badge
if
Rails
.
env
.
production?
&&
current_application_settings
.
version_check_enabled
if
Rails
.
env
.
production?
&&
current_application_settings
.
version_check_enabled
image_url
=
VersionCheck
.
new
.
url
image_url
=
VersionCheck
.
new
.
url
image_tag
image_url
,
class:
'js-version-status-badge'
image_tag
image_url
,
class:
'js-version-status-badge'
,
lazy:
false
end
end
end
end
end
end
app/models/concerns/cache_markdown_field.rb
View file @
52b8a0db
...
@@ -11,7 +11,7 @@ module CacheMarkdownField
...
@@ -11,7 +11,7 @@ module CacheMarkdownField
extend
ActiveSupport
::
Concern
extend
ActiveSupport
::
Concern
# Increment this number every time the renderer changes its output
# Increment this number every time the renderer changes its output
CACHE_VERSION
=
1
CACHE_VERSION
=
2
# changes to these attributes cause the cache to be invalidates
# changes to these attributes cause the cache to be invalidates
INVALIDATED_BY
=
%w[author project]
.
freeze
INVALIDATED_BY
=
%w[author project]
.
freeze
...
...
app/views/projects/blob/viewers/_image.html.haml
View file @
52b8a0db
.file-content.image_file
.file-content.image_file
%img
{
src
:
blob_raw_url
,
alt:
viewer
.
blob
.
name
}
%img
{
'data-src'
:
blob_raw_url
,
alt:
viewer
.
blob
.
name
}
app/views/projects/diffs/viewers/_image.html.haml
View file @
52b8a0db
...
@@ -8,7 +8,7 @@
...
@@ -8,7 +8,7 @@
.image
.image
%span
.wrap
%span
.wrap
.frame
{
class:
(
diff_file
.
deleted_file?
?
'deleted'
:
'added'
)
}
.frame
{
class:
(
diff_file
.
deleted_file?
?
'deleted'
:
'added'
)
}
%img
{
src
:
blob_raw_path
,
alt:
diff_file
.
file_path
}
%img
{
'data-src'
:
blob_raw_path
,
alt:
diff_file
.
file_path
}
%p
.image-info
=
number_to_human_size
(
blob
.
size
)
%p
.image-info
=
number_to_human_size
(
blob
.
size
)
-
else
-
else
.image
.image
...
@@ -16,7 +16,7 @@
...
@@ -16,7 +16,7 @@
%span
.wrap
%span
.wrap
.frame.deleted
.frame.deleted
%a
{
href:
project_blob_path
(
@project
,
tree_join
(
diff_file
.
old_content_sha
,
diff_file
.
old_path
))
}
%a
{
href:
project_blob_path
(
@project
,
tree_join
(
diff_file
.
old_content_sha
,
diff_file
.
old_path
))
}
%img
{
src
:
old_blob_raw_path
,
alt:
diff_file
.
old_path
}
%img
{
'data-src'
:
old_blob_raw_path
,
alt:
diff_file
.
old_path
}
%p
.image-info.hide
%p
.image-info.hide
%span
.meta-filesize
=
number_to_human_size
(
old_blob
.
size
)
%span
.meta-filesize
=
number_to_human_size
(
old_blob
.
size
)
|
|
...
@@ -28,7 +28,7 @@
...
@@ -28,7 +28,7 @@
%span
.wrap
%span
.wrap
.frame.added
.frame.added
%a
{
href:
project_blob_path
(
@project
,
tree_join
(
diff_file
.
content_sha
,
diff_file
.
new_path
))
}
%a
{
href:
project_blob_path
(
@project
,
tree_join
(
diff_file
.
content_sha
,
diff_file
.
new_path
))
}
%img
{
src
:
blob_raw_path
,
alt:
diff_file
.
new_path
}
%img
{
'data-src'
:
blob_raw_path
,
alt:
diff_file
.
new_path
}
%p
.image-info.hide
%p
.image-info.hide
%span
.meta-filesize
=
number_to_human_size
(
blob
.
size
)
%span
.meta-filesize
=
number_to_human_size
(
blob
.
size
)
|
|
...
@@ -41,10 +41,10 @@
...
@@ -41,10 +41,10 @@
.swipe.view.hide
.swipe.view.hide
.swipe-frame
.swipe-frame
.frame.deleted
.frame.deleted
%img
{
src
:
old_blob_raw_path
,
alt:
diff_file
.
old_path
}
%img
{
'data-src'
:
old_blob_raw_path
,
alt:
diff_file
.
old_path
}
.swipe-wrap
.swipe-wrap
.frame.added
.frame.added
%img
{
src
:
blob_raw_path
,
alt:
diff_file
.
new_path
}
%img
{
'data-src'
:
blob_raw_path
,
alt:
diff_file
.
new_path
}
%span
.swipe-bar
%span
.swipe-bar
%span
.top-handle
%span
.top-handle
%span
.bottom-handle
%span
.bottom-handle
...
@@ -52,9 +52,9 @@
...
@@ -52,9 +52,9 @@
.onion-skin.view.hide
.onion-skin.view.hide
.onion-skin-frame
.onion-skin-frame
.frame.deleted
.frame.deleted
%img
{
src
:
old_blob_raw_path
,
alt:
diff_file
.
old_path
}
%img
{
'data-src'
:
old_blob_raw_path
,
alt:
diff_file
.
old_path
}
.frame.added
.frame.added
%img
{
src
:
blob_raw_path
,
alt:
diff_file
.
new_path
}
%img
{
'data-src'
:
blob_raw_path
,
alt:
diff_file
.
new_path
}
.controls
.controls
.transparent
.transparent
.drag-track
.drag-track
...
...
changelogs/unreleased/34361-lazy-load-images-on-the-frontend.yml
0 → 100644
View file @
52b8a0db
---
title
:
Lazy load images for better Frontend performance
merge_request
:
12503
author
:
doc/development/fe_guide/performance.md
View file @
52b8a0db
...
@@ -23,6 +23,18 @@ controlled by the server.
...
@@ -23,6 +23,18 @@ controlled by the server.
1.
The backend code will most likely be using etags. You do not and should not check for status
1.
The backend code will most likely be using etags. You do not and should not check for status
`304 Not Modified`
. The browser will transform it for you.
`304 Not Modified`
. The browser will transform it for you.
### Lazy Loading
To improve the time to first render we are using lazy loading for images. This works by setting
the actual image source on the
`data-src`
attribute. After the HTML is rendered and JavaScript is loaded,
the value of
`data-src`
will be moved to
`src`
automatically if the image is in the current viewport.
*
Prepare images in HTML for lazy loading by renaming the
`src`
attribute to
`data-src`
*
If you are using the Rails
`image_tag`
helper, all images will be lazy-loaded by default unless
`lazy: false`
is provided.
If you are asynchronously adding content which contains lazy images then you need to call the function
`gl.lazyLoader.searchLazyImages()`
which will search for lazy images and load them if needed.
## Reducing Asset Footprint
## Reducing Asset Footprint
### Page-specific JavaScript
### Page-specific JavaScript
...
...
features/steps/project/wiki.rb
View file @
52b8a0db
...
@@ -114,7 +114,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
...
@@ -114,7 +114,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
end
step
'Image should be shown on the page'
do
step
'Image should be shown on the page'
do
expect
(
page
).
to
have_xpath
(
"//img[@src=
\"
image.jpg
\"
]"
)
expect
(
page
).
to
have_xpath
(
"//img[@
data-
src=
\"
image.jpg
\"
]"
)
end
end
step
'I click on image link'
do
step
'I click on image link'
do
...
...
lib/banzai/filter/gollum_tags_filter.rb
View file @
52b8a0db
...
@@ -118,7 +118,7 @@ module Banzai
...
@@ -118,7 +118,7 @@ module Banzai
end
end
if
path
if
path
content_tag
(
:img
,
nil
,
src:
path
,
class:
'gfm'
)
content_tag
(
:img
,
nil
,
data:
{
src:
path
}
,
class:
'gfm'
)
end
end
end
end
...
...
lib/banzai/filter/image_lazy_load_filter.rb
0 → 100644
View file @
52b8a0db
module
Banzai
module
Filter
# HTML filter that moves the value of the src attribute to the data-src attribute so it can be lazy loaded
class
ImageLazyLoadFilter
<
HTML
::
Pipeline
::
Filter
def
call
doc
.
xpath
(
'descendant-or-self::img'
).
each
do
|
img
|
img
[
'class'
]
||=
''
<<
'lazy'
img
[
'data-src'
]
=
img
[
'src'
]
img
[
'src'
]
=
LazyImageTagHelper
.
placeholder_image
end
doc
end
end
end
end
lib/banzai/filter/image_link_filter.rb
View file @
52b8a0db
...
@@ -10,7 +10,7 @@ module Banzai
...
@@ -10,7 +10,7 @@ module Banzai
link
=
doc
.
document
.
create_element
(
link
=
doc
.
document
.
create_element
(
'a'
,
'a'
,
class:
'no-attachment-icon'
,
class:
'no-attachment-icon'
,
href:
img
[
'src'
],
href:
img
[
'
data-src'
]
||
img
[
'
src'
],
target:
'_blank'
,
target:
'_blank'
,
rel:
'noopener noreferrer'
rel:
'noopener noreferrer'
)
)
...
...
lib/banzai/filter/relative_link_filter.rb
View file @
52b8a0db
...
@@ -22,6 +22,7 @@ module Banzai
...
@@ -22,6 +22,7 @@ module Banzai
doc
.
css
(
'img, video'
).
each
do
|
el
|
doc
.
css
(
'img, video'
).
each
do
|
el
|
process_link_attr
el
.
attribute
(
'src'
)
process_link_attr
el
.
attribute
(
'src'
)
process_link_attr
el
.
attribute
(
'data-src'
)
end
end
doc
doc
...
...
lib/banzai/pipeline/gfm_pipeline.rb
View file @
52b8a0db
...
@@ -16,6 +16,7 @@ module Banzai
...
@@ -16,6 +16,7 @@ module Banzai
Filter
::
MathFilter
,
Filter
::
MathFilter
,
Filter
::
UploadLinkFilter
,
Filter
::
UploadLinkFilter
,
Filter
::
VideoLinkFilter
,
Filter
::
VideoLinkFilter
,
Filter
::
ImageLazyLoadFilter
,
Filter
::
ImageLinkFilter
,
Filter
::
ImageLinkFilter
,
Filter
::
EmojiFilter
,
Filter
::
EmojiFilter
,
Filter
::
TableOfContentsFilter
,
Filter
::
TableOfContentsFilter
,
...
...
spec/features/admin/admin_appearance_spec.rb
View file @
52b8a0db
...
@@ -63,11 +63,11 @@ feature 'Admin Appearance', feature: true do
...
@@ -63,11 +63,11 @@ feature 'Admin Appearance', feature: true do
end
end
def
logo_selector
def
logo_selector
'//img[
@
src^="/uploads/-/system/appearance/logo"]'
'//img[
data-
src^="/uploads/-/system/appearance/logo"]'
end
end
def
header_logo_selector
def
header_logo_selector
'//img[
@
src^="/uploads/-/system/appearance/header_logo"]'
'//img[
data-
src^="/uploads/-/system/appearance/header_logo"]'
end
end
def
logo_fixture
def
logo_fixture
...
...
spec/features/markdown_spec.rb
View file @
52b8a0db
...
@@ -100,7 +100,7 @@ describe 'GitLab Markdown', feature: true do
...
@@ -100,7 +100,7 @@ describe 'GitLab Markdown', feature: true do
end
end
it
'permits img elements'
do
it
'permits img elements'
do
expect
(
doc
).
to
have_selector
(
'img[src*="smile.png"]'
)
expect
(
doc
).
to
have_selector
(
'img[
data-
src*="smile.png"]'
)
end
end
it
'permits br elements'
do
it
'permits br elements'
do
...
...
spec/features/uploads/user_uploads_avatar_to_group_spec.rb
View file @
52b8a0db
...
@@ -18,7 +18,7 @@ feature 'User uploads avatar to group', feature: true do
...
@@ -18,7 +18,7 @@ feature 'User uploads avatar to group', feature: true do
visit
group_path
(
group
)
visit
group_path
(
group
)
expect
(
page
).
to
have_selector
(
%Q(img[src$="/uploads/-/system/group/avatar/
#{
group
.
id
}
/dk.png"])
)
expect
(
page
).
to
have_selector
(
%Q(img[
data-
src$="/uploads/-/system/group/avatar/
#{
group
.
id
}
/dk.png"])
)
# Cheating here to verify something that isn't user-facing, but is important
# Cheating here to verify something that isn't user-facing, but is important
expect
(
group
.
reload
.
avatar
.
file
).
to
exist
expect
(
group
.
reload
.
avatar
.
file
).
to
exist
...
...
spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
View file @
52b8a0db
...
@@ -16,7 +16,7 @@ feature 'User uploads avatar to profile', feature: true do
...
@@ -16,7 +16,7 @@ feature 'User uploads avatar to profile', feature: true do
visit
user_path
(
user
)
visit
user_path
(
user
)
expect
(
page
).
to
have_selector
(
%Q(img[src$="/uploads/-/system/user/avatar/
#{
user
.
id
}
/dk.png"])
)
expect
(
page
).
to
have_selector
(
%Q(img[
data-
src$="/uploads/-/system/user/avatar/
#{
user
.
id
}
/dk.png"])
)
# Cheating here to verify something that isn't user-facing, but is important
# Cheating here to verify something that isn't user-facing, but is important
expect
(
user
.
reload
.
avatar
.
file
).
to
exist
expect
(
user
.
reload
.
avatar
.
file
).
to
exist
...
...
spec/helpers/application_helper_spec.rb
View file @
52b8a0db
...
@@ -62,13 +62,13 @@ describe ApplicationHelper do
...
@@ -62,13 +62,13 @@ describe ApplicationHelper do
avatar_url
=
"/uploads/-/system/project/avatar/
#{
project
.
id
}
/banana_sample.gif"
avatar_url
=
"/uploads/-/system/project/avatar/
#{
project
.
id
}
/banana_sample.gif"
expect
(
helper
.
project_icon
(
project
.
full_path
).
to_s
)
expect
(
helper
.
project_icon
(
project
.
full_path
).
to_s
)
.
to
eq
"<img
src=
\"
#{
avatar_url
}
\"
alt=
\"
Banana sample
\"
/>"
.
to
eq
"<img
data-src=
\"
#{
avatar_url
}
\"
class=
\"
lazy
\"
src=
\"
#{
LazyImageTagHelper
.
placeholder_image
}
\"
/>"
allow
(
ActionController
::
Base
).
to
receive
(
:asset_host
).
and_return
(
gitlab_host
)
allow
(
ActionController
::
Base
).
to
receive
(
:asset_host
).
and_return
(
gitlab_host
)
avatar_url
=
"
#{
gitlab_host
}
/uploads/-/system/project/avatar/
#{
project
.
id
}
/banana_sample.gif"
avatar_url
=
"
#{
gitlab_host
}
/uploads/-/system/project/avatar/
#{
project
.
id
}
/banana_sample.gif"
expect
(
helper
.
project_icon
(
project
.
full_path
).
to_s
)
expect
(
helper
.
project_icon
(
project
.
full_path
).
to_s
)
.
to
eq
"<img
src=
\"
#{
avatar_url
}
\"
alt=
\"
Banana sample
\"
/>"
.
to
eq
"<img
data-src=
\"
#{
avatar_url
}
\"
class=
\"
lazy
\"
src=
\"
#{
LazyImageTagHelper
.
placeholder_image
}
\"
/>"
end
end
it
'gives uploaded icon when present'
do
it
'gives uploaded icon when present'
do
...
@@ -77,7 +77,8 @@ describe ApplicationHelper do
...
@@ -77,7 +77,8 @@ describe ApplicationHelper do
allow_any_instance_of
(
Project
).
to
receive
(
:avatar_in_git
).
and_return
(
true
)
allow_any_instance_of
(
Project
).
to
receive
(
:avatar_in_git
).
and_return
(
true
)
avatar_url
=
"
#{
gitlab_host
}#{
project_avatar_path
(
project
)
}
"
avatar_url
=
"
#{
gitlab_host
}#{
project_avatar_path
(
project
)
}
"
expect
(
helper
.
project_icon
(
project
.
full_path
).
to_s
).
to
match
(
image_tag
(
avatar_url
))
expect
(
helper
.
project_icon
(
project
.
full_path
).
to_s
)
.
to
eq
"<img data-src=
\"
#{
avatar_url
}
\"
class=
\"
lazy
\"
src=
\"
#{
LazyImageTagHelper
.
placeholder_image
}
\"
/>"
end
end
end
end
...
...
spec/helpers/avatars_helper_spec.rb
View file @
52b8a0db
...
@@ -27,11 +27,11 @@ describe AvatarsHelper do
...
@@ -27,11 +27,11 @@ describe AvatarsHelper do
it
'displays user avatar'
do
it
'displays user avatar'
do
is_expected
.
to
eq
image_tag
(
is_expected
.
to
eq
image_tag
(
avatar_icon
(
user
,
16
)
,
LazyImageTagHelper
.
placeholder_image
,
class:
'avatar has-tooltip s16 '
,
class:
'avatar has-tooltip s16
lazy
'
,
alt:
"
#{
user
.
name
}
's avatar"
,
alt:
"
#{
user
.
name
}
's avatar"
,
title:
user
.
name
,
title:
user
.
name
,
data:
{
container:
'body'
}
data:
{
container:
'body'
,
src:
avatar_icon
(
user
,
16
)
}
)
)
end
end
...
@@ -40,22 +40,8 @@ describe AvatarsHelper do
...
@@ -40,22 +40,8 @@ describe AvatarsHelper do
it
'uses provided css_class'
do
it
'uses provided css_class'
do
is_expected
.
to
eq
image_tag
(
is_expected
.
to
eq
image_tag
(
avatar_icon
(
user
,
16
),
LazyImageTagHelper
.
placeholder_image
,
class:
"avatar has-tooltip s16
#{
options
[
:css_class
]
}
"
,
class:
"avatar has-tooltip s16
#{
options
[
:css_class
]
}
lazy"
,
alt:
"
#{
user
.
name
}
's avatar"
,
title:
user
.
name
,
data:
{
container:
'body'
}
)
end
end
context
'with lazy parameter'
do
let
(
:options
)
{
{
user:
user
,
lazy:
true
}
}
it
'uses data-src instead of src'
do
is_expected
.
to
eq
image_tag
(
''
,
class:
'avatar has-tooltip s16 '
,
alt:
"
#{
user
.
name
}
's avatar"
,
alt:
"
#{
user
.
name
}
's avatar"
,
title:
user
.
name
,
title:
user
.
name
,
data:
{
container:
'body'
,
src:
avatar_icon
(
user
,
16
)
}
data:
{
container:
'body'
,
src:
avatar_icon
(
user
,
16
)
}
...
@@ -68,11 +54,11 @@ describe AvatarsHelper do
...
@@ -68,11 +54,11 @@ describe AvatarsHelper do
it
'uses provided size'
do
it
'uses provided size'
do
is_expected
.
to
eq
image_tag
(
is_expected
.
to
eq
image_tag
(
avatar_icon
(
user
,
options
[
:size
])
,
LazyImageTagHelper
.
placeholder_image
,
class:
"avatar has-tooltip s
#{
options
[
:size
]
}
"
,
class:
"avatar has-tooltip s
#{
options
[
:size
]
}
lazy
"
,
alt:
"
#{
user
.
name
}
's avatar"
,
alt:
"
#{
user
.
name
}
's avatar"
,
title:
user
.
name
,
title:
user
.
name
,
data:
{
container:
'body'
}
data:
{
container:
'body'
,
src:
avatar_icon
(
user
,
options
[
:size
])
}
)
)
end
end
end
end
...
@@ -82,11 +68,11 @@ describe AvatarsHelper do
...
@@ -82,11 +68,11 @@ describe AvatarsHelper do
it
'uses provided url'
do
it
'uses provided url'
do
is_expected
.
to
eq
image_tag
(
is_expected
.
to
eq
image_tag
(
options
[
:url
]
,
LazyImageTagHelper
.
placeholder_image
,
class:
'avatar has-tooltip s16 '
,
class:
'avatar has-tooltip s16
lazy
'
,
alt:
"
#{
user
.
name
}
's avatar"
,
alt:
"
#{
user
.
name
}
's avatar"
,
title:
user
.
name
,
title:
user
.
name
,
data:
{
container:
'body'
}
data:
{
container:
'body'
,
src:
options
[
:url
]
}
)
)
end
end
end
end
...
@@ -99,22 +85,22 @@ describe AvatarsHelper do
...
@@ -99,22 +85,22 @@ describe AvatarsHelper do
it
'prefers user parameter'
do
it
'prefers user parameter'
do
is_expected
.
to
eq
image_tag
(
is_expected
.
to
eq
image_tag
(
avatar_icon
(
user
,
16
)
,
LazyImageTagHelper
.
placeholder_image
,
class:
'avatar has-tooltip s16 '
,
class:
'avatar has-tooltip s16
lazy
'
,
alt:
"
#{
user
.
name
}
's avatar"
,
alt:
"
#{
user
.
name
}
's avatar"
,
title:
user
.
name
,
title:
user
.
name
,
data:
{
container:
'body'
}
data:
{
container:
'body'
,
src:
avatar_icon
(
user
,
16
)
}
)
)
end
end
end
end
it
'uses user_name and user_email parameter if user is not present'
do
it
'uses user_name and user_email parameter if user is not present'
do
is_expected
.
to
eq
image_tag
(
is_expected
.
to
eq
image_tag
(
avatar_icon
(
options
[
:user_email
],
16
)
,
LazyImageTagHelper
.
placeholder_image
,
class:
'avatar has-tooltip s16 '
,
class:
'avatar has-tooltip s16
lazy
'
,
alt:
"
#{
options
[
:user_name
]
}
's avatar"
,
alt:
"
#{
options
[
:user_name
]
}
's avatar"
,
title:
options
[
:user_name
],
title:
options
[
:user_name
],
data:
{
container:
'body'
}
data:
{
container:
'body'
,
src:
avatar_icon
(
options
[
:user_email
],
16
)
}
)
)
end
end
end
end
...
...
spec/javascripts/lazy_loader_spec.js
0 → 100644
View file @
52b8a0db
import
LazyLoader
from
'
~/lazy_loader
'
;
let
lazyLoader
=
null
;
describe
(
'
LazyLoader
'
,
function
()
{
preloadFixtures
(
'
issues/issue_with_comment.html.raw
'
);
beforeEach
(
function
()
{
loadFixtures
(
'
issues/issue_with_comment.html.raw
'
);
lazyLoader
=
new
LazyLoader
({
observerNode
:
'
body
'
,
});
// Doing everything that happens normally in onload
lazyLoader
.
loadCheck
();
});
describe
(
'
behavior
'
,
function
()
{
it
(
'
should copy value from data-src to src for img 1
'
,
function
(
done
)
{
const
img
=
document
.
querySelectorAll
(
'
img[data-src]
'
)[
0
];
const
originalDataSrc
=
img
.
getAttribute
(
'
data-src
'
);
img
.
scrollIntoView
();
setTimeout
(()
=>
{
expect
(
img
.
getAttribute
(
'
src
'
)).
toBe
(
originalDataSrc
);
expect
(
document
.
getElementsByClassName
(
'
js-lazy-loaded
'
).
length
).
toBeGreaterThan
(
0
);
done
();
},
100
);
});
it
(
'
should lazy load dynamically added data-src images
'
,
function
(
done
)
{
const
newImg
=
document
.
createElement
(
'
img
'
);
const
testPath
=
'
/img/testimg.png
'
;
newImg
.
className
=
'
lazy
'
;
newImg
.
setAttribute
(
'
data-src
'
,
testPath
);
document
.
body
.
appendChild
(
newImg
);
newImg
.
scrollIntoView
();
setTimeout
(()
=>
{
expect
(
newImg
.
getAttribute
(
'
src
'
)).
toBe
(
testPath
);
expect
(
document
.
getElementsByClassName
(
'
js-lazy-loaded
'
).
length
).
toBeGreaterThan
(
0
);
done
();
},
100
);
});
it
(
'
should not alter normal images
'
,
function
(
done
)
{
const
newImg
=
document
.
createElement
(
'
img
'
);
const
testPath
=
'
/img/testimg.png
'
;
newImg
.
setAttribute
(
'
src
'
,
testPath
);
document
.
body
.
appendChild
(
newImg
);
newImg
.
scrollIntoView
();
setTimeout
(()
=>
{
expect
(
newImg
).
not
.
toHaveClass
(
'
js-lazy-loaded
'
);
done
();
},
100
);
});
});
});
spec/lib/banzai/filter/gollum_tags_filter_spec.rb
View file @
52b8a0db
...
@@ -22,7 +22,7 @@ describe Banzai::Filter::GollumTagsFilter, lib: true do
...
@@ -22,7 +22,7 @@ describe Banzai::Filter::GollumTagsFilter, lib: true do
tag
=
'[[images/image.jpg]]'
tag
=
'[[images/image.jpg]]'
doc
=
filter
(
"See
#{
tag
}
"
,
project_wiki:
project_wiki
)
doc
=
filter
(
"See
#{
tag
}
"
,
project_wiki:
project_wiki
)
expect
(
doc
.
at_css
(
'img'
)[
'src'
]).
to
eq
"
#{
project_wiki
.
wiki_base_path
}
/images/image.jpg"
expect
(
doc
.
at_css
(
'img'
)[
'
data-
src'
]).
to
eq
"
#{
project_wiki
.
wiki_base_path
}
/images/image.jpg"
end
end
it
'does not creates img tag if image does not exist'
do
it
'does not creates img tag if image does not exist'
do
...
@@ -40,7 +40,7 @@ describe Banzai::Filter::GollumTagsFilter, lib: true do
...
@@ -40,7 +40,7 @@ describe Banzai::Filter::GollumTagsFilter, lib: true do
tag
=
'[[http://example.com/image.jpg]]'
tag
=
'[[http://example.com/image.jpg]]'
doc
=
filter
(
"See
#{
tag
}
"
,
project_wiki:
project_wiki
)
doc
=
filter
(
"See
#{
tag
}
"
,
project_wiki:
project_wiki
)
expect
(
doc
.
at_css
(
'img'
)[
'src'
]).
to
eq
"http://example.com/image.jpg"
expect
(
doc
.
at_css
(
'img'
)[
'
data-
src'
]).
to
eq
"http://example.com/image.jpg"
end
end
it
'does not creates img tag for invalid URL'
do
it
'does not creates img tag for invalid URL'
do
...
...
spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
0 → 100644
View file @
52b8a0db
require
'spec_helper'
describe
Banzai
::
Filter
::
ImageLazyLoadFilter
,
lib:
true
do
include
FilterSpecHelper
def
image
(
path
)
%(<img src="#{path}" />)
end
it
'transforms the image src to a data-src'
do
doc
=
filter
(
image
(
'/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'
))
expect
(
doc
.
at_css
(
'img'
)[
'data-src'
]).
to
eq
'/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'
end
it
'works with external images'
do
doc
=
filter
(
image
(
'https://i.imgur.com/DfssX9C.jpg'
))
expect
(
doc
.
at_css
(
'img'
)[
'data-src'
]).
to
eq
'https://i.imgur.com/DfssX9C.jpg'
end
end
spec/support/matchers/markdown_matchers.rb
View file @
52b8a0db
...
@@ -17,7 +17,7 @@ module MarkdownMatchers
...
@@ -17,7 +17,7 @@ module MarkdownMatchers
image
=
actual
.
at_css
(
'img[alt="Relative Image"]'
)
image
=
actual
.
at_css
(
'img[alt="Relative Image"]'
)
expect
(
link
[
'href'
]).
to
end_with
(
'master/doc/README.md'
)
expect
(
link
[
'href'
]).
to
end_with
(
'master/doc/README.md'
)
expect
(
image
[
'src'
]).
to
end_with
(
'master/app/assets/images/touch-icon-ipad.png'
)
expect
(
image
[
'
data-
src'
]).
to
end_with
(
'master/app/assets/images/touch-icon-ipad.png'
)
end
end
end
end
...
@@ -70,7 +70,7 @@ module MarkdownMatchers
...
@@ -70,7 +70,7 @@ module MarkdownMatchers
# GollumTagsFilter
# GollumTagsFilter
matcher
:parse_gollum_tags
do
matcher
:parse_gollum_tags
do
def
have_image
(
src
)
def
have_image
(
src
)
have_css
(
"img[src$='
#{
src
}
']"
)
have_css
(
"img[
data-
src$='
#{
src
}
']"
)
end
end
prefix
=
'/namespace1/gitlabhq/wikis'
prefix
=
'/namespace1/gitlabhq/wikis'
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment