Commit 7e6ebcdf authored by Jacques Erasmus's avatar Jacques Erasmus

Merge branch 'ph/blobTOC' into 'master'

Added ToC dropdown to markdown blob viewers

See merge request gitlab-org/gitlab!61802
parents d92951f3 e6aa2fbd
<script>
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
function getHeaderNumber(el) {
return parseInt(el.tagName.match(/\d+/)[0], 10);
}
export default {
components: {
GlDropdown,
GlDropdownItem,
},
data() {
return {
isHidden: false,
items: [],
};
},
mounted() {
this.blobViewer = document.querySelector('.blob-viewer[data-type="rich"]');
this.observer = new MutationObserver(() => {
if (this.blobViewer.classList.contains('hidden')) {
this.isHidden = true;
} else if (this.blobViewer.getAttribute('data-loaded') === 'true') {
this.isHidden = false;
this.generateHeaders();
}
});
if (this.blobViewer) {
this.observer.observe(this.blobViewer, {
attributes: true,
});
}
},
beforeDestroy() {
if (this.observer) {
this.observer.disconnect();
}
},
methods: {
generateHeaders() {
const headers = [...this.blobViewer.querySelectorAll('h1,h2,h3,h4,h5,h6')];
if (headers.length) {
const firstHeader = getHeaderNumber(headers[0]);
headers.forEach((el) => {
this.items.push({
text: el.textContent.trim(),
anchor: el.querySelector('a').getAttribute('id'),
spacing: Math.max((getHeaderNumber(el) - firstHeader) * 8, 0),
});
});
}
},
},
};
</script>
<template>
<gl-dropdown v-if="!isHidden && items.length" icon="list-bulleted" class="gl-mr-2">
<gl-dropdown-item v-for="(item, index) in items" :key="index" :href="`#${item.anchor}`">
<span
:style="{ 'padding-left': `${item.spacing}px` }"
class="gl-display-block"
data-testid="tableContentsLink"
>
{{ item.text }}
</span>
</gl-dropdown-item>
</gl-dropdown>
</template>
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import TableOfContents from '~/blob/components/table_contents.vue';
import PipelineTourSuccessModal from '~/blob/pipeline_tour_success_modal.vue';
import BlobViewer from '~/blob/viewer/index';
import GpgBadges from '~/gpg_badges';
......@@ -92,3 +93,15 @@ if (successPipelineEl) {
},
});
}
const tableContentsEl = document.querySelector('.js-table-contents');
if (tableContentsEl) {
// eslint-disable-next-line no-new
new Vue({
el: tableContentsEl,
render(h) {
return h(TableOfContents);
},
});
}
.file-header-content
- if Gitlab::MarkupHelper.gitlab_markdown?(blob.path)
.js-table-contents
= blob_icon blob.mode, blob.name
%strong.file-title-name.gl-word-break-all{ data: { qa_selector: 'file_name_content' } }
......
import { GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import TableContents from '~/blob/components/table_contents.vue';
let wrapper;
function createComponent() {
wrapper = shallowMount(TableContents);
}
async function setLoaded(loaded) {
document.querySelector('.blob-viewer').setAttribute('data-loaded', loaded);
await nextTick();
}
describe('Markdown table of contents component', () => {
beforeEach(() => {
setFixtures(`
<div class="blob-viewer" data-type="rich" data-loaded="false">
<h1><a href="#1"></a>Hello</h1>
<h2><a href="#2"></a>World</h2>
<h3><a href="#3"></a>Testing</h3>
<h2><a href="#4"></a>GitLab</h2>
</div>
`);
});
afterEach(() => {
wrapper.destroy();
});
describe('not loaded', () => {
it('does not populate dropdown', () => {
createComponent();
expect(wrapper.findComponent(GlDropdownItem).exists()).toBe(false);
});
});
describe('loaded', () => {
it('populates dropdown', async () => {
createComponent();
await setLoaded(true);
const dropdownItems = wrapper.findAllComponents(GlDropdownItem);
expect(dropdownItems.exists()).toBe(true);
expect(dropdownItems.length).toBe(4);
});
it('sets padding for dropdown items', async () => {
createComponent();
await setLoaded(true);
const dropdownLinks = wrapper.findAll('[data-testid="tableContentsLink"]');
expect(dropdownLinks.at(0).element.style.paddingLeft).toBe('0px');
expect(dropdownLinks.at(1).element.style.paddingLeft).toBe('8px');
expect(dropdownLinks.at(2).element.style.paddingLeft).toBe('16px');
expect(dropdownLinks.at(3).element.style.paddingLeft).toBe('8px');
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment