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
45020549
Commit
45020549
authored
Nov 13, 2020
by
Jacques Erasmus
Committed by
Andrew Fontaine
Nov 13, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add the ability to preview uploaded images
Added the ability to preview uploaded images
parent
4a6bbc73
Changes
14
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
149 additions
and
56 deletions
+149
-56
app/assets/javascripts/static_site_editor/components/edit_area.vue
...s/javascripts/static_site_editor/components/edit_area.vue
+7
-1
app/assets/javascripts/static_site_editor/image_repository.js
...assets/javascripts/static_site_editor/image_repository.js
+3
-1
app/assets/javascripts/static_site_editor/services/renderers/render_image.js
...pts/static_site_editor/services/renderers/render_image.js
+13
-5
app/assets/javascripts/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue
.../rich_content_editor/modals/add_image/add_image_modal.vue
+4
-17
app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue
...ed/components/rich_content_editor/rich_content_editor.vue
+1
-2
app/assets/javascripts/vue_shared/components/rich_content_editor/services/editor_service.js
...components/rich_content_editor/services/editor_service.js
+22
-1
app/controllers/projects/static_site_editor_controller.rb
app/controllers/projects/static_site_editor_controller.rb
+0
-3
changelogs/unreleased/218529-display-uploaded-images.yml
changelogs/unreleased/218529-display-uploaded-images.yml
+5
-0
doc/user/project/static_site_editor/index.md
doc/user/project/static_site_editor/index.md
+27
-4
locale/gitlab.pot
locale/gitlab.pot
+6
-3
spec/frontend/static_site_editor/services/renderers/render_image_spec.js
...tatic_site_editor/services/renderers/render_image_spec.js
+42
-10
spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js
...red/components/rich_content_editor/editor_service_spec.js
+17
-4
spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal_spec.js
...h_content_editor/modals/add_image/add_image_modal_spec.js
+1
-4
spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
...omponents/rich_content_editor/rich_content_editor_spec.js
+1
-1
No files found.
app/assets/javascripts/static_site_editor/components/edit_area.vue
View file @
45020549
...
...
@@ -83,7 +83,13 @@ export default {
return
this
.
editorMode
===
EDITOR_TYPES
.
wysiwyg
;
},
customRenderers
()
{
const
imageRenderer
=
renderImage
.
build
(
this
.
mounts
,
this
.
project
,
this
.
branch
,
this
.
baseUrl
);
const
imageRenderer
=
renderImage
.
build
(
this
.
mounts
,
this
.
project
,
this
.
branch
,
this
.
baseUrl
,
this
.
$options
.
imageRepository
,
);
return
{
image
:
[
imageRenderer
],
};
...
...
app/assets/javascripts/static_site_editor/image_repository.js
View file @
45020549
...
...
@@ -12,9 +12,11 @@ const imageRepository = () => {
.
catch
(()
=>
flash
(
__
(
'
Something went wrong while inserting your image. Please try again.
'
)));
};
const
get
=
path
=>
images
.
get
(
path
);
const
getAll
=
()
=>
images
;
return
{
add
,
getAll
};
return
{
add
,
get
,
get
All
};
};
export
default
imageRepository
;
app/assets/javascripts/static_site_editor/services/renderers/render_image.js
View file @
45020549
...
...
@@ -4,6 +4,8 @@ const canRender = ({ type }) => type === 'image';
let
metadata
;
const
getCachedContent
=
basePath
=>
metadata
.
imageRepository
.
get
(
basePath
);
const
isRelativeToCurrentDirectory
=
basePath
=>
!
basePath
.
startsWith
(
'
/
'
);
const
extractSourceDirectory
=
url
=>
{
...
...
@@ -46,7 +48,11 @@ const generateSourceDirectory = basePath => {
return
sourceDir
||
defaultSourceDir
;
};
const
resolveFullPath
=
originalSrc
=>
{
const
resolveFullPath
=
(
originalSrc
,
cachedContent
)
=>
{
if
(
cachedContent
)
{
return
`data:image;base64,
${
cachedContent
}
`
;
}
if
(
isAbsolute
(
originalSrc
))
{
return
originalSrc
;
}
...
...
@@ -61,20 +67,22 @@ const resolveFullPath = originalSrc => {
const
render
=
({
destination
:
originalSrc
,
firstChild
},
{
skipChildren
})
=>
{
skipChildren
();
const
cachedContent
=
getCachedContent
(
originalSrc
);
return
{
type
:
'
openTag
'
,
tagName
:
'
img
'
,
selfClose
:
true
,
attributes
:
{
'
data-original-src
'
:
!
isAbsolute
(
originalSrc
)
?
originalSrc
:
''
,
src
:
resolveFullPath
(
originalSrc
),
'
data-original-src
'
:
!
isAbsolute
(
originalSrc
)
||
cachedContent
?
originalSrc
:
''
,
src
:
resolveFullPath
(
originalSrc
,
cachedContent
),
alt
:
firstChild
.
literal
,
},
};
};
const
build
=
(
mounts
=
[],
project
,
branch
,
baseUrl
)
=>
{
metadata
=
{
mounts
,
project
,
branch
,
baseUrl
};
const
build
=
(
mounts
=
[],
project
,
branch
,
baseUrl
,
imageRepository
)
=>
{
metadata
=
{
mounts
,
project
,
branch
,
baseUrl
,
imageRepository
};
return
{
canRender
,
render
};
};
...
...
app/assets/javascripts/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue
View file @
45020549
...
...
@@ -2,7 +2,6 @@
import
{
GlModal
,
GlFormGroup
,
GlFormInput
,
GlTabs
,
GlTab
}
from
'
@gitlab/ui
'
;
import
{
isSafeURL
,
joinPaths
}
from
'
~/lib/utils/url_utility
'
;
import
{
__
}
from
'
~/locale
'
;
import
glFeatureFlagMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
import
{
IMAGE_TABS
}
from
'
../../constants
'
;
import
UploadImageTab
from
'
./upload_image_tab.vue
'
;
...
...
@@ -15,7 +14,6 @@ export default {
GlTabs
,
GlTab
,
},
mixins
:
[
glFeatureFlagMixin
()],
props
:
{
imageRoot
:
{
type
:
String
,
...
...
@@ -34,10 +32,10 @@ export default {
},
modalTitle
:
__
(
'
Image details
'
),
okTitle
:
__
(
'
Insert image
'
),
urlTabTitle
:
__
(
'
By URL
'
),
urlTabTitle
:
__
(
'
Link to an image
'
),
urlLabel
:
__
(
'
Image URL
'
),
descriptionLabel
:
__
(
'
Description
'
),
uploadTabTitle
:
__
(
'
Upload
fil
e
'
),
uploadTabTitle
:
__
(
'
Upload
an imag
e
'
),
computed
:
{
altText
()
{
return
this
.
description
;
...
...
@@ -54,7 +52,7 @@ export default {
this
.
$refs
.
modal
.
show
();
},
onOk
(
event
)
{
if
(
this
.
glFeatures
.
sseImageUploads
&&
this
.
tabIndex
===
IMAGE_TABS
.
UPLOAD_TAB
)
{
if
(
this
.
tabIndex
===
IMAGE_TABS
.
UPLOAD_TAB
)
{
this
.
submitFile
(
event
);
return
;
}
...
...
@@ -108,7 +106,7 @@ export default {
:ok-title=
"$options.okTitle"
@
ok=
"onOk"
>
<gl-tabs
v-
if=
"glFeatures.sseImageUploads"
v-
model=
"tabIndex"
>
<gl-tabs
v-model=
"tabIndex"
>
<!-- Upload file Tab -->
<gl-tab
:title=
"$options.uploadTabTitle"
>
<upload-image-tab
ref=
"uploadImageTab"
@
input=
"setFile"
/>
...
...
@@ -128,17 +126,6 @@ export default {
</gl-tab>
</gl-tabs>
<gl-form-group
v-else
class=
"gl-mt-5 gl-mb-3"
:label=
"$options.urlLabel"
label-for=
"url-input"
:state=
"!Boolean(urlError)"
:invalid-feedback=
"urlError"
>
<gl-form-input
id=
"url-input"
ref=
"urlInput"
v-model=
"imageUrl"
/>
</gl-form-group>
<!-- Description Input -->
<gl-form-group
:label=
"$options.descriptionLabel"
label-for=
"description-input"
>
<gl-form-input
id=
"description-input"
ref=
"descriptionInput"
v-model=
"description"
/>
...
...
app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue
View file @
45020549
...
...
@@ -114,10 +114,9 @@ export default {
if
(
file
)
{
this
.
$emit
(
'
uploadImage
'
,
{
file
,
imageUrl
});
// TODO - ensure that the actual repo URL for the image is used in Markdown mode
}
addImage
(
this
.
editorInstance
,
image
);
addImage
(
this
.
editorInstance
,
image
,
file
);
},
onOpenInsertVideoModal
()
{
this
.
$refs
.
insertVideoModal
.
show
();
...
...
app/assets/javascripts/vue_shared/components/rich_content_editor/services/editor_service.js
View file @
45020549
...
...
@@ -34,6 +34,20 @@ const buildVideoIframe = src => {
return
wrapper
;
};
const
buildImg
=
(
alt
,
originalSrc
,
file
)
=>
{
const
img
=
document
.
createElement
(
'
img
'
);
const
src
=
file
?
URL
.
createObjectURL
(
file
)
:
originalSrc
;
const
attributes
=
{
alt
,
src
};
if
(
file
)
{
img
.
dataset
.
originalSrc
=
originalSrc
;
}
Object
.
assign
(
img
,
attributes
);
return
img
;
};
export
const
generateToolbarItem
=
config
=>
{
const
{
icon
,
classes
,
event
,
command
,
tooltip
,
isDivider
}
=
config
;
...
...
@@ -59,7 +73,14 @@ export const addCustomEventListener = (editorApi, event, handler) => {
export
const
removeCustomEventListener
=
(
editorApi
,
event
,
handler
)
=>
editorApi
.
eventManager
.
removeEventHandler
(
event
,
handler
);
export
const
addImage
=
({
editor
},
image
)
=>
editor
.
exec
(
'
AddImage
'
,
image
);
export
const
addImage
=
({
editor
},
{
altText
,
imageUrl
},
file
)
=>
{
if
(
editor
.
isWysiwygMode
())
{
const
img
=
buildImg
(
altText
,
imageUrl
,
file
);
editor
.
getSquire
().
insertElement
(
img
);
}
else
{
editor
.
insertText
(
`![
${
altText
}
](
${
imageUrl
}
)`
);
}
};
export
const
insertVideo
=
({
editor
},
url
)
=>
{
const
videoIframe
=
buildVideoIframe
(
url
);
...
...
app/controllers/projects/static_site_editor_controller.rb
View file @
45020549
...
...
@@ -16,9 +16,6 @@ class Projects::StaticSiteEditorController < Projects::ApplicationController
prepend_before_action
:authenticate_user!
,
only:
[
:show
]
before_action
:assign_ref_and_path
,
only:
[
:show
]
before_action
:authorize_edit_tree!
,
only:
[
:show
]
before_action
do
push_frontend_feature_flag
(
:sse_image_uploads
)
end
feature_category
:static_site_editor
...
...
changelogs/unreleased/218529-display-uploaded-images.yml
0 → 100644
View file @
45020549
---
title
:
Enable the ability to upload images via the SSE
merge_request
:
36299
author
:
type
:
added
doc/user/project/static_site_editor/index.md
View file @
45020549
...
...
@@ -107,13 +107,36 @@ The Static Site Editors supports Markdown files (`.md`, `.md.erb`) for editing t
### Images
> - Support for adding images through the WYSIWYG editor [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216640) in GitLab 13.1.
> - Support for uploading images via the WYSIWYG editor [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218529) in GitLab 13.6.
You can add image files on the WYSIWYG mode by clicking the image icon (
**{doc-image}**
).
From there, link to a URL, add optional
[
ALT text
](
https://moz.com/learn/seo/alt-text
)
,
and you're done. The link can reference images already hosted in your project, an asset hosted
#### Upload an image
You can upload image files via the WYSIWYG editor directly to the repository to default upload directory
`source/images`
. To do so:
1.
Click the image icon (
**{doc-image}**
).
1.
Choose the
**Upload file**
tab.
1.
Click
**Choose file**
to select a file from your computer.
1.
Optional: add a description to the image for SEO and accessibility (
[
ALT text
](
https://moz.com/learn/seo/alt-text
)
).
1.
Click
**Insert image**
.
The selected file can be any supported image file (
`.png`
,
`.jpg`
,
`.jpeg`
,
`.gif`
). The editor renders
thumbnail previews so you can verify the correct image is included and there aren't any references to
missing images.
#### Link to an image
You can also link to an image if you'd like:
1.
Click the image icon (
**{doc-image}**
).
1.
Choose the
**Link to an image**
tab.
1.
Add the link to the image into the
**Image URL**
field (use the full path; relative paths are not supported yet).
1.
Optional: add a description to the image for SEO and accessibility (
[
ALT text
](
https://moz.com/learn/seo/alt-text
)
).
1.
Click
**Insert image**
.
The link can reference images already hosted in your project, an asset hosted
externally on a content delivery network, or any other external URL. The editor renders thumbnail previews
so you can verify the correct image is included and there aren't any references to missing images.
default directory (
`source/images/`
).
### Videos
...
...
locale/gitlab.pot
View file @
45020549
...
...
@@ -4693,9 +4693,6 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
msgid "By URL"
msgstr ""
msgid "By clicking Register, I agree that I have read and accepted the %{company_name} %{linkStart}Terms of Use and Privacy Policy%{linkEnd}"
msgstr ""
...
...
@@ -16107,6 +16104,9 @@ msgstr ""
msgid "Link title is required"
msgstr ""
msgid "Link to an image"
msgstr ""
msgid "Link to go to GitLab pipeline documentation"
msgstr ""
...
...
@@ -29138,6 +29138,9 @@ msgstr ""
msgid "Upload a private key for your certificate"
msgstr ""
msgid "Upload an image"
msgstr ""
msgid "Upload file"
msgstr ""
...
...
spec/frontend/static_site_editor/services/renderers/render_image_spec.js
View file @
45020549
...
...
@@ -3,9 +3,11 @@ import { mounts, project, branch, baseUrl } from '../../mock_data';
describe
(
'
rich_content_editor/renderers/render_image
'
,
()
=>
{
let
renderer
;
let
imageRepository
;
beforeEach
(()
=>
{
renderer
=
imageRenderer
.
build
(
mounts
,
project
,
branch
,
baseUrl
);
renderer
=
imageRenderer
.
build
(
mounts
,
project
,
branch
,
baseUrl
,
imageRepository
);
imageRepository
=
{
get
:
()
=>
null
};
});
describe
(
'
build
'
,
()
=>
{
...
...
@@ -27,6 +29,21 @@ describe('rich_content_editor/renderers/render_image', () => {
});
describe
(
'
render
'
,
()
=>
{
let
skipChildren
;
let
context
;
let
node
;
beforeEach
(()
=>
{
skipChildren
=
jest
.
fn
();
context
=
{
skipChildren
};
node
=
{
firstChild
:
{
type
:
'
img
'
,
literal
:
'
Some Image
'
,
},
};
});
it
.
each
`
destination | isAbsolute | src
${
'
http://test.host/absolute/path/to/image.png
'
}
|
${
true
}
|
${
'
http://test.host/absolute/path/to/image.png
'
}
...
...
@@ -36,15 +53,8 @@ describe('rich_content_editor/renderers/render_image', () => {
${
'
./relative/to/current/image.png
'
}
|
${
false
}
|
${
'
http://test.host/user1/project1/-/raw/master/./relative/to/current/image.png
'
}
${
'
../relative/to/current/image.png
'
}
|
${
false
}
|
${
'
http://test.host/user1/project1/-/raw/master/../relative/to/current/image.png
'
}
`
(
'
returns an image with the correct attributes
'
,
({
destination
,
isAbsolute
,
src
})
=>
{
const
skipChildren
=
jest
.
fn
();
const
context
=
{
skipChildren
};
const
node
=
{
destination
,
firstChild
:
{
type
:
'
img
'
,
literal
:
'
Some Image
'
,
},
};
node
.
destination
=
destination
;
const
result
=
renderer
.
render
(
node
,
context
);
expect
(
result
).
toEqual
({
...
...
@@ -60,5 +70,27 @@ describe('rich_content_editor/renderers/render_image', () => {
expect
(
skipChildren
).
toHaveBeenCalled
();
});
it
(
'
renders an image if a cached image is found in the repository, use the base64 content as the source
'
,
()
=>
{
const
imageContent
=
'
some-content
'
;
const
originalSrc
=
'
path/to/image.png
'
;
imageRepository
.
get
=
()
=>
imageContent
;
renderer
=
imageRenderer
.
build
(
mounts
,
project
,
branch
,
baseUrl
,
imageRepository
);
node
.
destination
=
originalSrc
;
const
result
=
renderer
.
render
(
node
,
context
);
expect
(
result
).
toEqual
({
type
:
'
openTag
'
,
tagName
:
'
img
'
,
selfClose
:
true
,
attributes
:
{
'
data-original-src
'
:
originalSrc
,
src
:
`data:image;base64,
${
imageContent
}
`
,
alt
:
'
Some Image
'
,
},
});
});
});
});
spec/frontend/vue_shared/components/rich_content_editor/editor_service_spec.js
View file @
45020549
...
...
@@ -91,12 +91,25 @@ describe('Editor Service', () => {
});
describe
(
'
addImage
'
,
()
=>
{
it
(
'
calls the exec method on the instance
'
,
()
=>
{
const
mockImage
=
{
imageUrl
:
'
some/url.png
'
,
description
:
'
some description
'
};
const
file
=
new
File
([],
'
some-file.jpg
'
);
const
mockImage
=
{
imageUrl
:
'
some/url.png
'
,
altText
:
'
some alt text
'
};
addImage
(
mockInstance
,
mockImage
);
it
(
'
calls the insertElement method on the squire instance when in WYSIWYG mode
'
,
()
=>
{
jest
.
spyOn
(
URL
,
'
createObjectURL
'
);
mockInstance
.
editor
.
isWysiwygMode
.
mockReturnValue
(
true
);
mockInstance
.
editor
.
getSquire
.
mockReturnValue
({
insertElement
:
jest
.
fn
()
});
addImage
(
mockInstance
,
mockImage
,
file
);
expect
(
mockInstance
.
editor
.
getSquire
().
insertElement
).
toHaveBeenCalled
();
expect
(
global
.
URL
.
createObjectURL
).
toHaveBeenLastCalledWith
(
file
);
});
it
(
'
calls the insertText method on the instance when in Markdown mode
'
,
()
=>
{
mockInstance
.
editor
.
isWysiwygMode
.
mockReturnValue
(
false
);
addImage
(
mockInstance
,
mockImage
,
file
);
expect
(
mockInstance
.
editor
.
exec
).
toHaveBeenCalledWith
(
'
AddImage
'
,
mockImage
);
expect
(
mockInstance
.
editor
.
insertText
).
toHaveBeenCalledWith
(
'
![some alt text](some/url.png)
'
);
});
});
...
...
spec/frontend/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal_spec.js
View file @
45020549
...
...
@@ -15,10 +15,7 @@ describe('Add Image Modal', () => {
const
findDescriptionInput
=
()
=>
wrapper
.
find
({
ref
:
'
descriptionInput
'
});
beforeEach
(()
=>
{
wrapper
=
shallowMount
(
AddImageModal
,
{
provide
:
{
glFeatures
:
{
sseImageUploads
:
true
}
},
propsData
,
});
wrapper
=
shallowMount
(
AddImageModal
,
{
propsData
});
});
describe
(
'
when content is loaded
'
,
()
=>
{
...
...
spec/frontend/vue_shared/components/rich_content_editor/rich_content_editor_spec.js
View file @
45020549
...
...
@@ -180,7 +180,7 @@ describe('Rich Content Editor', () => {
wrapper
.
vm
.
$refs
.
editor
=
mockInstance
;
findAddImageModal
().
vm
.
$emit
(
'
addImage
'
,
mockImage
);
expect
(
addImage
).
toHaveBeenCalledWith
(
mockInstance
,
mockImage
);
expect
(
addImage
).
toHaveBeenCalledWith
(
mockInstance
,
mockImage
,
undefined
);
});
});
...
...
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