summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/blob/components
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/blob/components')
-rw-r--r--app/assets/javascripts/blob/components/blob_content.vue17
-rw-r--r--app/assets/javascripts/blob/components/blob_content_error.vue71
-rw-r--r--app/assets/javascripts/blob/components/blob_edit_header.vue4
-rw-r--r--app/assets/javascripts/blob/components/blob_header.vue2
-rw-r--r--app/assets/javascripts/blob/components/blob_header_filepath.vue14
-rw-r--r--app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue2
-rw-r--r--app/assets/javascripts/blob/components/constants.js56
7 files changed, 154 insertions, 12 deletions
diff --git a/app/assets/javascripts/blob/components/blob_content.vue b/app/assets/javascripts/blob/components/blob_content.vue
index 7d5d48cfc31..4f433bd8dfd 100644
--- a/app/assets/javascripts/blob/components/blob_content.vue
+++ b/app/assets/javascripts/blob/components/blob_content.vue
@@ -3,12 +3,19 @@ import { GlLoadingIcon } from '@gitlab/ui';
import { RichViewer, SimpleViewer } from '~/vue_shared/components/blob_viewers';
import BlobContentError from './blob_content_error.vue';
+import { BLOB_RENDER_EVENT_LOAD, BLOB_RENDER_EVENT_SHOW_SOURCE } from './constants';
+
export default {
components: {
GlLoadingIcon,
BlobContentError,
},
props: {
+ blob: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
content: {
type: String,
default: '',
@@ -37,6 +44,8 @@ export default {
return this.activeViewer.renderError;
},
},
+ BLOB_RENDER_EVENT_LOAD,
+ BLOB_RENDER_EVENT_SHOW_SOURCE,
};
</script>
<template>
@@ -44,7 +53,13 @@ export default {
<gl-loading-icon v-if="loading" size="md" color="dark" class="my-4 mx-auto" />
<template v-else>
- <blob-content-error v-if="viewerError" :viewer-error="viewerError" />
+ <blob-content-error
+ v-if="viewerError"
+ :viewer-error="viewerError"
+ :blob="blob"
+ @[$options.BLOB_RENDER_EVENT_LOAD]="$emit($options.BLOB_RENDER_EVENT_LOAD)"
+ @[$options.BLOB_RENDER_EVENT_SHOW_SOURCE]="$emit($options.BLOB_RENDER_EVENT_SHOW_SOURCE)"
+ />
<component
:is="viewer"
v-else
diff --git a/app/assets/javascripts/blob/components/blob_content_error.vue b/app/assets/javascripts/blob/components/blob_content_error.vue
index 0f1af0a962d..44dc4a6c727 100644
--- a/app/assets/javascripts/blob/components/blob_content_error.vue
+++ b/app/assets/javascripts/blob/components/blob_content_error.vue
@@ -1,15 +1,84 @@
<script>
+import { __ } from '~/locale';
+import { GlSprintf, GlLink } from '@gitlab/ui';
+import { BLOB_RENDER_ERRORS } from './constants';
+
export default {
+ components: {
+ GlSprintf,
+ GlLink,
+ },
props: {
viewerError: {
type: String,
required: true,
},
+ blob: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ },
+ computed: {
+ notStoredExternally() {
+ return this.viewerError !== BLOB_RENDER_ERRORS.REASONS.EXTERNAL.id;
+ },
+ renderErrorReason() {
+ const defaultReasonPath = Object.keys(BLOB_RENDER_ERRORS.REASONS).find(
+ reason => BLOB_RENDER_ERRORS.REASONS[reason].id === this.viewerError,
+ );
+ const defaultReason = BLOB_RENDER_ERRORS.REASONS[defaultReasonPath].text;
+ return this.notStoredExternally
+ ? defaultReason
+ : defaultReason[this.blob.externalStorage || 'default'];
+ },
+ renderErrorOptions() {
+ const load = {
+ ...BLOB_RENDER_ERRORS.OPTIONS.LOAD,
+ condition: this.shouldShowLoadBtn,
+ };
+ const showSource = {
+ ...BLOB_RENDER_ERRORS.OPTIONS.SHOW_SOURCE,
+ condition: this.shouldShowSourceBtn,
+ };
+ const download = {
+ ...BLOB_RENDER_ERRORS.OPTIONS.DOWNLOAD,
+ href: this.blob.rawPath,
+ };
+ return [load, showSource, download];
+ },
+ shouldShowLoadBtn() {
+ return this.viewerError === BLOB_RENDER_ERRORS.REASONS.COLLAPSED.id;
+ },
+ shouldShowSourceBtn() {
+ return this.blob.richViewer && this.blob.renderedAsText && this.notStoredExternally;
+ },
},
+ errorMessage: __(
+ 'This content could not be displayed because %{reason}. You can %{options} instead.',
+ ),
};
</script>
<template>
<div class="file-content code">
- <div class="text-center py-4" v-html="viewerError"></div>
+ <div class="text-center py-4">
+ <gl-sprintf :message="$options.errorMessage">
+ <template #reason>{{ renderErrorReason }}</template>
+ <template #options>
+ <template v-for="option in renderErrorOptions">
+ <span v-if="option.condition" :key="option.text">
+ <gl-link
+ :href="option.href"
+ :target="option.target"
+ :data-test-id="`option-${option.id}`"
+ @click="option.event && $emit(option.event)"
+ >{{ option.text }}</gl-link
+ >
+ {{ option.conjunction }}
+ </span>
+ </template>
+ </template>
+ </gl-sprintf>
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/blob/components/blob_edit_header.vue b/app/assets/javascripts/blob/components/blob_edit_header.vue
index e9b5ceda479..e1e1d76f721 100644
--- a/app/assets/javascripts/blob/components/blob_edit_header.vue
+++ b/app/assets/javascripts/blob/components/blob_edit_header.vue
@@ -5,6 +5,7 @@ export default {
components: {
GlFormInput,
},
+ inheritAttrs: false,
props: {
value: {
type: String,
@@ -27,8 +28,9 @@ export default {
s__('Snippets|Give your file a name to add code highlighting, e.g. example.rb for Ruby')
"
name="snippet_file_name"
- class="form-control js-snippet-file-name qa-snippet-file-name"
+ class="form-control js-snippet-file-name"
type="text"
+ v-bind="$attrs"
@change="$emit('input', name)"
/>
</div>
diff --git a/app/assets/javascripts/blob/components/blob_header.vue b/app/assets/javascripts/blob/components/blob_header.vue
index b7d9600ec40..e5e01caa9a5 100644
--- a/app/assets/javascripts/blob/components/blob_header.vue
+++ b/app/assets/javascripts/blob/components/blob_header.vue
@@ -66,7 +66,7 @@ export default {
</template>
</blob-filepath>
- <div class="file-actions d-none d-sm-block">
+ <div class="file-actions d-none d-sm-flex">
<viewer-switcher v-if="showViewerSwitcher" v-model="viewer" />
<slot name="actions"></slot>
diff --git a/app/assets/javascripts/blob/components/blob_header_filepath.vue b/app/assets/javascripts/blob/components/blob_header_filepath.vue
index 6c6a22e2b36..e9be7fbcf9b 100644
--- a/app/assets/javascripts/blob/components/blob_header_filepath.vue
+++ b/app/assets/javascripts/blob/components/blob_header_filepath.vue
@@ -28,12 +28,14 @@ export default {
<div class="file-header-content d-flex align-items-center lh-100">
<slot name="filepathPrepend"></slot>
- <file-icon :file-name="blob.path" :size="18" aria-hidden="true" css-classes="mr-2" />
- <strong
- v-if="blob.name"
- class="file-title-name qa-file-title-name mr-1 js-blob-header-filepath"
- >{{ blob.name }}</strong
- >
+ <template v-if="blob.path">
+ <file-icon :file-name="blob.path" :size="18" aria-hidden="true" css-classes="mr-2" />
+ <strong
+ class="file-title-name mr-1 js-blob-header-filepath"
+ data-qa-selector="file_title_name"
+ >{{ blob.path }}</strong
+ >
+ </template>
<small class="mr-2">{{ blobSize }}</small>
diff --git a/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue b/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue
index 7155a1d35b1..ed03213d7cf 100644
--- a/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue
+++ b/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue
@@ -45,7 +45,7 @@ export default {
};
</script>
<template>
- <gl-button-group class="js-blob-viewer-switcher ml-2">
+ <gl-button-group class="js-blob-viewer-switcher mx-2">
<gl-deprecated-button
v-gl-tooltip.hover
:aria-label="$options.SIMPLE_BLOB_VIEWER_TITLE"
diff --git a/app/assets/javascripts/blob/components/constants.js b/app/assets/javascripts/blob/components/constants.js
index d3fed9e51e9..93dceacabdd 100644
--- a/app/assets/javascripts/blob/components/constants.js
+++ b/app/assets/javascripts/blob/components/constants.js
@@ -1,4 +1,5 @@
-import { __ } from '~/locale';
+import { __, sprintf } from '~/locale';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
export const BTN_COPY_CONTENTS_TITLE = __('Copy file contents');
export const BTN_RAW_TITLE = __('Open raw');
@@ -9,3 +10,56 @@ export const SIMPLE_BLOB_VIEWER_TITLE = __('Display source');
export const RICH_BLOB_VIEWER = 'rich';
export const RICH_BLOB_VIEWER_TITLE = __('Display rendered file');
+
+export const BLOB_RENDER_EVENT_LOAD = 'force-content-fetch';
+export const BLOB_RENDER_EVENT_SHOW_SOURCE = 'force-switch-viewer';
+
+export const BLOB_RENDER_ERRORS = {
+ REASONS: {
+ COLLAPSED: {
+ id: 'collapsed',
+ text: sprintf(__('it is larger than %{limit}'), {
+ limit: numberToHumanSize(1048576), // 1MB in bytes
+ }),
+ },
+ TOO_LARGE: {
+ id: 'too_large',
+ text: sprintf(__('it is larger than %{limit}'), {
+ limit: numberToHumanSize(104857600), // 100MB in bytes
+ }),
+ },
+ EXTERNAL: {
+ id: 'server_side_but_stored_externally',
+ text: {
+ lfs: __('it is stored in LFS'),
+ build_artifact: __('it is stored as a job artifact'),
+ default: __('it is stored externally'),
+ },
+ },
+ },
+ OPTIONS: {
+ LOAD: {
+ id: 'load',
+ text: __('load it anyway'),
+ conjunction: __('or'),
+ href: '#',
+ target: '',
+ event: BLOB_RENDER_EVENT_LOAD,
+ },
+ SHOW_SOURCE: {
+ id: 'show_source',
+ text: __('view the source'),
+ conjunction: __('or'),
+ href: '#',
+ target: '',
+ event: BLOB_RENDER_EVENT_SHOW_SOURCE,
+ },
+ DOWNLOAD: {
+ id: 'download',
+ text: __('download it'),
+ conjunction: '',
+ target: '_blank',
+ condition: true,
+ },
+ },
+};