gitlab-org--gitlab-foss/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue

367 lines
10 KiB
Vue

<script>
import {
GlForm,
GlIcon,
GlLink,
GlButton,
GlSprintf,
GlFormGroup,
GlFormInput,
GlFormSelect,
} from '@gitlab/ui';
import csrf from '~/lib/utils/csrf';
import { setUrlFragment } from '~/lib/utils/url_utility';
import { s__, sprintf } from '~/locale';
import Tracking from '~/tracking';
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
import {
SAVED_USING_CONTENT_EDITOR_ACTION,
WIKI_CONTENT_EDITOR_TRACKING_LABEL,
WIKI_FORMAT_LABEL,
WIKI_FORMAT_UPDATED_ACTION,
CONTENT_EDITOR_LOADED_ACTION,
} from '../constants';
const trackingMixin = Tracking.mixin({
label: WIKI_CONTENT_EDITOR_TRACKING_LABEL,
});
const MARKDOWN_LINK_TEXT = {
markdown: '[Link Title](page-slug)',
rdoc: '{Link title}[link:page-slug]',
asciidoc: 'link:page-slug[Link title]',
org: '[[page-slug]]',
};
export default {
i18n: {
title: {
label: s__('WikiPage|Title'),
placeholder: s__('WikiPage|Page title'),
helpText: {
existingPage: s__(
'WikiPage|Tip: You can move this page by adding the path to the beginning of the title.',
),
newPage: s__(
'WikiPage|Tip: You can specify the full path for the new file. We will automatically create any missing directories.',
),
learnMore: s__('WikiPage|Learn more.'),
},
},
format: {
label: s__('WikiPage|Format'),
},
content: {
label: s__('WikiPage|Content'),
placeholder: s__('WikiPage|Write your content or drag files here…'),
},
linksHelpText: s__(
'WikiPage|To link to a (new) page, simply type %{linkExample}. More examples are in the %{linkStart}documentation%{linkEnd}.',
),
commitMessage: {
label: s__('WikiPage|Commit message'),
value: {
existingPage: s__('WikiPage|Update %{pageTitle}'),
newPage: s__('WikiPage|Create %{pageTitle}'),
},
},
submitButton: {
existingPage: s__('WikiPage|Save changes'),
newPage: s__('WikiPage|Create page'),
},
cancel: s__('WikiPage|Cancel'),
},
components: {
GlIcon,
GlForm,
GlFormGroup,
GlFormInput,
GlFormSelect,
GlSprintf,
GlLink,
GlButton,
MarkdownEditor,
},
mixins: [trackingMixin],
inject: ['formatOptions', 'pageInfo'],
data() {
return {
editingMode: 'source',
title: this.pageInfo.title?.trim() || '',
format: this.pageInfo.format || 'markdown',
content: this.pageInfo.content || '',
commitMessage: '',
isDirty: false,
contentEditorEmpty: false,
isContentEditorActive: false,
};
},
computed: {
noContent() {
return !this.content.trim();
},
csrfToken() {
return csrf.token;
},
formAction() {
return this.pageInfo.persisted ? this.pageInfo.path : this.pageInfo.createPath;
},
helpPath() {
return setUrlFragment(
this.pageInfo.helpPath,
this.pageInfo.persisted ? 'move-a-wiki-page' : 'create-a-new-wiki-page',
);
},
commitMessageI18n() {
return this.pageInfo.persisted
? this.$options.i18n.commitMessage.value.existingPage
: this.$options.i18n.commitMessage.value.newPage;
},
linkExample() {
return MARKDOWN_LINK_TEXT[this.format];
},
submitButtonText() {
return this.pageInfo.persisted
? this.$options.i18n.submitButton.existingPage
: this.$options.i18n.submitButton.newPage;
},
titleHelpText() {
return this.pageInfo.persisted
? this.$options.i18n.title.helpText.existingPage
: this.$options.i18n.title.helpText.newPage;
},
cancelFormPath() {
if (this.pageInfo.persisted) return this.pageInfo.path;
return this.pageInfo.wikiPath;
},
wikiSpecificMarkdownHelpPath() {
return setUrlFragment(this.pageInfo.markdownHelpPath, 'wiki-specific-markdown');
},
contentEditorHelpPath() {
return setUrlFragment(this.pageInfo.helpPath, 'gitlab-flavored-markdown-support');
},
isMarkdownFormat() {
return this.format === 'markdown';
},
displayWikiSpecificMarkdownHelp() {
return !this.isContentEditorActive;
},
disableSubmitButton() {
return this.noContent || !this.title;
},
},
mounted() {
this.updateCommitMessage();
window.addEventListener('beforeunload', this.onPageUnload);
},
destroyed() {
window.removeEventListener('beforeunload', this.onPageUnload);
},
methods: {
async handleFormSubmit(e) {
e.preventDefault();
this.trackFormSubmit();
this.trackWikiFormat();
// Wait until form field values are refreshed
await this.$nextTick();
e.target.submit();
this.isDirty = false;
},
onPageUnload(event) {
if (!this.isDirty) return undefined;
event.preventDefault();
// eslint-disable-next-line no-param-reassign
event.returnValue = '';
return '';
},
updateCommitMessage() {
if (!this.title) return;
// Replace hyphens with spaces
const newTitle = this.title.replace(/-+/g, ' ');
const newCommitMessage = sprintf(this.commitMessageI18n, { pageTitle: newTitle }, false);
this.commitMessage = newCommitMessage;
},
notifyContentEditorActive() {
this.isContentEditorActive = true;
this.trackContentEditorLoaded();
},
notifyContentEditorInactive() {
this.isContentEditorActive = false;
},
trackFormSubmit() {
if (this.isContentEditorActive) {
this.track(SAVED_USING_CONTENT_EDITOR_ACTION);
}
},
trackWikiFormat() {
this.track(WIKI_FORMAT_UPDATED_ACTION, {
label: WIKI_FORMAT_LABEL,
extra: {
project_path: this.pageInfo.path,
old_format: this.pageInfo.format,
value: this.format,
},
});
},
trackContentEditorLoaded() {
this.track(CONTENT_EDITOR_LOADED_ACTION);
},
checkDirty(markdown) {
this.isDirty = this.pageInfo.content !== markdown;
},
},
};
</script>
<template>
<gl-form
:action="formAction"
method="post"
class="wiki-form common-note-form gl-mt-3 js-quick-submit"
@submit="handleFormSubmit"
>
<input :value="csrfToken" type="hidden" name="authenticity_token" />
<input v-if="pageInfo.persisted" type="hidden" name="_method" value="put" />
<input
:v-if="pageInfo.persisted"
type="hidden"
name="wiki[last_commit_sha]"
:value="pageInfo.lastCommitSha"
/>
<div class="row">
<div class="col-sm-9">
<gl-form-group :label="$options.i18n.title.label" label-for="wiki_title">
<template #description>
<gl-icon class="gl-mr-n1" name="bulb" />
{{ titleHelpText }}
<gl-link :href="helpPath" target="_blank">
{{ $options.i18n.title.helpText.learnMore }}
</gl-link>
</template>
<gl-form-input
id="wiki_title"
v-model="title"
name="wiki[title]"
type="text"
class="form-control"
data-qa-selector="wiki_title_textbox"
:required="true"
:autofocus="!pageInfo.persisted"
:placeholder="$options.i18n.title.placeholder"
@input="updateCommitMessage"
/>
</gl-form-group>
</div>
<div class="col-sm-3 row-sm-10">
<gl-form-group :label="$options.i18n.format.label" label-for="wiki_format">
<gl-form-select
id="wiki_format"
v-model="format"
name="wiki[format]"
:disabled="isContentEditorActive"
class="form-control"
:value="formatOptions.Markdown"
>
<option v-for="(key, label) of formatOptions" :key="key" :value="key">
{{ label }}
</option>
</gl-form-select>
</gl-form-group>
</div>
</div>
<div class="row" data-testid="wiki-form-content-fieldset">
<div class="col-sm-12 row-sm-5">
<gl-form-group>
<markdown-editor
v-model="content"
:render-markdown-path="pageInfo.markdownPreviewPath"
:markdown-docs-path="pageInfo.markdownHelpPath"
:uploads-path="pageInfo.uploadsPath"
:enable-content-editor="isMarkdownFormat"
:enable-preview="isMarkdownFormat"
:init-on-autofocus="pageInfo.persisted"
:form-field-placeholder="$options.i18n.content.placeholder"
:form-field-aria-label="$options.i18n.content.label"
form-field-id="wiki_content"
form-field-name="wiki[content]"
@contentEditor="notifyContentEditorActive"
@markdownField="notifyContentEditorInactive"
@input="checkDirty"
/>
<div class="form-text gl-text-gray-600">
<gl-sprintf
v-if="displayWikiSpecificMarkdownHelp"
:message="$options.i18n.linksHelpText"
>
<template #linkExample>
<code>{{ linkExample }}</code>
</template>
<template
#link="// eslint-disable-next-line vue/no-template-shadow
{ content }"
><gl-link
:href="wikiSpecificMarkdownHelpPath"
target="_blank"
data-testid="wiki-markdown-help-link"
>{{ content }}</gl-link
></template
>
</gl-sprintf>
</div>
</gl-form-group>
</div>
</div>
<div class="row">
<div class="col-sm-12 row-sm-5">
<gl-form-group :label="$options.i18n.commitMessage.label" label-for="wiki_message">
<gl-form-input
id="wiki_message"
v-model.trim="commitMessage"
name="wiki[message]"
type="text"
class="form-control"
data-qa-selector="wiki_message_textbox"
:placeholder="$options.i18n.commitMessage.label"
/>
</gl-form-group>
</div>
</div>
<div class="form-actions">
<gl-button
category="primary"
variant="confirm"
type="submit"
data-qa-selector="wiki_submit_button"
data-testid="wiki-submit-button"
:disabled="disableSubmitButton"
>{{ submitButtonText }}</gl-button
>
<gl-button data-testid="wiki-cancel-button" :href="cancelFormPath" class="float-right">{{
$options.i18n.cancel
}}</gl-button>
</div>
</gl-form>
</template>