Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 18:44:42 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 18:44:42 +0300
commit4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch)
tree5423a1c7516cffe36384133ade12572cf709398d /app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue
parente570267f2f6b326480d284e0164a6464ba4081bc (diff)
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue')
-rw-r--r--app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue298
1 files changed, 258 insertions, 40 deletions
diff --git a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue
index 6afc33ec8a5..43753926039 100644
--- a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue
+++ b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue
@@ -1,9 +1,21 @@
<script>
-import { GlForm, GlIcon, GlLink, GlButton, GlSprintf } from '@gitlab/ui';
+import {
+ GlForm,
+ GlIcon,
+ GlLink,
+ GlButton,
+ GlSprintf,
+ GlAlert,
+ GlLoadingIcon,
+ GlModal,
+ GlModalDirective,
+} from '@gitlab/ui';
+import axios from '~/lib/utils/axios_utils';
import csrf from '~/lib/utils/csrf';
import { setUrlFragment } from '~/lib/utils/url_utility';
-import { __, s__, sprintf } from '~/locale';
+import { s__, sprintf } from '~/locale';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
const MARKDOWN_LINK_TEXT = {
markdown: '[Link Title](page-slug)',
@@ -13,21 +25,98 @@ const MARKDOWN_LINK_TEXT = {
};
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.',
+ ),
+ moreInformation: s__('WikiPage|More Information.'),
+ },
+ },
+ format: {
+ label: s__('WikiPage|Format'),
+ },
+ content: {
+ label: s__('WikiPage|Content'),
+ placeholder: s__('WikiPage|Write your content or drag files here…'),
+ },
+ contentEditor: {
+ renderFailed: {
+ message: s__(
+ 'WikiPage|An error occured while trying to render the content editor. Please try again later.',
+ ),
+ primaryAction: s__('WikiPage|Retry'),
+ },
+ useNewEditor: s__('WikiPage|Use new editor'),
+ switchToOldEditor: {
+ label: s__('WikiPage|Switch to old editor'),
+ helpText: s__("WikiPage|Switching will discard any changes you've made in the new editor."),
+ modal: {
+ title: s__('WikiPage|Are you sure you want to switch to the old editor?'),
+ primary: s__('WikiPage|Switch to old editor'),
+ cancel: s__('WikiPage|Keep editing'),
+ text: s__(
+ "WikiPage|Switching to the old editor will discard any changes you've made in the new editor.",
+ ),
+ },
+ },
+ helpText: s__(
+ "WikiPage|This editor is in beta and may not display the page's contents properly.",
+ ),
+ },
+ 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: {
+ GlAlert,
GlForm,
GlSprintf,
GlIcon,
GlLink,
GlButton,
+ GlModal,
MarkdownField,
+ GlLoadingIcon,
+ ContentEditor: () =>
+ import(
+ /* webpackChunkName: 'content_editor' */ '~/content_editor/components/content_editor.vue'
+ ),
+ },
+ directives: {
+ GlModalDirective,
},
+ mixins: [glFeatureFlagMixin()],
inject: ['formatOptions', 'pageInfo'],
data() {
return {
title: this.pageInfo.title?.trim() || '',
format: this.pageInfo.format || 'markdown',
content: this.pageInfo.content?.trim() || '',
+ isContentEditorLoading: true,
+ useContentEditor: false,
commitMessage: '',
+ contentEditor: null,
+ isDirty: false,
+ contentEditorRenderFailed: false,
};
},
computed: {
@@ -45,15 +134,21 @@ export default {
},
commitMessageI18n() {
return this.pageInfo.persisted
- ? s__('WikiPage|Update %{pageTitle}')
- : s__('WikiPage|Create %{pageTitle}');
+ ? this.$options.i18n.commitMessage.value.existingPage
+ : this.$options.i18n.commitMessage.value.newPage;
},
linkExample() {
return MARKDOWN_LINK_TEXT[this.format];
},
submitButtonText() {
- if (this.pageInfo.persisted) return __('Save changes');
- return s__('WikiPage|Create page');
+ 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;
@@ -62,20 +157,53 @@ export default {
wikiSpecificMarkdownHelpPath() {
return setUrlFragment(this.pageInfo.markdownHelpPath, 'wiki-specific-markdown');
},
+ isMarkdownFormat() {
+ return this.format === 'markdown';
+ },
+ showContentEditorButton() {
+ return this.isMarkdownFormat && !this.useContentEditor && this.glFeatures.wikiContentEditor;
+ },
+ disableSubmitButton() {
+ return !this.content || !this.title || this.contentEditorRenderFailed;
+ },
+ isContentEditorActive() {
+ return this.isMarkdownFormat && this.useContentEditor;
+ },
},
mounted() {
this.updateCommitMessage();
+
+ window.addEventListener('beforeunload', this.onPageUnload);
+ },
+ destroyed() {
+ window.removeEventListener('beforeunload', this.onPageUnload);
},
methods: {
+ getContentHTML(content) {
+ return axios
+ .post(this.pageInfo.markdownPreviewPath, { text: content })
+ .then(({ data }) => data.body);
+ },
+
handleFormSubmit() {
- window.removeEventListener('beforeunload', this.onBeforeUnload);
+ if (this.useContentEditor) {
+ this.content = this.contentEditor.getSerializedContent();
+ }
+
+ this.isDirty = false;
},
handleContentChange() {
- window.addEventListener('beforeunload', this.onBeforeUnload);
+ this.isDirty = true;
},
- onBeforeUnload() {
+ onPageUnload(event) {
+ if (!this.isDirty) return undefined;
+
+ event.preventDefault();
+
+ // eslint-disable-next-line no-param-reassign
+ event.returnValue = '';
return '';
},
@@ -88,6 +216,48 @@ export default {
const newCommitMessage = sprintf(this.commitMessageI18n, { pageTitle: newTitle }, false);
this.commitMessage = newCommitMessage;
},
+
+ async initContentEditor() {
+ this.isContentEditorLoading = true;
+ this.useContentEditor = true;
+
+ const { createContentEditor } = await import(
+ /* webpackChunkName: 'content_editor' */ '~/content_editor/services/create_content_editor'
+ );
+ this.contentEditor =
+ this.contentEditor ||
+ createContentEditor({
+ renderMarkdown: (markdown) => this.getContentHTML(markdown),
+ tiptapOptions: {
+ onUpdate: () => this.handleContentChange(),
+ },
+ });
+
+ try {
+ await this.contentEditor.setSerializedContent(this.content);
+ this.isContentEditorLoading = false;
+ } catch (e) {
+ this.contentEditorRenderFailed = true;
+ }
+ },
+
+ retryInitContentEditor() {
+ this.contentEditorRenderFailed = false;
+ this.initContentEditor();
+ },
+
+ switchToOldEditor() {
+ this.useContentEditor = false;
+ },
+
+ confirmSwitchToOldEditor() {
+ if (this.contentEditorRenderFailed) {
+ this.contentEditorRenderFailed = false;
+ this.switchToOldEditor();
+ } else {
+ this.$refs.confirmSwitchToOldEditorModal.show();
+ }
+ },
},
};
</script>
@@ -99,6 +269,19 @@ export default {
class="wiki-form common-note-form gl-mt-3 js-quick-submit"
@submit="handleFormSubmit"
>
+ <gl-alert
+ v-if="isContentEditorActive && contentEditorRenderFailed"
+ class="gl-mb-6"
+ :dismissible="false"
+ variant="danger"
+ :primary-button-text="$options.i18n.contentEditor.renderFailed.primaryAction"
+ @primaryAction="retryInitContentEditor()"
+ >
+ <p>
+ {{ $options.i18n.contentEditor.renderFailed.message }}
+ </p>
+ </gl-alert>
+
<input :value="csrfToken" type="hidden" name="authenticity_token" />
<input v-if="pageInfo.persisted" type="hidden" name="_method" value="put" />
<input
@@ -109,7 +292,9 @@ export default {
/>
<div class="form-group row">
<div class="col-sm-2 col-form-label">
- <label class="control-label-full-width" for="wiki_title">{{ s__('WikiPage|Title') }}</label>
+ <label class="control-label-full-width" for="wiki_title">{{
+ $options.i18n.title.label
+ }}</label>
</div>
<div class="col-sm-10">
<input
@@ -121,22 +306,15 @@ export default {
data-qa-selector="wiki_title_textbox"
:required="true"
:autofocus="!pageInfo.persisted"
- :placeholder="s__('WikiPage|Page title')"
+ :placeholder="$options.i18n.title.placeholder"
@input="updateCommitMessage"
/>
<span class="gl-display-inline-block gl-max-w-full gl-mt-2 gl-text-gray-600">
<gl-icon class="gl-mr-n1" name="bulb" />
- {{
- pageInfo.persisted
- ? s__(
- 'WikiPage|Tip: You can move this page by adding the path to the beginning of the title.',
- )
- : s__(
- 'WikiPage|Tip: You can specify the full path for the new file. We will automatically create any missing directories.',
- )
- }}
- <gl-link :href="helpPath" target="_blank" data-testid="wiki-title-help-link"
- ><gl-icon name="question-o" /> {{ __('More Information.') }}</gl-link
+ {{ titleHelpText }}
+ <gl-link :href="helpPath" target="_blank"
+ ><gl-icon name="question-o" />
+ {{ $options.i18n.title.helpText.moreInformation }}</gl-link
>
</span>
</div>
@@ -144,25 +322,63 @@ export default {
<div class="form-group row">
<div class="col-sm-2 col-form-label">
<label class="control-label-full-width" for="wiki_format">{{
- s__('WikiPage|Format')
+ $options.i18n.format.label
}}</label>
</div>
<div class="col-sm-10">
- <select id="wiki_format" v-model="format" class="form-control" name="wiki[format]">
+ <select
+ id="wiki_format"
+ v-model="format"
+ class="form-control"
+ name="wiki[format]"
+ :disabled="isContentEditorActive"
+ >
<option v-for="(key, label) of formatOptions" :key="key" :value="key">
{{ label }}
</option>
</select>
+ <div>
+ <gl-button
+ v-if="showContentEditorButton"
+ category="secondary"
+ variant="confirm"
+ class="gl-mt-4"
+ @click="initContentEditor"
+ >{{ $options.i18n.contentEditor.useNewEditor }}</gl-button
+ >
+ <div v-if="isContentEditorActive" class="gl-mt-4 gl-display-flex">
+ <div class="gl-mr-4">
+ <gl-button category="secondary" variant="confirm" @click="confirmSwitchToOldEditor">{{
+ $options.i18n.contentEditor.switchToOldEditor.label
+ }}</gl-button>
+ </div>
+ <div class="gl-mt-2">
+ <gl-icon name="warning" />
+ {{ $options.i18n.contentEditor.switchToOldEditor.helpText }}
+ </div>
+ </div>
+ <gl-modal
+ ref="confirmSwitchToOldEditorModal"
+ modal-id="confirm-switch-to-old-editor"
+ :title="$options.i18n.contentEditor.switchToOldEditor.modal.title"
+ :action-primary="{ text: $options.i18n.contentEditor.switchToOldEditor.modal.primary }"
+ :action-cancel="{ text: $options.i18n.contentEditor.switchToOldEditor.modal.cancel }"
+ @primary="switchToOldEditor"
+ >
+ {{ $options.i18n.contentEditor.switchToOldEditor.modal.text }}
+ </gl-modal>
+ </div>
</div>
</div>
<div class="form-group row">
<div class="col-sm-2 col-form-label">
<label class="control-label-full-width" for="wiki_content">{{
- s__('WikiPage|Content')
+ $options.i18n.content.label
}}</label>
</div>
<div class="col-sm-10">
<markdown-field
+ v-if="!isContentEditorActive"
:markdown-preview-path="pageInfo.markdownPreviewPath"
:can-attach-file="true"
:enable-autocomplete="true"
@@ -182,24 +398,25 @@ export default {
data-supports-quick-actions="false"
data-qa-selector="wiki_content_textarea"
:autofocus="pageInfo.persisted"
- :aria-label="s__('WikiPage|Content')"
- :placeholder="s__('WikiPage|Write your content or drag files here…')"
+ :aria-label="$options.i18n.content.label"
+ :placeholder="$options.i18n.content.placeholder"
@input="handleContentChange"
>
</textarea>
</template>
</markdown-field>
+
+ <div v-if="isContentEditorActive">
+ <gl-loading-icon v-if="isContentEditorLoading" class="bordered-box gl-w-full gl-py-6" />
+ <content-editor v-else :content-editor="contentEditor" />
+ <input id="wiki_content" v-model.trim="content" type="hidden" name="wiki[content]" />
+ </div>
+
<div class="clearfix"></div>
<div class="error-alert"></div>
<div class="form-text gl-text-gray-600">
- <gl-sprintf
- :message="
- s__(
- 'WikiPage|To link to a (new) page, simply type %{linkExample}. More examples are in the %{linkStart}documentation%{linkEnd}.',
- )
- "
- >
+ <gl-sprintf v-if="!isContentEditorActive" :message="$options.i18n.linksHelpText">
<template #linkExample
><code>{{ linkExample }}</code></template
>
@@ -214,13 +431,16 @@ export default {
></template
>
</gl-sprintf>
+ <span v-else>
+ {{ $options.i18n.contentEditor.helpText }}
+ </span>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-sm-2 col-form-label">
<label class="control-label-full-width" for="wiki_message">{{
- s__('WikiPage|Commit message')
+ $options.i18n.commitMessage.label
}}</label>
</div>
<div class="col-sm-10">
@@ -231,7 +451,7 @@ export default {
type="text"
class="form-control"
data-qa-selector="wiki_message_textbox"
- :placeholder="s__('WikiPage|Commit message')"
+ :placeholder="$options.i18n.commitMessage.label"
/>
</div>
</div>
@@ -242,12 +462,10 @@ export default {
type="submit"
data-qa-selector="wiki_submit_button"
data-testid="wiki-submit-button"
- :disabled="!content || !title"
+ :disabled="disableSubmitButton"
>{{ submitButtonText }}</gl-button
>
- <gl-button :href="cancelFormPath" class="float-right" data-testid="wiki-cancel-button">{{
- __('Cancel')
- }}</gl-button>
+ <gl-button :href="cancelFormPath" class="float-right">{{ $options.i18n.cancel }}</gl-button>
</div>
</gl-form>
</template>