Commit 71636d36 authored by derek-knox's avatar derek-knox

Add EditHeader to StaticSiteEditor component

EditHeader manages the optional display of a
title and falls back to a default label. The
initial plan of the 'Return to site' button
in the header has been moved to the toolbar.
parent 20199b04
<script>
import { DEFAULT_HEADING } from '../constants';
export default {
props: {
title: {
type: String,
required: false,
default: '',
},
},
computed: {
heading() {
return this.title || DEFAULT_HEADING;
},
},
};
</script>
<template>
<div>
<h3 ref="sseHeading">{{ heading }}</h3>
</div>
</template>
...@@ -7,6 +7,11 @@ export default { ...@@ -7,6 +7,11 @@ export default {
GlLoadingIcon, GlLoadingIcon,
}, },
props: { props: {
returnUrl: {
type: String,
required: false,
default: '',
},
saveable: { saveable: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -23,6 +28,10 @@ export default { ...@@ -23,6 +28,10 @@ export default {
<template> <template>
<div class="d-flex bg-light border-top justify-content-between align-items-center py-3 px-4"> <div class="d-flex bg-light border-top justify-content-between align-items-center py-3 px-4">
<gl-loading-icon :class="{ invisible: !savingChanges }" size="md" /> <gl-loading-icon :class="{ invisible: !savingChanges }" size="md" />
<div>
<gl-new-button v-if="returnUrl" ref="returnUrlLink" :href="returnUrl">{{
s__('StaticSiteEditor|Return to site')
}}</gl-new-button>
<gl-new-button <gl-new-button
variant="success" variant="success"
:disabled="!saveable || savingChanges" :disabled="!saveable || savingChanges"
...@@ -31,4 +40,5 @@ export default { ...@@ -31,4 +40,5 @@ export default {
{{ __('Submit Changes') }} {{ __('Submit Changes') }}
</gl-new-button> </gl-new-button>
</div> </div>
</div>
</template> </template>
...@@ -3,16 +3,25 @@ import { mapState, mapGetters, mapActions } from 'vuex'; ...@@ -3,16 +3,25 @@ import { mapState, mapGetters, mapActions } from 'vuex';
import { GlSkeletonLoader } from '@gitlab/ui'; import { GlSkeletonLoader } from '@gitlab/ui';
import EditArea from './edit_area.vue'; import EditArea from './edit_area.vue';
import EditHeader from './edit_header.vue';
import Toolbar from './publish_toolbar.vue'; import Toolbar from './publish_toolbar.vue';
export default { export default {
components: { components: {
EditArea, EditArea,
EditHeader,
GlSkeletonLoader, GlSkeletonLoader,
Toolbar, Toolbar,
}, },
computed: { computed: {
...mapState(['content', 'isLoadingContent', 'isSavingChanges', 'isContentLoaded']), ...mapState([
'content',
'isLoadingContent',
'isSavingChanges',
'isContentLoaded',
'returnUrl',
'title',
]),
...mapGetters(['contentChanged']), ...mapGetters(['contentChanged']),
}, },
mounted() { mounted() {
...@@ -36,12 +45,14 @@ export default { ...@@ -36,12 +45,14 @@ export default {
</gl-skeleton-loader> </gl-skeleton-loader>
</div> </div>
<div v-if="isContentLoaded" class="d-flex flex-grow-1 flex-column"> <div v-if="isContentLoaded" class="d-flex flex-grow-1 flex-column">
<edit-header class="w-75 align-self-center py-2" :title="title" />
<edit-area <edit-area
class="w-75 h-100 shadow-none align-self-center" class="w-75 h-100 shadow-none align-self-center"
:value="content" :value="content"
@input="setContent" @input="setContent"
/> />
<toolbar <toolbar
:return-url="returnUrl"
:saveable="contentChanged" :saveable="contentChanged"
:saving-changes="isSavingChanges" :saving-changes="isSavingChanges"
@submit="submitChanges" @submit="submitChanges"
......
...@@ -10,3 +10,5 @@ export const SUBMIT_CHANGES_COMMIT_ERROR = s__( ...@@ -10,3 +10,5 @@ export const SUBMIT_CHANGES_COMMIT_ERROR = s__(
export const SUBMIT_CHANGES_MERGE_REQUEST_ERROR = s__( export const SUBMIT_CHANGES_MERGE_REQUEST_ERROR = s__(
'StaticSiteEditor|Could not create merge request.', 'StaticSiteEditor|Could not create merge request.',
); );
export const DEFAULT_HEADING = s__('StaticSiteEditor|Static site editor');
...@@ -3,10 +3,10 @@ import StaticSiteEditor from './components/static_site_editor.vue'; ...@@ -3,10 +3,10 @@ import StaticSiteEditor from './components/static_site_editor.vue';
import createStore from './store'; import createStore from './store';
const initStaticSiteEditor = el => { const initStaticSiteEditor = el => {
const { projectId, path: sourcePath } = el.dataset; const { projectId, returnUrl, path: sourcePath } = el.dataset;
const store = createStore({ const store = createStore({
initialState: { projectId, sourcePath, username: window.gon.current_username }, initialState: { projectId, returnUrl, sourcePath, username: window.gon.current_username },
}); });
return new Vue({ return new Vue({
......
const createState = (initialState = {}) => ({ const createState = (initialState = {}) => ({
username: null, username: null,
projectId: null, projectId: null,
returnUrl: null,
sourcePath: null, sourcePath: null,
isLoadingContent: false, isLoadingContent: false,
......
...@@ -19404,6 +19404,9 @@ msgstr "" ...@@ -19404,6 +19404,9 @@ msgstr ""
msgid "StaticSiteEditor|Return to site" msgid "StaticSiteEditor|Return to site"
msgstr "" msgstr ""
msgid "StaticSiteEditor|Static site editor"
msgstr ""
msgid "StaticSiteEditor|Success!" msgid "StaticSiteEditor|Success!"
msgstr "" msgstr ""
......
import { shallowMount } from '@vue/test-utils';
import EditHeader from '~/static_site_editor/components/edit_header.vue';
import { DEFAULT_HEADING } from '~/static_site_editor/constants';
import { sourceContentTitle } from '../mock_data';
describe('~/static_site_editor/components/edit_header.vue', () => {
let wrapper;
const buildWrapper = (propsData = {}) => {
wrapper = shallowMount(EditHeader, {
propsData: {
...propsData,
},
});
};
const findHeading = () => wrapper.find({ ref: 'sseHeading' });
beforeEach(() => {
buildWrapper();
});
afterEach(() => {
wrapper.destroy();
});
it('renders the default heading if there is no title prop', () => {
expect(findHeading().text()).toBe(DEFAULT_HEADING);
});
it('renders the title prop value in the heading', () => {
buildWrapper({ title: sourceContentTitle });
expect(findHeading().text()).toBe(sourceContentTitle);
});
});
...@@ -3,6 +3,8 @@ import { GlNewButton, GlLoadingIcon } from '@gitlab/ui'; ...@@ -3,6 +3,8 @@ import { GlNewButton, GlLoadingIcon } from '@gitlab/ui';
import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue'; import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue';
import { returnUrl } from '../mock_data';
describe('Static Site Editor Toolbar', () => { describe('Static Site Editor Toolbar', () => {
let wrapper; let wrapper;
...@@ -15,6 +17,7 @@ describe('Static Site Editor Toolbar', () => { ...@@ -15,6 +17,7 @@ describe('Static Site Editor Toolbar', () => {
}); });
}; };
const findReturnUrlLink = () => wrapper.find({ ref: 'returnUrlLink' });
const findSaveChangesButton = () => wrapper.find(GlNewButton); const findSaveChangesButton = () => wrapper.find(GlNewButton);
const findLoadingIndicator = () => wrapper.find(GlLoadingIcon); const findLoadingIndicator = () => wrapper.find(GlLoadingIcon);
...@@ -38,6 +41,17 @@ describe('Static Site Editor Toolbar', () => { ...@@ -38,6 +41,17 @@ describe('Static Site Editor Toolbar', () => {
expect(findLoadingIndicator().classes()).toContain('invisible'); expect(findLoadingIndicator().classes()).toContain('invisible');
}); });
it('does not render returnUrl link', () => {
expect(findReturnUrlLink().exists()).toBe(false);
});
it('renders returnUrl link when returnUrl prop exists', () => {
buildWrapper({ returnUrl });
expect(findReturnUrlLink().exists()).toBe(true);
expect(findReturnUrlLink().attributes('href')).toBe(returnUrl);
});
describe('when saveable', () => { describe('when saveable', () => {
it('enables Submit Changes button', () => { it('enables Submit Changes button', () => {
buildWrapper({ saveable: true }); buildWrapper({ saveable: true });
......
...@@ -7,9 +7,10 @@ import createState from '~/static_site_editor/store/state'; ...@@ -7,9 +7,10 @@ import createState from '~/static_site_editor/store/state';
import StaticSiteEditor from '~/static_site_editor/components/static_site_editor.vue'; import StaticSiteEditor from '~/static_site_editor/components/static_site_editor.vue';
import EditArea from '~/static_site_editor/components/edit_area.vue'; import EditArea from '~/static_site_editor/components/edit_area.vue';
import EditHeader from '~/static_site_editor/components/edit_header.vue';
import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue'; import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue';
import { sourceContent } from '../mock_data'; import { sourceContent, sourceContentTitle } from '../mock_data';
const localVue = createLocalVue(); const localVue = createLocalVue();
...@@ -60,6 +61,7 @@ describe('StaticSiteEditor', () => { ...@@ -60,6 +61,7 @@ describe('StaticSiteEditor', () => {
}; };
const findEditArea = () => wrapper.find(EditArea); const findEditArea = () => wrapper.find(EditArea);
const findEditHeader = () => wrapper.find(EditHeader);
const findPublishToolbar = () => wrapper.find(PublishToolbar); const findPublishToolbar = () => wrapper.find(PublishToolbar);
const findSkeletonLoader = () => wrapper.find(GlSkeletonLoader); const findSkeletonLoader = () => wrapper.find(GlSkeletonLoader);
...@@ -77,16 +79,21 @@ describe('StaticSiteEditor', () => { ...@@ -77,16 +79,21 @@ describe('StaticSiteEditor', () => {
expect(findEditArea().exists()).toBe(false); expect(findEditArea().exists()).toBe(false);
}); });
it('does not render edit header', () => {
expect(findEditHeader().exists()).toBe(false);
});
it('does not render toolbar', () => { it('does not render toolbar', () => {
expect(findPublishToolbar().exists()).toBe(false); expect(findPublishToolbar().exists()).toBe(false);
}); });
}); });
describe('when content is loaded', () => { describe('when content is loaded', () => {
const content = 'edit area content'; const content = sourceContent;
const title = sourceContentTitle;
beforeEach(() => { beforeEach(() => {
buildContentLoadedStore({ initialState: { content } }); buildContentLoadedStore({ initialState: { content, title } });
buildWrapper(); buildWrapper();
}); });
...@@ -94,6 +101,10 @@ describe('StaticSiteEditor', () => { ...@@ -94,6 +101,10 @@ describe('StaticSiteEditor', () => {
expect(findEditArea().exists()).toBe(true); expect(findEditArea().exists()).toBe(true);
}); });
it('renders the edit header', () => {
expect(findEditHeader().exists()).toBe(true);
});
it('does not render skeleton loader', () => { it('does not render skeleton loader', () => {
expect(findSkeletonLoader().exists()).toBe(false); expect(findSkeletonLoader().exists()).toBe(false);
}); });
...@@ -102,6 +113,10 @@ describe('StaticSiteEditor', () => { ...@@ -102,6 +113,10 @@ describe('StaticSiteEditor', () => {
expect(findEditArea().props('value')).toBe(content); expect(findEditArea().props('value')).toBe(content);
}); });
it('passes page title to edit header', () => {
expect(findEditHeader().props('title')).toBe(title);
});
it('renders toolbar', () => { it('renders toolbar', () => {
expect(findPublishToolbar().exists()).toBe(true); expect(findPublishToolbar().exists()).toBe(true);
}); });
......
...@@ -11,11 +11,11 @@ twitter_image: '/images/tweets/handbook-gitlab.png' ...@@ -11,11 +11,11 @@ twitter_image: '/images/tweets/handbook-gitlab.png'
- TOC - TOC
{:toc .hidden-md .hidden-lg} {:toc .hidden-md .hidden-lg}
`; `;
export const sourceContentTitle = 'Handbook'; export const sourceContentTitle = 'Handbook';
export const username = 'gitlabuser'; export const username = 'gitlabuser';
export const projectId = '123456'; export const projectId = '123456';
export const returnUrl = 'https://www.gitlab.com';
export const sourcePath = 'foobar.md.html'; export const sourcePath = 'foobar.md.html';
export const savedContentMeta = { export const savedContentMeta = {
......
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