diff options
Diffstat (limited to 'app/assets/javascripts')
12 files changed, 218 insertions, 11 deletions
diff --git a/app/assets/javascripts/blob/components/blob_content.vue b/app/assets/javascripts/blob/components/blob_content.vue new file mode 100644 index 00000000000..2639a099093 --- /dev/null +++ b/app/assets/javascripts/blob/components/blob_content.vue @@ -0,0 +1,51 @@ +<script> +import { GlLoadingIcon } from '@gitlab/ui'; +import { RichViewer, SimpleViewer } from '~/vue_shared/components/blob_viewers'; +import BlobContentError from './blob_content_error.vue'; + +export default { + components: { + GlLoadingIcon, + BlobContentError, + }, + props: { + content: { + type: String, + default: '', + required: false, + }, + loading: { + type: Boolean, + default: true, + required: false, + }, + activeViewer: { + type: Object, + required: true, + }, + }, + computed: { + viewer() { + switch (this.activeViewer.type) { + case 'rich': + return RichViewer; + default: + return SimpleViewer; + } + }, + viewerError() { + return this.activeViewer.renderError; + }, + }, +}; +</script> +<template> + <div class="blob-viewer" :data-type="activeViewer.type"> + <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" /> + <component :is="viewer" v-else ref="contentViewer" :content="content" /> + </template> + </div> +</template> diff --git a/app/assets/javascripts/blob/components/blob_content_error.vue b/app/assets/javascripts/blob/components/blob_content_error.vue new file mode 100644 index 00000000000..0f1af0a962d --- /dev/null +++ b/app/assets/javascripts/blob/components/blob_content_error.vue @@ -0,0 +1,15 @@ +<script> +export default { + props: { + viewerError: { + type: String, + required: true, + }, + }, +}; +</script> +<template> + <div class="file-content code"> + <div class="text-center py-4" v-html="viewerError"></div> + </div> +</template> diff --git a/app/assets/javascripts/blob/components/blob_header_default_actions.vue b/app/assets/javascripts/blob/components/blob_header_default_actions.vue index f5157fba819..6b38b871606 100644 --- a/app/assets/javascripts/blob/components/blob_header_default_actions.vue +++ b/app/assets/javascripts/blob/components/blob_header_default_actions.vue @@ -36,11 +36,6 @@ export default { return this.activeViewer === RICH_BLOB_VIEWER; }, }, - methods: { - requestCopyContents() { - this.$emit('copy'); - }, - }, BTN_COPY_CONTENTS_TITLE, BTN_DOWNLOAD_TITLE, BTN_RAW_TITLE, @@ -53,7 +48,7 @@ export default { :aria-label="$options.BTN_COPY_CONTENTS_TITLE" :title="$options.BTN_COPY_CONTENTS_TITLE" :disabled="copyDisabled" - @click="requestCopyContents" + data-clipboard-target="#blob-code-content" > <gl-icon name="copy-to-clipboard" :size="14" /> </gl-button> diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js index 43f5e9954ce..6d2b11e39d3 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import Vue from 'vue'; import Cookies from 'js-cookie'; -import { GlEmptyState } from '@gitlab/ui'; +import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui'; import filterMixins from 'ee_else_ce/analytics/cycle_analytics/mixins/filter_mixins'; import Flash from '../flash'; import { __ } from '~/locale'; @@ -28,6 +28,7 @@ export default () => { name: 'CycleAnalytics', components: { GlEmptyState, + GlLoadingIcon, banner, 'stage-issue-component': stageComponent, 'stage-plan-component': stageComponent, diff --git a/app/assets/javascripts/graphql_shared/fragments/blobviewer.fragment.graphql b/app/assets/javascripts/graphql_shared/fragments/blobviewer.fragment.graphql index 64c894df115..b202ed12f80 100644 --- a/app/assets/javascripts/graphql_shared/fragments/blobviewer.fragment.graphql +++ b/app/assets/javascripts/graphql_shared/fragments/blobviewer.fragment.graphql @@ -1,6 +1,7 @@ fragment BlobViewer on SnippetBlobViewer { collapsed - loadingPartialName renderError tooLarge + type + fileType } diff --git a/app/assets/javascripts/snippets/components/snippet_blob_view.vue b/app/assets/javascripts/snippets/components/snippet_blob_view.vue index 49e0ef35cb8..4703a940e08 100644 --- a/app/assets/javascripts/snippets/components/snippet_blob_view.vue +++ b/app/assets/javascripts/snippets/components/snippet_blob_view.vue @@ -2,13 +2,19 @@ import BlobEmbeddable from '~/blob/components/blob_embeddable.vue'; import { SNIPPET_VISIBILITY_PUBLIC } from '../constants'; import BlobHeader from '~/blob/components/blob_header.vue'; -import GetSnippetBlobQuery from '../queries/snippet.blob.query.graphql'; +import BlobContent from '~/blob/components/blob_content.vue'; import { GlLoadingIcon } from '@gitlab/ui'; +import GetSnippetBlobQuery from '../queries/snippet.blob.query.graphql'; +import GetBlobContent from '../queries/snippet.blob.content.query.graphql'; + +import { SIMPLE_BLOB_VIEWER, RICH_BLOB_VIEWER } from '~/blob/components/constants'; + export default { components: { BlobEmbeddable, BlobHeader, + BlobContent, GlLoadingIcon, }, apollo: { @@ -20,6 +26,23 @@ export default { }; }, update: data => data.snippets.edges[0].node.blob, + result(res) { + const viewer = res.data.snippets.edges[0].node.blob.richViewer + ? RICH_BLOB_VIEWER + : SIMPLE_BLOB_VIEWER; + this.switchViewer(viewer, true); + }, + }, + blobContent: { + query: GetBlobContent, + variables() { + return { + ids: this.snippet.id, + rich: this.activeViewerType === RICH_BLOB_VIEWER, + }; + }, + update: data => + data.snippets.edges[0].node.blob.richData || data.snippets.edges[0].node.blob.plainData, }, }, props: { @@ -31,6 +54,8 @@ export default { data() { return { blob: {}, + blobContent: '', + activeViewerType: window.location.hash ? SIMPLE_BLOB_VIEWER : '', }; }, computed: { @@ -40,6 +65,18 @@ export default { isBlobLoading() { return this.$apollo.queries.blob.loading; }, + isContentLoading() { + return this.$apollo.queries.blobContent.loading; + }, + viewer() { + const { richViewer, simpleViewer } = this.blob; + return this.activeViewerType === RICH_BLOB_VIEWER ? richViewer : simpleViewer; + }, + }, + methods: { + switchViewer(newViewer, respectHash = false) { + this.activeViewerType = respectHash && window.location.hash ? SIMPLE_BLOB_VIEWER : newViewer; + }, }, }; </script> @@ -49,11 +86,12 @@ export default { <gl-loading-icon v-if="isBlobLoading" :label="__('Loading blob')" - :size="2" + size="lg" class="prepend-top-20 append-bottom-20" /> <article v-else class="file-holder snippet-file-content"> - <blob-header :blob="blob" /> + <blob-header :blob="blob" :active-viewer-type="viewer.type" @viewer-changed="switchViewer" /> + <blob-content :loading="isContentLoading" :content="blobContent" :active-viewer="viewer" /> </article> </div> </template> diff --git a/app/assets/javascripts/snippets/queries/snippet.blob.content.query.graphql b/app/assets/javascripts/snippets/queries/snippet.blob.content.query.graphql new file mode 100644 index 00000000000..889a88dd93c --- /dev/null +++ b/app/assets/javascripts/snippets/queries/snippet.blob.content.query.graphql @@ -0,0 +1,13 @@ +query SnippetBlobContent($ids: [ID!], $rich: Boolean!) { + snippets(ids: $ids) { + edges { + node { + id + blob { + richData @include(if: $rich) + plainData @skip(if: $rich) + } + } + } + } +} diff --git a/app/assets/javascripts/vue_shared/components/blob_viewers/constants.js b/app/assets/javascripts/vue_shared/components/blob_viewers/constants.js new file mode 100644 index 00000000000..d4c1808eec2 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/blob_viewers/constants.js @@ -0,0 +1,3 @@ +export const HIGHLIGHT_CLASS_NAME = 'hll'; + +export default {}; diff --git a/app/assets/javascripts/vue_shared/components/blob_viewers/index.js b/app/assets/javascripts/vue_shared/components/blob_viewers/index.js new file mode 100644 index 00000000000..72fba9392f9 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/blob_viewers/index.js @@ -0,0 +1,4 @@ +import RichViewer from './rich_viewer.vue'; +import SimpleViewer from './simple_viewer.vue'; + +export { RichViewer, SimpleViewer }; diff --git a/app/assets/javascripts/vue_shared/components/blob_viewers/mixins.js b/app/assets/javascripts/vue_shared/components/blob_viewers/mixins.js new file mode 100644 index 00000000000..582213ee8d3 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/blob_viewers/mixins.js @@ -0,0 +1,8 @@ +export default { + props: { + content: { + type: String, + required: true, + }, + }, +}; diff --git a/app/assets/javascripts/vue_shared/components/blob_viewers/rich_viewer.vue b/app/assets/javascripts/vue_shared/components/blob_viewers/rich_viewer.vue new file mode 100644 index 00000000000..b3a1df8f303 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/blob_viewers/rich_viewer.vue @@ -0,0 +1,10 @@ +<script> +import ViewerMixin from './mixins'; + +export default { + mixins: [ViewerMixin], +}; +</script> +<template> + <div v-html="content"></div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue b/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue new file mode 100644 index 00000000000..e64c7132117 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue @@ -0,0 +1,68 @@ +<script> +import ViewerMixin from './mixins'; +import { GlIcon } from '@gitlab/ui'; +import { HIGHLIGHT_CLASS_NAME } from './constants'; + +export default { + components: { + GlIcon, + }, + mixins: [ViewerMixin], + data() { + return { + highlightedLine: null, + }; + }, + computed: { + lineNumbers() { + return this.content.split('\n').length; + }, + }, + mounted() { + const { hash } = window.location; + if (hash) this.scrollToLine(hash, true); + }, + methods: { + scrollToLine(hash, scroll = false) { + const lineToHighlight = hash && this.$el.querySelector(hash); + const currentlyHighlighted = this.highlightedLine; + if (lineToHighlight) { + if (currentlyHighlighted) { + currentlyHighlighted.classList.remove(HIGHLIGHT_CLASS_NAME); + } + + lineToHighlight.classList.add(HIGHLIGHT_CLASS_NAME); + this.highlightedLine = lineToHighlight; + if (scroll) { + lineToHighlight.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + } + }, + }, + userColorScheme: window.gon.user_color_scheme, +}; +</script> +<template> + <div + class="file-content code js-syntax-highlight qa-file-content" + :class="$options.userColorScheme" + > + <div class="line-numbers"> + <a + v-for="line in lineNumbers" + :id="`L${line}`" + :key="line" + class="diff-line-num js-line-number" + :href="`#LC${line}`" + :data-line-number="line" + @click="scrollToLine(`#LC${line}`)" + > + <gl-icon :size="12" name="link" /> + {{ line }} + </a> + </div> + <div class="blob-content"> + <pre class="code highlight"><code id="blob-code-content" v-html="content"></code></pre> + </div> + </div> +</template> |