diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-10-28 00:09:58 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-10-28 00:09:58 +0300 |
commit | 1f992463a93d2db88a661dd2a60a1f0a81375f44 (patch) | |
tree | 396a12aa7f82d2c7e2ebf672fda9993796eae5cf /app/assets/javascripts/vue_shared/components/markdown_drawer | |
parent | bd746eebdc82ea3731b38cd903a999569ff3b8be (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/vue_shared/components/markdown_drawer')
3 files changed, 203 insertions, 0 deletions
diff --git a/app/assets/javascripts/vue_shared/components/markdown_drawer/makrdown_drawer.stories.js b/app/assets/javascripts/vue_shared/components/markdown_drawer/makrdown_drawer.stories.js new file mode 100644 index 00000000000..03bd64e2a57 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/markdown_drawer/makrdown_drawer.stories.js @@ -0,0 +1,54 @@ +import { GlButton } from '@gitlab/ui'; +import { MOCK_HTML } from '../../../../../../spec/frontend/vue_shared/components/markdown_drawer/mock_data'; +import MarkdownDrawer from './markdown_drawer.vue'; + +export default { + component: MarkdownDrawer, + title: 'vue_shared/markdown_drawer', + parameters: { + mirage: { + timing: 1000, + handlers: { + get: { + '/help/user/search/global_search/advanced_search_syntax.json': [ + 200, + {}, + { html: MOCK_HTML }, + ], + }, + }, + }, + }, +}; + +const createStory = ({ ...options }) => (_, { argTypes }) => ({ + components: { MarkdownDrawer, GlButton }, + props: Object.keys(argTypes), + data() { + return { + render: false, + }; + }, + methods: { + toggleDrawer() { + this.$refs.drawer.toggleDrawer(); + }, + }, + mounted() { + window.requestAnimationFrame(() => { + this.render = true; + }); + }, + template: ` + <div v-if="render"> + <gl-button @click="toggleDrawer">Open Drawer</gl-button> + <markdown-drawer + :documentPath="'user/search/global_search/advanced_search_syntax.json'" + ref="drawer" + /> + </div> + `, + ...options, +}); + +export const Default = createStory({}); diff --git a/app/assets/javascripts/vue_shared/components/markdown_drawer/markdown_drawer.vue b/app/assets/javascripts/vue_shared/components/markdown_drawer/markdown_drawer.vue new file mode 100644 index 00000000000..a4b509f8656 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/markdown_drawer/markdown_drawer.vue @@ -0,0 +1,117 @@ +<script> +import { GlSafeHtmlDirective as SafeHtml, GlDrawer, GlAlert, GlSkeletonLoader } from '@gitlab/ui'; +import $ from 'jquery'; +import '~/behaviors/markdown/render_gfm'; +import { s__ } from '~/locale'; +import { contentTop } from '~/lib/utils/common_utils'; +import { getRenderedMarkdown } from './utils/fetch'; + +export const cache = {}; + +export default { + name: 'MarkdownDrawer', + components: { + GlDrawer, + GlAlert, + GlSkeletonLoader, + }, + directives: { + SafeHtml, + }, + i18n: { + alert: s__('MardownDrawer|Could not fetch help contents.'), + }, + props: { + documentPath: { + type: String, + required: true, + }, + }, + data() { + return { + loading: false, + hasFetchError: false, + title: '', + body: null, + open: false, + }; + }, + computed: { + drawerOffsetTop() { + return `${contentTop()}px`; + }, + }, + watch: { + documentPath: { + immediate: true, + handler: 'fetchMarkdown', + }, + open(open) { + if (open && this.body) { + this.renderGLFM(); + } + }, + }, + methods: { + async fetchMarkdown() { + const cached = cache[this.documentPath]; + this.hasFetchError = false; + this.title = ''; + if (cached) { + this.title = cached.title; + this.body = cached.body; + if (this.open) { + this.renderGLFM(); + } + } else { + this.loading = true; + const { body, title, hasFetchError } = await getRenderedMarkdown(this.documentPath); + this.title = title; + this.body = body; + this.loading = false; + this.hasFetchError = hasFetchError; + if (this.open) { + this.renderGLFM(); + } + cache[this.documentPath] = { title, body }; + } + }, + renderGLFM() { + this.$nextTick(() => { + $(this.$refs['content-element']).renderGFM(); + }); + }, + closeDrawer() { + this.open = false; + }, + toggleDrawer() { + this.open = !this.open; + }, + openDrawer() { + this.open = true; + }, + }, + safeHtmlConfig: { + ADD_TAGS: ['copy-code'], + }, +}; +</script> +<template> + <gl-drawer :header-height="drawerOffsetTop" :open="open" header-sticky @close="closeDrawer"> + <template #title> + <h4 data-testid="title-element" class="gl-m-0">{{ title }}</h4> + </template> + <template #default> + <div v-if="hasFetchError"> + <gl-alert :dismissible="false" variant="danger">{{ $options.i18n.alert }}</gl-alert> + </div> + <gl-skeleton-loader v-else-if="loading" /> + <div + v-else + ref="content-element" + v-safe-html:[$options.safeHtmlConfig]="body" + class="md" + ></div> + </template> + </gl-drawer> +</template> diff --git a/app/assets/javascripts/vue_shared/components/markdown_drawer/utils/fetch.js b/app/assets/javascripts/vue_shared/components/markdown_drawer/utils/fetch.js new file mode 100644 index 00000000000..7c8e1bc160a --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/markdown_drawer/utils/fetch.js @@ -0,0 +1,32 @@ +import * as Sentry from '@sentry/browser'; +import { helpPagePath } from '~/helpers/help_page_helper'; +import axios from '~/lib/utils/axios_utils'; + +export const splitDocument = (htmlString) => { + const htmlDocument = new DOMParser().parseFromString(htmlString, 'text/html'); + const title = htmlDocument.querySelector('h1')?.innerText; + htmlDocument.querySelector('h1')?.remove(); + return { + title, + body: htmlDocument.querySelector('body').innerHTML.toString(), + }; +}; + +export const getRenderedMarkdown = (documentPath) => { + return axios + .get(helpPagePath(documentPath)) + .then(({ data }) => { + const { body, title } = splitDocument(data.html); + return { + body, + title, + hasFetchError: false, + }; + }) + .catch((e) => { + Sentry.captureException(e); + return { + hasFetchError: true, + }; + }); +}; |