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
80e658e7
Commit
80e658e7
authored
Dec 14, 2021
by
Jacques Erasmus
Committed by
Kushal Pandya
Dec 14, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add source viewer component
parent
9d65b3fa
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
282 additions
and
60 deletions
+282
-60
app/assets/javascripts/repository/components/blob_viewers/index.js
...s/javascripts/repository/components/blob_viewers/index.js
+5
-3
app/assets/javascripts/repository/components/blob_viewers/text_viewer.vue
...cripts/repository/components/blob_viewers/text_viewer.vue
+0
-25
app/assets/javascripts/vue_shared/components/line_numbers.vue
...assets/javascripts/vue_shared/components/line_numbers.vue
+57
-0
app/assets/javascripts/vue_shared/components/source_viewer.vue
...ssets/javascripts/vue_shared/components/source_viewer.vue
+88
-0
spec/frontend/repository/components/blob_content_viewer_spec.js
...rontend/repository/components/blob_content_viewer_spec.js
+2
-2
spec/frontend/repository/components/blob_viewers/text_viewer_spec.js
...nd/repository/components/blob_viewers/text_viewer_spec.js
+0
-30
spec/frontend/vue_shared/components/line_numbers_spec.js
spec/frontend/vue_shared/components/line_numbers_spec.js
+71
-0
spec/frontend/vue_shared/components/source_viewer_spec.js
spec/frontend/vue_shared/components/source_viewer_spec.js
+59
-0
No files found.
app/assets/javascripts/repository/components/blob_viewers/index.js
View file @
80e658e7
...
...
@@ -4,7 +4,10 @@ export const loadViewer = (type) => {
return
()
=>
import
(
/* webpackChunkName: 'blob_empty_viewer' */
'
./empty_viewer.vue
'
);
case
'
text
'
:
return
gon
.
features
.
highlightJs
?
()
=>
import
(
/* webpackChunkName: 'blob_text_viewer' */
'
./text_viewer.vue
'
)
?
()
=>
import
(
/* webpackChunkName: 'blob_text_viewer' */
'
~/vue_shared/components/source_viewer.vue
'
)
:
null
;
case
'
download
'
:
return
()
=>
import
(
/* webpackChunkName: 'blob_download_viewer' */
'
./download_viewer.vue
'
);
...
...
@@ -23,8 +26,7 @@ export const viewerProps = (type, blob) => {
return
{
text
:
{
content
:
blob
.
rawTextBlob
,
fileName
:
blob
.
name
,
readOnly
:
true
,
autoDetect
:
true
,
// We'll eventually disable autoDetect and pass the language explicitly to reduce the footprint (https://gitlab.com/gitlab-org/gitlab/-/issues/348145)
},
download
:
{
fileName
:
blob
.
name
,
...
...
app/assets/javascripts/repository/components/blob_viewers/text_viewer.vue
deleted
100644 → 0
View file @
9d65b3fa
<
script
>
export
default
{
components
:
{
SourceEditor
:
()
=>
import
(
/* webpackChunkName: 'SourceEditor' */
'
~/vue_shared/components/source_editor.vue
'
),
},
props
:
{
content
:
{
type
:
String
,
required
:
true
,
},
fileName
:
{
type
:
String
,
required
:
true
,
},
readOnly
:
{
type
:
Boolean
,
required
:
true
,
},
},
};
</
script
>
<
template
>
<source-editor
:value=
"content"
:file-name=
"fileName"
:editor-options=
"
{ readOnly }" />
</
template
>
app/assets/javascripts/vue_shared/components/line_numbers.vue
0 → 100644
View file @
80e658e7
<
script
>
import
{
GlIcon
,
GlLink
}
from
'
@gitlab/ui
'
;
export
default
{
components
:
{
GlIcon
,
GlLink
,
},
props
:
{
lines
:
{
type
:
Number
,
required
:
true
,
},
},
data
()
{
return
{
currentlyHighlightedLine
:
null
,
};
},
mounted
()
{
this
.
scrollToLine
();
},
methods
:
{
scrollToLine
(
hash
=
window
.
location
.
hash
)
{
const
lineToHighlight
=
hash
&&
this
.
$el
.
querySelector
(
hash
);
if
(
!
lineToHighlight
)
{
return
;
}
if
(
this
.
currentlyHighlightedLine
)
{
this
.
currentlyHighlightedLine
.
classList
.
remove
(
'
hll
'
);
}
lineToHighlight
.
classList
.
add
(
'
hll
'
);
this
.
currentlyHighlightedLine
=
lineToHighlight
;
lineToHighlight
.
scrollIntoView
({
behavior
:
'
smooth
'
,
block
:
'
center
'
});
},
},
};
</
script
>
<
template
>
<div
class=
"line-numbers"
>
<gl-link
v-for=
"line in lines"
:id=
"`L$
{line}`"
:key="line"
class="diff-line-num"
:href="`#L${line}`"
:data-line-number="line"
@click="scrollToLine(`#L${line}`)"
>
<gl-icon
:size=
"12"
name=
"link"
/>
{{
line
}}
</gl-link>
</div>
</
template
>
app/assets/javascripts/vue_shared/components/source_viewer.vue
0 → 100644
View file @
80e658e7
<
script
>
import
{
GlSafeHtmlDirective
}
from
'
@gitlab/ui
'
;
import
LineNumbers
from
'
~/vue_shared/components/line_numbers.vue
'
;
export
default
{
components
:
{
LineNumbers
,
},
directives
:
{
SafeHtml
:
GlSafeHtmlDirective
,
},
props
:
{
content
:
{
type
:
String
,
required
:
true
,
},
language
:
{
type
:
String
,
required
:
false
,
default
:
'
plaintext
'
,
},
autoDetect
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
data
()
{
return
{
languageDefinition
:
null
,
hljs
:
null
,
};
},
computed
:
{
lineNumbers
()
{
return
this
.
content
.
split
(
'
\n
'
).
length
;
},
highlightedContent
()
{
let
highlightedContent
;
if
(
this
.
hljs
)
{
if
(
this
.
autoDetect
)
{
highlightedContent
=
this
.
hljs
.
highlightAuto
(
this
.
content
).
value
;
}
else
if
(
this
.
languageDefinition
)
{
highlightedContent
=
this
.
hljs
.
highlight
(
this
.
content
,
{
language
:
this
.
language
}).
value
;
}
}
return
highlightedContent
;
},
},
async
mounted
()
{
this
.
hljs
=
await
this
.
loadHighlightJS
();
if
(
!
this
.
autoDetect
)
{
this
.
languageDefinition
=
await
this
.
loadLanguage
();
}
},
methods
:
{
loadHighlightJS
()
{
// With auto-detect enabled we load all common languages else we load only the core (smallest footprint)
return
this
.
autoDetect
?
import
(
'
highlight.js/lib/common
'
)
:
import
(
'
highlight.js/lib/core
'
);
},
async
loadLanguage
()
{
let
languageDefinition
;
try
{
languageDefinition
=
await
import
(
`highlight.js/lib/languages/
${
this
.
language
}
`
);
this
.
hljs
.
registerLanguage
(
this
.
language
,
languageDefinition
.
default
);
}
catch
(
message
)
{
this
.
$emit
(
'
error
'
,
message
);
}
return
languageDefinition
;
},
},
userColorScheme
:
window
.
gon
.
user_color_scheme
,
};
</
script
>
<
template
>
<div
class=
"file-content code"
:class=
"$options.userColorScheme"
>
<line-numbers
:lines=
"lineNumbers"
/>
<pre
class=
"code gl-pl-3!"
><code
v-safe-html=
"highlightedContent"
class=
"gl-white-space-pre-wrap!"
></code>
</pre>
</div>
</
template
>
spec/frontend/repository/components/blob_content_viewer_spec.js
View file @
80e658e7
...
...
@@ -15,7 +15,7 @@ import ForkSuggestion from '~/repository/components/fork_suggestion.vue';
import
{
loadViewer
,
viewerProps
}
from
'
~/repository/components/blob_viewers
'
;
import
DownloadViewer
from
'
~/repository/components/blob_viewers/download_viewer.vue
'
;
import
EmptyViewer
from
'
~/repository/components/blob_viewers/empty_viewer.vue
'
;
import
TextViewer
from
'
~/repository/components/blob_viewers/text
_viewer.vue
'
;
import
SourceViewer
from
'
~/vue_shared/components/source
_viewer.vue
'
;
import
blobInfoQuery
from
'
~/repository/queries/blob_info.query.graphql
'
;
import
{
redirectTo
}
from
'
~/lib/utils/url_utility
'
;
import
{
isLoggedIn
}
from
'
~/lib/utils/common_utils
'
;
...
...
@@ -215,7 +215,7 @@ describe('Blob content viewer component', () => {
viewer | loadViewerReturnValue | viewerPropsReturnValue
${
'
empty
'
}
|
${
EmptyViewer
}
|
${{}}
$
{
'
download
'
}
|
${
DownloadViewer
}
|
${{
filePath
:
'
/some/file/path
'
,
fileName
:
'
test.js
'
,
fileSize
:
100
}
}
${
'
text
'
}
|
${
TextViewer
}
|
${{
content
:
'
test
'
,
fileName
:
'
test.js
'
,
readOnly
:
true
}
}
${
'
text
'
}
|
${
SourceViewer
}
|
${{
content
:
'
test
'
,
autoDetect
:
true
}
}
`
(
'
renders viewer component for $viewer files
'
,
async
({
viewer
,
loadViewerReturnValue
,
viewerPropsReturnValue
})
=>
{
...
...
spec/frontend/repository/components/blob_viewers/text_viewer_spec.js
deleted
100644 → 0
View file @
9d65b3fa
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
TextViewer
from
'
~/repository/components/blob_viewers/text_viewer.vue
'
;
import
SourceEditor
from
'
~/vue_shared/components/source_editor.vue
'
;
describe
(
'
Text Viewer
'
,
()
=>
{
let
wrapper
;
const
propsData
=
{
content
:
'
Some content
'
,
fileName
:
'
file_name.js
'
,
readOnly
:
true
,
};
const
createComponent
=
()
=>
{
wrapper
=
shallowMount
(
TextViewer
,
{
propsData
});
};
const
findEditor
=
()
=>
wrapper
.
findComponent
(
SourceEditor
);
it
(
'
renders a Source Editor component
'
,
async
()
=>
{
createComponent
();
await
waitForPromises
();
expect
(
findEditor
().
exists
()).
toBe
(
true
);
expect
(
findEditor
().
props
(
'
value
'
)).
toBe
(
propsData
.
content
);
expect
(
findEditor
().
props
(
'
fileName
'
)).
toBe
(
propsData
.
fileName
);
expect
(
findEditor
().
props
(
'
editorOptions
'
)).
toEqual
({
readOnly
:
propsData
.
readOnly
});
});
});
spec/frontend/vue_shared/components/line_numbers_spec.js
0 → 100644
View file @
80e658e7
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
GlIcon
,
GlLink
}
from
'
@gitlab/ui
'
;
import
LineNumbers
from
'
~/vue_shared/components/line_numbers.vue
'
;
describe
(
'
Line Numbers component
'
,
()
=>
{
let
wrapper
;
const
lines
=
10
;
const
createComponent
=
()
=>
{
wrapper
=
shallowMount
(
LineNumbers
,
{
propsData
:
{
lines
}
});
};
const
findGlIcon
=
()
=>
wrapper
.
findComponent
(
GlIcon
);
const
findLineNumbers
=
()
=>
wrapper
.
findAllComponents
(
GlLink
);
const
findFirstLineNumber
=
()
=>
findLineNumbers
().
at
(
0
);
const
findSecondLineNumber
=
()
=>
findLineNumbers
().
at
(
1
);
beforeEach
(()
=>
createComponent
());
afterEach
(()
=>
wrapper
.
destroy
());
describe
(
'
rendering
'
,
()
=>
{
it
(
'
renders Line Numbers
'
,
()
=>
{
expect
(
findLineNumbers
().
length
).
toBe
(
lines
);
expect
(
findFirstLineNumber
().
attributes
()).
toMatchObject
({
id
:
'
L1
'
,
href
:
'
#L1
'
,
});
});
it
(
'
renders a link icon
'
,
()
=>
{
expect
(
findGlIcon
().
props
()).
toMatchObject
({
size
:
12
,
name
:
'
link
'
,
});
});
});
describe
(
'
clicking a line number
'
,
()
=>
{
let
firstLineNumber
;
let
firstLineNumberElement
;
beforeEach
(()
=>
{
firstLineNumber
=
findFirstLineNumber
();
firstLineNumberElement
=
firstLineNumber
.
element
;
jest
.
spyOn
(
firstLineNumberElement
,
'
scrollIntoView
'
);
jest
.
spyOn
(
firstLineNumberElement
.
classList
,
'
add
'
);
jest
.
spyOn
(
firstLineNumberElement
.
classList
,
'
remove
'
);
firstLineNumber
.
vm
.
$emit
(
'
click
'
);
});
it
(
'
adds the highlight (hll) class
'
,
()
=>
{
expect
(
firstLineNumberElement
.
classList
.
add
).
toHaveBeenCalledWith
(
'
hll
'
);
});
it
(
'
removes the highlight (hll) class from a previously highlighted line
'
,
()
=>
{
findSecondLineNumber
().
vm
.
$emit
(
'
click
'
);
expect
(
firstLineNumberElement
.
classList
.
remove
).
toHaveBeenCalledWith
(
'
hll
'
);
});
it
(
'
scrolls the line into view
'
,
()
=>
{
expect
(
firstLineNumberElement
.
scrollIntoView
).
toHaveBeenCalledWith
({
behavior
:
'
smooth
'
,
block
:
'
center
'
,
});
});
});
});
spec/frontend/vue_shared/components/source_viewer_spec.js
0 → 100644
View file @
80e658e7
import
hljs
from
'
highlight.js/lib/core
'
;
import
{
shallowMountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
SourceViewer
from
'
~/vue_shared/components/source_viewer.vue
'
;
import
LineNumbers
from
'
~/vue_shared/components/line_numbers.vue
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
jest
.
mock
(
'
highlight.js/lib/core
'
);
describe
(
'
Source Viewer component
'
,
()
=>
{
let
wrapper
;
const
content
=
`// Some source code`
;
const
highlightedContent
=
`<span data-testid='test-highlighted'>
${
content
}
</span>`
;
const
language
=
'
javascript
'
;
hljs
.
highlight
.
mockImplementation
(()
=>
({
value
:
highlightedContent
}));
hljs
.
highlightAuto
.
mockImplementation
(()
=>
({
value
:
highlightedContent
}));
const
createComponent
=
async
(
props
=
{})
=>
{
wrapper
=
shallowMountExtended
(
SourceViewer
,
{
propsData
:
{
content
,
language
,
...
props
}
});
await
waitForPromises
();
};
const
findLineNumbers
=
()
=>
wrapper
.
findComponent
(
LineNumbers
);
const
findHighlightedContent
=
()
=>
wrapper
.
findByTestId
(
'
test-highlighted
'
);
beforeEach
(()
=>
createComponent
());
afterEach
(()
=>
wrapper
.
destroy
());
describe
(
'
highlight.js
'
,
()
=>
{
it
(
'
registers the language definition
'
,
async
()
=>
{
const
languageDefinition
=
await
import
(
`highlight.js/lib/languages/
${
language
}
`
);
expect
(
hljs
.
registerLanguage
).
toHaveBeenCalledWith
(
language
,
languageDefinition
.
default
);
});
it
(
'
highlights the content
'
,
()
=>
{
expect
(
hljs
.
highlight
).
toHaveBeenCalledWith
(
content
,
{
language
});
});
describe
(
'
auto-detect enabled
'
,
()
=>
{
beforeEach
(()
=>
createComponent
({
autoDetect
:
true
}));
it
(
'
highlights the content with auto-detection
'
,
()
=>
{
expect
(
hljs
.
highlightAuto
).
toHaveBeenCalledWith
(
content
);
});
});
});
describe
(
'
rendering
'
,
()
=>
{
it
(
'
renders Line Numbers
'
,
()
=>
{
expect
(
findLineNumbers
().
props
(
'
lines
'
)).
toBe
(
1
);
});
it
(
'
renders the highlighted content
'
,
()
=>
{
expect
(
findHighlightedContent
().
exists
()).
toBe
(
true
);
});
});
});
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