diff --git a/app/assets/javascripts/batch_comments/components/submit_dropdown.vue b/app/assets/javascripts/batch_comments/components/submit_dropdown.vue index 9005c1b12209ce555d2bd8bb5671d407473337de..6aa5bb715b2095a0e06d8d5038bc27bc8b4f2eca 100644 --- a/app/assets/javascripts/batch_comments/components/submit_dropdown.vue +++ b/app/assets/javascripts/batch_comments/components/submit_dropdown.vue @@ -117,9 +117,7 @@ export default { {{ __('Summary comment (optional)') }} </template> <div class="common-note-form gfm-form"> - <div - class="comment-warning-wrapper gl-border-solid gl-border-1 gl-rounded-base gl-border-gray-100" - > + <div class="comment-warning-wrapper-large gl-border-0 gl-bg-white"> <markdown-field :is-submitting="isSubmitting" :add-spacing-classes="false" diff --git a/app/assets/javascripts/behaviors/preview_markdown.js b/app/assets/javascripts/behaviors/preview_markdown.js index dc408f5a950857e57da879a3101a40aa72f854f4..bcd92d090338a3b1ec04fd4fba6ab108fd6abfd9 100644 --- a/app/assets/javascripts/behaviors/preview_markdown.js +++ b/app/assets/javascripts/behaviors/preview_markdown.js @@ -121,9 +121,7 @@ MarkdownPreview.prototype.renderReferencedCommands = function (commands, $form) const markdownPreview = new MarkdownPreview(); const previewButtonSelector = '.js-md-preview-button'; -const writeButtonSelector = '.js-md-write-button'; lastTextareaPreviewed = null; -const markdownToolbar = $('.md-header-toolbar'); $(document).on('markdown-preview:show', (e, $form) => { if (!$form) { @@ -134,13 +132,15 @@ $(document).on('markdown-preview:show', (e, $form) => { lastTextareaHeight = lastTextareaPreviewed.height(); // toggle tabs - $form.find(writeButtonSelector).parent().removeClass('active'); - $form.find(previewButtonSelector).parent().addClass('active'); + $form.find(previewButtonSelector).val('edit'); + $form.find(previewButtonSelector).children('span.gl-button-text').text(__('Continue editing')); + $form.find(previewButtonSelector).addClass('gl-shadow-none! gl-bg-transparent!'); // toggle content $form.find('.md-write-holder').hide(); $form.find('.md-preview-holder').show(); - markdownToolbar.removeClass('active'); + $form.find('.md-header-toolbar, .js-zen-enter').addClass('gl-display-none!'); + markdownPreview.showPreview($form); }); @@ -155,14 +155,14 @@ $(document).on('markdown-preview:hide', (e, $form) => { } // toggle tabs - $form.find(writeButtonSelector).parent().addClass('active'); - $form.find(previewButtonSelector).parent().removeClass('active'); + $form.find(previewButtonSelector).val('preview'); + $form.find(previewButtonSelector).children('span.gl-button-text').text(__('Preview')); // toggle content $form.find('.md-write-holder').show(); $form.find('textarea.markdown-area').focus(); $form.find('.md-preview-holder').hide(); - markdownToolbar.addClass('active'); + $form.find('.md-header-toolbar, .js-zen-enter').removeClass('gl-display-none!'); markdownPreview.hideReferencedCommands($form); }); @@ -183,13 +183,26 @@ $(document).on('markdown-preview:toggle', (e, keyboardEvent) => { $(document).on('click', previewButtonSelector, function (e) { e.preventDefault(); const $form = $(this).closest('form'); - $(document).triggerHandler('markdown-preview:show', [$form]); + const eventName = e.currentTarget.getAttribute('value') === 'preview' ? 'show' : 'hide'; + $(document).triggerHandler(`markdown-preview:${eventName}`, [$form]); +}); + +$(document).on('mousedown', previewButtonSelector, function (e) { + e.preventDefault(); + const $form = $(this).closest('form'); + $form.find(previewButtonSelector).removeClass('gl-shadow-none! gl-bg-transparent!'); +}); + +$(document).on('mouseenter', previewButtonSelector, function (e) { + e.preventDefault(); + const $form = $(this).closest('form'); + $form.find(previewButtonSelector).removeClass('gl-bg-transparent!'); }); -$(document).on('click', writeButtonSelector, function (e) { +$(document).on('mouseleave', previewButtonSelector, function (e) { e.preventDefault(); const $form = $(this).closest('form'); - $(document).triggerHandler('markdown-preview:hide', [$form]); + $form.find(previewButtonSelector).addClass('gl-bg-transparent!'); }); export default MarkdownPreview; diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js index 5c6e4665a5c1957d3f0c135b3cafd2517b0ec411..efb462f477805a32605779a09c34ae194c0c4053 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js @@ -186,13 +186,14 @@ export default class Shortcuts { } static toggleMarkdownPreview(e) { - // Check if short-cut was triggered while in Write Mode - const $target = $(e.target); - const $form = $target.closest('form'); + const $form = $(e.target).closest('form'); + const toggle = $('.js-md-preview-button', $form).get(0); + + if (!toggle) return; + + toggle.focus(); + toggle.click(); - if ($target.hasClass('js-note-text')) { - $('.js-md-preview-button', $form).focus(); - } $(document).triggerHandler('markdown-preview:toggle', [e]); } diff --git a/app/assets/javascripts/content_editor/components/content_editor.vue b/app/assets/javascripts/content_editor/components/content_editor.vue index f9d48708473f67c3ce208e7c0f532079386e4b83..2b2c4a5ac1c334753756a4e0e18618518dfc4c24 100644 --- a/app/assets/javascripts/content_editor/components/content_editor.vue +++ b/app/assets/javascripts/content_editor/components/content_editor.vue @@ -3,7 +3,6 @@ import { EditorContent as TiptapEditorContent } from '@tiptap/vue-2'; import { GlSprintf, GlLink } from '@gitlab/ui'; import { __, s__ } from '~/locale'; import { VARIANT_DANGER } from '~/alert'; -import EditorModeDropdown from '~/vue_shared/components/markdown/editor_mode_dropdown.vue'; import { createContentEditor } from '../services/create_content_editor'; import { ALERT_EVENT, TIPTAP_AUTOFOCUS_OPTIONS } from '../constants'; import ContentEditorAlert from './content_editor_alert.vue'; @@ -30,7 +29,6 @@ export default { LinkBubbleMenu, MediaBubbleMenu, EditorStateObserver, - EditorModeDropdown, }, props: { renderMarkdown: { @@ -100,6 +98,11 @@ export default { latestMarkdown: null, }; }, + computed: { + showPlaceholder() { + return this.placeholder && !this.markdown && !this.focused; + }, + }, watch: { markdown(markdown) { if (markdown !== this.latestMarkdown) { @@ -196,11 +199,6 @@ export default { markdown: this.latestMarkdown, }); }, - handleEditorModeChanged(mode) { - if (mode === 'markdown') { - this.$emit('enableMarkdownEditor'); - } - }, }, i18n: { quickActionsText: s__( @@ -226,34 +224,36 @@ export default { :class="{ 'is-focused': focused }" > <formatting-toolbar ref="toolbar" @enableMarkdownEditor="$emit('enableMarkdownEditor')" /> - <div class="gl-relative gl-mt-4"> + <div class="gl-relative"> <formatting-bubble-menu /> <code-block-bubble-menu /> <link-bubble-menu /> <media-bubble-menu /> - <div v-if="placeholder && !markdown && !focused" class="gl-absolute gl-text-gray-400"> + <div v-if="showPlaceholder" class="gl-absolute gl-text-gray-400 gl-px-5 gl-pt-4"> {{ placeholder }} </div> <tiptap-editor-content - class="md" + class="md gl-px-5" data-testid="content_editor_editablebox" :editor="contentEditor.tiptapEditor" /> <loading-indicator v-if="isLoading" /> - <div class="gl-display-flex gl-border-t gl-py-2 gl-text-secondary"> - <div class="gl-w-full"> - <template v-if="quickActionsDocsPath"> - <gl-sprintf :message="$options.i18n.quickActionsText"> - <template #keyboard="{ content }"> - <kbd>{{ content }}</kbd> - </template> - <template #quickActionsDocsLink="{ content }"> - <gl-link :href="quickActionsDocsPath" target="_blank">{{ content }}</gl-link> - </template> - </gl-sprintf> - </template> + <div + v-if="quickActionsDocsPath" + class="gl-display-flex gl-align-items-center gl-rounded-bottom-left-base gl-rounded-bottom-right-base gl-px-4 gl-mx-2 gl-mb-2 gl-bg-gray-10 gl-text-secondary" + > + <div class="gl-w-full gl-line-height-32 gl-font-sm"> + <gl-sprintf :message="$options.i18n.quickActionsText"> + <template #keyboard="{ content }"> + <kbd>{{ content }}</kbd> + </template> + <template #quickActionsDocsLink="{ content }"> + <gl-link :href="quickActionsDocsPath" target="_blank" class="gl-font-sm">{{ + content + }}</gl-link> + </template> + </gl-sprintf> </div> - <editor-mode-dropdown size="small" value="richText" @input="handleEditorModeChanged" /> </div> </div> </div> diff --git a/app/assets/javascripts/content_editor/components/formatting_toolbar.vue b/app/assets/javascripts/content_editor/components/formatting_toolbar.vue index cd9fdeeca465b65ea0e3d472f5ae7989459c3d46..e7e520a55daf75f023437d1fc8ffa6dde76bb485 100644 --- a/app/assets/javascripts/content_editor/components/formatting_toolbar.vue +++ b/app/assets/javascripts/content_editor/components/formatting_toolbar.vue @@ -1,4 +1,5 @@ <script> +import EditorModeSwitcher from '~/vue_shared/components/markdown/editor_mode_switcher.vue'; import trackUIControl from '../services/track_ui_control'; import ToolbarButton from './toolbar_button.vue'; import ToolbarAttachmentButton from './toolbar_attachment_button.vue'; @@ -13,94 +14,106 @@ export default { ToolbarTableButton, ToolbarAttachmentButton, ToolbarMoreDropdown, + EditorModeSwitcher, }, methods: { trackToolbarControlExecution({ contentType, value }) { trackUIControl({ property: contentType, value }); }, + handleEditorModeChanged() { + this.$emit('enableMarkdownEditor'); + }, }, }; </script> <template> - <div - class="gl-w-full gl-border-b gl-display-flex gl-justify-content-end" - data-testid="formatting-toolbar" - > - <div class="gl-py-2 gl-display-flex gl-flex-wrap gl-align-items-end"> - <toolbar-text-style-dropdown - data-testid="text-styles" - @execute="trackToolbarControlExecution" - /> - <toolbar-button - data-testid="bold" - content-type="bold" - icon-name="bold" - editor-command="toggleBold" - :label="__('Bold text')" - @execute="trackToolbarControlExecution" - /> - <toolbar-button - data-testid="italic" - content-type="italic" - icon-name="italic" - editor-command="toggleItalic" - :label="__('Italic text')" - @execute="trackToolbarControlExecution" - /> - <toolbar-button - data-testid="blockquote" - content-type="blockquote" - icon-name="quote" - editor-command="toggleBlockquote" - :label="__('Insert a quote')" - @execute="trackToolbarControlExecution" - /> - <toolbar-button - data-testid="code" - content-type="code" - icon-name="code" - editor-command="toggleCode" - :label="__('Code')" - @execute="trackToolbarControlExecution" - /> - <toolbar-button - data-testid="link" - content-type="link" - icon-name="link" - editor-command="editLink" - :label="__('Insert link')" - @execute="trackToolbarControlExecution" - /> - <toolbar-button - data-testid="bullet-list" - content-type="bulletList" - icon-name="list-bulleted" - class="gl-display-none gl-sm-display-inline" - editor-command="toggleBulletList" - :label="__('Add a bullet list')" - @execute="trackToolbarControlExecution" - /> - <toolbar-button - data-testid="ordered-list" - content-type="orderedList" - icon-name="list-numbered" - class="gl-display-none gl-sm-display-inline" - editor-command="toggleOrderedList" - :label="__('Add a numbered list')" - @execute="trackToolbarControlExecution" - /> - <toolbar-button - data-testid="task-list" - content-type="taskList" - icon-name="list-task" - class="gl-display-none gl-sm-display-inline" - editor-command="toggleTaskList" - :label="__('Add a checklist')" - @execute="trackToolbarControlExecution" - /> - <toolbar-table-button data-testid="table" @execute="trackToolbarControlExecution" /> - <toolbar-attachment-button data-testid="attachment" @execute="trackToolbarControlExecution" /> - <toolbar-more-dropdown data-testid="more" @execute="trackToolbarControlExecution" /> + <div class="gl-mx-2 gl-mt-2"> + <div + class="gl-w-full gl-display-flex gl-align-items-center gl-flex-wrap gl-bg-gray-50 gl-px-2 gl-rounded-base gl-justify-content-space-between" + data-testid="formatting-toolbar" + > + <div class="gl-py-2 gl-display-flex gl-flex-wrap-wrap"> + <toolbar-text-style-dropdown + data-testid="text-styles" + @execute="trackToolbarControlExecution" + /> + <toolbar-button + data-testid="bold" + content-type="bold" + icon-name="bold" + editor-command="toggleBold" + :label="__('Bold text')" + @execute="trackToolbarControlExecution" + /> + <toolbar-button + data-testid="italic" + content-type="italic" + icon-name="italic" + editor-command="toggleItalic" + :label="__('Italic text')" + @execute="trackToolbarControlExecution" + /> + <toolbar-button + data-testid="blockquote" + content-type="blockquote" + icon-name="quote" + editor-command="toggleBlockquote" + :label="__('Insert a quote')" + @execute="trackToolbarControlExecution" + /> + <toolbar-button + data-testid="code" + content-type="code" + icon-name="code" + editor-command="toggleCode" + :label="__('Code')" + @execute="trackToolbarControlExecution" + /> + <toolbar-button + data-testid="link" + content-type="link" + icon-name="link" + editor-command="editLink" + :label="__('Insert link')" + @execute="trackToolbarControlExecution" + /> + <toolbar-button + data-testid="bullet-list" + content-type="bulletList" + icon-name="list-bulleted" + class="gl-display-none gl-sm-display-inline" + editor-command="toggleBulletList" + :label="__('Add a bullet list')" + @execute="trackToolbarControlExecution" + /> + <toolbar-button + data-testid="ordered-list" + content-type="orderedList" + icon-name="list-numbered" + class="gl-display-none gl-sm-display-inline" + editor-command="toggleOrderedList" + :label="__('Add a numbered list')" + @execute="trackToolbarControlExecution" + /> + <toolbar-button + data-testid="task-list" + content-type="taskList" + icon-name="list-task" + class="gl-display-none gl-sm-display-inline" + editor-command="toggleTaskList" + :label="__('Add a checklist')" + @execute="trackToolbarControlExecution" + /> + <toolbar-table-button data-testid="table" @execute="trackToolbarControlExecution" /> + <toolbar-attachment-button + data-testid="attachment" + @execute="trackToolbarControlExecution" + /> + <toolbar-more-dropdown data-testid="more" @execute="trackToolbarControlExecution" /> + </div> + <div class="content-editor-switcher gl-display-flex gl-align-items-center gl-ml-auto"> + <editor-mode-switcher size="small" value="richText" @input="handleEditorModeChanged" /> + </div> </div> </div> </template> diff --git a/app/assets/javascripts/content_editor/components/toolbar_attachment_button.vue b/app/assets/javascripts/content_editor/components/toolbar_attachment_button.vue index efb9a5b07b542de0089f74d8b0ad4f8647d88e06..1f18090e7d74c0151abe61fcaf7eda83cba9e57c 100644 --- a/app/assets/javascripts/content_editor/components/toolbar_attachment_button.vue +++ b/app/assets/javascripts/content_editor/components/toolbar_attachment_button.vue @@ -46,6 +46,7 @@ export default { :title="__('Attach a file or image')" category="tertiary" icon="paperclip" + size="small" lazy @click="openFileUpload" /> diff --git a/app/assets/javascripts/content_editor/components/toolbar_button.vue b/app/assets/javascripts/content_editor/components/toolbar_button.vue index cef026c5bc6dd5b25e643e85b42c3c7ce5384d71..1f3c7062b67665021462e56bc311af4107fe6e8f 100644 --- a/app/assets/javascripts/content_editor/components/toolbar_button.vue +++ b/app/assets/javascripts/content_editor/components/toolbar_button.vue @@ -47,7 +47,7 @@ export default { size: { type: String, required: false, - default: 'medium', + default: 'small', }, }, data() { @@ -82,6 +82,7 @@ export default { :aria-label="label" :title="label" :icon="iconName" + class="gl-mr-3" @click="execute" /> </editor-state-observer> diff --git a/app/assets/javascripts/deprecated_notes.js b/app/assets/javascripts/deprecated_notes.js index 537c810bcffddeffd79b2c0b4262655c66203f6e..a46a8d4affaca755f9d88b47ff610a41bbc16a8c 100644 --- a/app/assets/javascripts/deprecated_notes.js +++ b/app/assets/javascripts/deprecated_notes.js @@ -599,7 +599,10 @@ export default class Notes { // remove validation errors form.find('.js-errors').remove(); // reset text and preview - form.find('.js-md-write-button').click(); + if (form.find('.js-md-preview-button').val() === 'edit') { + form.find('.js-md-preview-button').click(); + } + form.find('.js-note-text').val('').trigger('input'); form.find('.js-note-text').each(function reset() { this.$autosave.reset(); @@ -939,6 +942,7 @@ export default class Notes { const replyLink = $(target).closest('.js-discussion-reply-button'); // insert the form after the button replyLink.closest('.discussion-reply-holder').hide().after(form); + // show the form return this.setupDiscussionNoteForm(replyLink, form); } @@ -1241,7 +1245,10 @@ export default class Notes { $editForm.find('.js-form-target-id').val(targetId); $editForm.find('.js-form-target-type').val(targetType); $editForm.find('.js-note-text').focus().val(originalContent); - $editForm.find('.js-md-write-button').trigger('click'); + // reset preview + if ($editForm.find('.js-md-preview-button').val() === 'edit') { + $editForm.find('.js-md-preview-button').click(); + } $editForm.find('.referenced-users').hide(); } diff --git a/app/assets/javascripts/notes/components/comment_field_layout.vue b/app/assets/javascripts/notes/components/comment_field_layout.vue index 02d128eb119ee081432ab1442a5d741d54d96766..cfe4baaa1f93dc593b39be3e801eb3bb72ef2be3 100644 --- a/app/assets/javascripts/notes/components/comment_field_layout.vue +++ b/app/assets/javascripts/notes/components/comment_field_layout.vue @@ -67,7 +67,7 @@ export default { </script> <template> <div - class="comment-warning-wrapper gl-border-solid gl-border-1 gl-rounded-base gl-border-gray-100" + class="comment-warning-wrapper gl-border-solid gl-border-1 gl-rounded-lg gl-border-gray-100 gl-bg-white" > <div v-if="withAlertContainer" diff --git a/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue b/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue index 1dd07fe90d2b493563af8bbcdfaa220232289811..a0d2b47c89c4a7faa4c0c5049370ea636419a861 100644 --- a/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue +++ b/app/assets/javascripts/notes/components/discussion_notes_replies_wrapper.vue @@ -21,7 +21,7 @@ export default { 'li', { class: - 'discussion-collapsible gl-border-solid gl-border-gray-100 gl-border-1 gl-rounded-base clearfix', + 'discussion-collapsible gl-border-solid gl-border-gray-100 gl-border-1 gl-rounded-base clearfix gl-pt-5', }, [h('ul', { class: 'notes' }, children)], ); diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index 3375e366ecf0be80d917dcdef0ebed6c54069e25..375b16f6ce2c52d89b3d24d5ae03c8825ed391cf 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -319,7 +319,7 @@ export default { /> <li v-else-if="canShowReplyActions && showReplies" - :class="{ 'is-replying': isReplying }" + :class="{ 'is-replying gl-bg-white! gl-pt-0!': isReplying }" class="discussion-reply-holder gl-border-t-0! clearfix" > <discussion-actions diff --git a/app/assets/javascripts/vue_shared/components/markdown/comment_templates_dropdown.vue b/app/assets/javascripts/vue_shared/components/markdown/comment_templates_dropdown.vue index feee132629fdbf1a82c6138e42649dd9122ec811..1377a40fcf066d73ede9da9aa2b761c8c1e42669 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/comment_templates_dropdown.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/comment_templates_dropdown.vue @@ -86,6 +86,7 @@ export default { category="tertiary" placement="right" searchable + size="small" class="comment-template-dropdown" :searching="$apollo.queries.savedReplies.loading" @shown="fetchCommentTemplates" diff --git a/app/assets/javascripts/vue_shared/components/markdown/drawio_toolbar_button.vue b/app/assets/javascripts/vue_shared/components/markdown/drawio_toolbar_button.vue index a66becb5c927eab99579f7a784768f2e9dcadbff..e88c7f75745628f4bd5c5c6176b1a1b499aeff59 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/drawio_toolbar_button.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/drawio_toolbar_button.vue @@ -43,6 +43,7 @@ export default { :aria-label="__('Insert or edit diagram')" category="tertiary" icon="diagram" + size="small" @click="launchDrawioEditor" /> </template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/editor_mode_dropdown.vue b/app/assets/javascripts/vue_shared/components/markdown/editor_mode_dropdown.vue deleted file mode 100644 index 7803d6f53e0aa793f34b57f1b97a386a425e6c7a..0000000000000000000000000000000000000000 --- a/app/assets/javascripts/vue_shared/components/markdown/editor_mode_dropdown.vue +++ /dev/null @@ -1,58 +0,0 @@ -<script> -import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; -import { __ } from '~/locale'; - -export default { - components: { - GlDropdown, - GlDropdownItem, - }, - props: { - size: { - type: String, - required: false, - default: 'medium', - }, - value: { - type: String, - required: true, - }, - }, - computed: { - markdownEditorSelected() { - return this.value === 'markdown'; - }, - text() { - return this.markdownEditorSelected ? __('Editing markdown') : __('Editing rich text'); - }, - }, -}; -</script> -<template> - <gl-dropdown - category="tertiary" - data-qa-selector="editing_mode_switcher" - :size="size" - :text="text" - right - > - <gl-dropdown-item - is-check-item - :is-checked="!markdownEditorSelected" - @click="$emit('input', 'richText')" - ><div class="gl-font-weight-bold">{{ __('Rich text') }}</div> - <div class="gl-text-secondary"> - {{ __('View the formatted output in real-time as you edit.') }} - </div> - </gl-dropdown-item> - <gl-dropdown-item - is-check-item - :is-checked="markdownEditorSelected" - @click="$emit('input', 'markdown')" - ><div class="gl-font-weight-bold">{{ __('Markdown') }}</div> - <div class="gl-text-secondary"> - {{ __('View and edit markdown, with the option to preview the formatted output.') }} - </div></gl-dropdown-item - > - </gl-dropdown> -</template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue b/app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue new file mode 100644 index 0000000000000000000000000000000000000000..645975ca565edbfb311cc975abcda74ca2c44c4e --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue @@ -0,0 +1,32 @@ +<script> +import { GlButton } from '@gitlab/ui'; +import { __ } from '~/locale'; + +export default { + components: { + GlButton, + }, + props: { + value: { + type: String, + required: true, + }, + }, + computed: { + markdownEditorSelected() { + return this.value === 'markdown'; + }, + text() { + return this.markdownEditorSelected ? __('Switch to rich text') : __('Switch to Markdown'); + }, + }, +}; +</script> +<template> + <gl-button + class="btn btn-default btn-sm gl-button btn-default-tertiary" + data-qa-selector="editing_mode_switcher" + @click="$emit('input')" + >{{ text }}</gl-button + > +</template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index cc1537477656a7e067948645211ac85409bb9b2d..3c4070105d1f742016a929dbbc8daed45ae188c5 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -1,5 +1,5 @@ <script> -import { GlIcon } from '@gitlab/ui'; +import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; import $ from 'jquery'; import { debounce, unescape } from 'lodash'; import { createAlert } from '~/alert'; @@ -27,6 +27,7 @@ export default { }, directives: { SafeHtml, + GlTooltip: GlTooltipDirective, }, mixins: [glFeatureFlagsMixin()], props: { @@ -245,7 +246,7 @@ export default { immediate: true, handler(newVal) { if (!newVal) { - this.showWriteTab(); + this.hidePreview(); } }, }, @@ -277,7 +278,7 @@ export default { } }, methods: { - showPreviewTab() { + showPreview() { if (this.previewMarkdown) return; this.previewMarkdown = true; @@ -297,7 +298,7 @@ export default { this.renderMarkdown(); } }, - showWriteTab() { + hidePreview() { this.markdownPreview = ''; this.previewMarkdown = false; }, @@ -365,9 +366,11 @@ export default { :drawio-enabled="drawioEnabled" data-testid="markdownHeader" :restricted-tool-bar-items="restrictedToolBarItems" - @preview-markdown="showPreviewTab" - @write-markdown="showWriteTab" + :show-content-editor-switcher="showContentEditorSwitcher" + @showPreview="showPreview" + @hidePreview="hidePreview" @handleSuggestDismissed="() => $emit('handleSuggestDismissed')" + @enableContentEditor="$emit('enableContentEditor')" /> <div v-show="!previewMarkdown" class="md-write-holder"> <div class="zen-backdrop"> @@ -384,8 +387,6 @@ export default { :quick-actions-docs-path="quickActionsDocsPath" :can-attach-file="canAttachFile" :show-comment-tool-bar="showCommentToolBar" - :show-content-editor-switcher="showContentEditorSwitcher" - @enableContentEditor="$emit('enableContentEditor')" /> </div> </div> @@ -393,7 +394,7 @@ export default { <div v-show="previewMarkdown" ref="markdown-preview" - class="js-vue-md-preview md-preview-holder" + class="js-vue-md-preview md-preview-holder gl-px-5" > <suggestions v-if="hasSuggestion" @@ -410,13 +411,13 @@ export default { v-show="previewMarkdown" ref="markdown-preview" v-safe-html:[$options.safeHtmlConfig]="markdownPreview" - class="js-vue-md-preview md md-preview-holder" + class="js-vue-md-preview md md-preview-holder gl-px-5" ></div> </template> <div v-if="referencedCommands && previewMarkdown && !markdownPreviewLoading" v-safe-html:[$options.safeHtmlConfig]="referencedCommands" - class="referenced-commands gl-mx-n5" + class="referenced-commands gl-mx-2 gl-mb-2 gl-px-4 gl-rounded-bottom-left-base gl-rounded-bottom-right-base" data-testid="referenced-commands" ></div> <div v-if="shouldShowReferencedUsers" class="referenced-users"> diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index 53a3913ebda0d3887af127d495a83ef349023915..17d9a2daf0bfa14bf7f7a46c19eba168784a06db 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -1,5 +1,5 @@ <script> -import { GlPopover, GlButton, GlTooltipDirective, GlTabs, GlTab } from '@gitlab/ui'; +import { GlPopover, GlButton, GlTooltipDirective } from '@gitlab/ui'; import $ from 'jquery'; import { keysFor, @@ -19,17 +19,17 @@ import { updateText } from '~/lib/utils/text_markdown'; import ToolbarButton from './toolbar_button.vue'; import DrawioToolbarButton from './drawio_toolbar_button.vue'; import CommentTemplatesDropdown from './comment_templates_dropdown.vue'; +import EditorModeSwitcher from './editor_mode_switcher.vue'; export default { components: { ToolbarButton, GlPopover, GlButton, - GlTabs, - GlTab, DrawioToolbarButton, CommentTemplatesDropdown, AiActionsDropdown: () => import('ee_component/ai/components/ai_actions_dropdown.vue'), + EditorModeSwitcher, }, directives: { GlTooltip: GlTooltipDirective, @@ -91,6 +91,11 @@ export default { required: false, default: false, }, + showContentEditorSwitcher: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -121,6 +126,9 @@ export default { const expandText = s__('MarkdownEditor|Click to expand'); return [`<details><summary>${expandText}</summary>`, `{text}`, '</details>'].join('\n'); }, + showEditorModeSwitcher() { + return this.showContentEditorSwitcher && !this.previewMarkdown; + }, }, watch: { showSuggestPopover() { @@ -128,14 +136,14 @@ export default { }, }, mounted() { - $(document).on('markdown-preview:show.vue', this.previewMarkdownTab); - $(document).on('markdown-preview:hide.vue', this.writeMarkdownTab); + $(document).on('markdown-preview:show.vue', this.showMarkdownPreview); + $(document).on('markdown-preview:hide.vue', this.hideMarkdownPreview); this.updateSuggestPopoverVisibility(); }, beforeDestroy() { - $(document).off('markdown-preview:show.vue', this.previewMarkdownTab); - $(document).off('markdown-preview:hide.vue', this.writeMarkdownTab); + $(document).off('markdown-preview:show.vue', this.showMarkdownPreview); + $(document).off('markdown-preview:hide.vue', this.hideMarkdownPreview); }, methods: { async updateSuggestPopoverVisibility() { @@ -149,19 +157,15 @@ export default { (form.find('.js-vue-markdown-field').length && $(this.$el).closest('form')[0] === form[0]) ); }, - - previewMarkdownTab(event, form) { - if (event.target.blur) event.target.blur(); + showMarkdownPreview(_, form) { if (!this.isValid(form)) return; - this.$emit('preview-markdown'); + this.$emit('showPreview'); }, - - writeMarkdownTab(event, form) { - if (event.target.blur) event.target.blur(); + hideMarkdownPreview(_, form) { if (!this.isValid(form)) return; - this.$emit('write-markdown'); + this.$emit('hidePreview'); }, handleSuggestDismissed() { this.$emit('handleSuggestDismissed'); @@ -204,6 +208,16 @@ export default { }); } }, + handleEditorModeChanged() { + this.$emit('enableContentEditor'); + }, + switchPreview() { + if (this.previewMarkdown) { + this.hideMarkdownPreview(); + } else { + this.showMarkdownPreview(); + } + }, }, shortcuts: { bold: keysFor(BOLD_TEXT), @@ -214,225 +228,240 @@ export default { outdent: keysFor(OUTDENT_LINE), }, i18n: { - writeTabTitle: __('Write'), - previewTabTitle: __('Preview'), + preview: __('Preview'), + hidePreview: __('Continue editing'), }, }; </script> <template> - <div class="md-header"> - <gl-tabs content-class="gl-display-none"> - <gl-tab - title-link-class="gl-py-4 gl-px-3 js-md-write-button" - :title="$options.i18n.writeTabTitle" - :active="!previewMarkdown" - data-testid="write-tab" - @click="writeMarkdownTab($event)" - /> - <gl-tab - v-if="enablePreview" - title-link-class="gl-py-4 gl-px-3 js-md-preview-button" - :title="$options.i18n.previewTabTitle" - :active="previewMarkdown" - data-testid="preview-tab" - @click="previewMarkdownTab($event)" - /> - - <template #tabs-end> - <div - data-testid="md-header-toolbar" - :class="{ 'gl-display-none!': previewMarkdown }" - class="md-header-toolbar gl-ml-auto gl-py-2 gl-justify-content-center" - > - <template v-if="canSuggest"> - <toolbar-button - ref="suggestButton" - :tag="mdSuggestion" - :prepend="true" - :button-title="__('Insert suggestion')" - :cursor-offset="4" - :tag-content="lineContent" - icon="doc-code" - data-qa-selector="suggestion_button" - class="js-suggestion-btn" - @click="handleSuggestDismissed" - /> - <gl-popover - v-if="suggestPopoverVisible" - :target="$refs.suggestButton.$el" - :css-classes="['diff-suggest-popover']" - placement="bottom" - :show="suggestPopoverVisible" - > - <strong>{{ __('New! Suggest changes directly') }}</strong> - <p class="mb-2"> - {{ - __( - 'Suggest code changes which can be immediately applied in one click. Try it out!', - ) - }} - </p> - <gl-button - variant="confirm" - category="primary" - size="small" - data-qa-selector="dismiss_suggestion_popover_button" - @click="handleSuggestDismissed" - > - {{ __('Got it') }} - </gl-button> - </gl-popover> - </template> - <ai-actions-dropdown - v-if="editorAiActions.length" - :actions="editorAiActions" - @input="insertIntoTextarea" - /> - <toolbar-button - tag="**" - :button-title=" - /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ - sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), { - modifierKey, - }) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */ - " - :shortcuts="$options.shortcuts.bold" - icon="bold" - /> - <toolbar-button - tag="_" - :button-title=" - /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ - sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), { - modifierKey, - }) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */ - " - :shortcuts="$options.shortcuts.italic" - icon="italic" - /> - <toolbar-button - v-if="!restrictedToolBarItems.includes('strikethrough')" - tag="~~" - :button-title=" - /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ - sprintf(s__('MarkdownEditor|Add strikethrough text (%{modifierKey}⇧X)'), { - modifierKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */, - }) - " - :shortcuts="$options.shortcuts.strikethrough" - icon="strikethrough" - /> - <toolbar-button - v-if="!restrictedToolBarItems.includes('quote')" - :prepend="true" - :tag="tag" - :button-title="__('Insert a quote')" - icon="quote" - @click="handleQuote" - /> - <toolbar-button tag="`" tag-block="```" :button-title="__('Insert code')" icon="code" /> - <toolbar-button - tag="[{text}](url)" - tag-select="url" - :button-title=" - /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ - sprintf(s__('MarkdownEditor|Add a link (%{modifierKey}K)'), { - modifierKey, - }) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */ - " - :shortcuts="$options.shortcuts.link" - icon="link" - /> + <div class="md-header gl-bg-gray-50 gl-px-2 gl-rounded-base gl-mx-2 gl-mt-2"> + <div + class="gl-display-flex gl-align-items-center gl-flex-wrap" + :class="{ + 'gl-justify-content-end': previewMarkdown, + 'gl-justify-content-space-between': !previewMarkdown, + }" + > + <div + data-testid="md-header-toolbar" + class="md-header-toolbar gl-display-flex gl-py-2 gl-flex-wrap" + :class="{ 'gl-display-none!': previewMarkdown }" + > + <template v-if="canSuggest"> <toolbar-button - v-if="!restrictedToolBarItems.includes('bullet-list')" + ref="suggestButton" + :tag="mdSuggestion" :prepend="true" - tag="- " - :button-title="__('Add a bullet list')" - icon="list-bulleted" + :button-title="__('Insert suggestion')" + :cursor-offset="4" + :tag-content="lineContent" + icon="doc-code" + data-qa-selector="suggestion_button" + class="js-suggestion-btn" + @click="handleSuggestDismissed" /> - <toolbar-button - v-if="!restrictedToolBarItems.includes('numbered-list')" - :prepend="true" - tag="1. " - :button-title="__('Add a numbered list')" - icon="list-numbered" - /> - <toolbar-button - v-if="!restrictedToolBarItems.includes('task-list')" - :prepend="true" - tag="- [ ] " - :button-title="__('Add a checklist')" - icon="list-task" - /> - <toolbar-button - v-if="!restrictedToolBarItems.includes('indent')" - class="gl-display-none" - :button-title=" - /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ - sprintf(s__('MarkdownEditor|Indent line (%{modifierKey}])'), { - modifierKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */, - }) - " - :shortcuts="$options.shortcuts.indent" - command="indentLines" - icon="list-indent" - /> - <toolbar-button - v-if="!restrictedToolBarItems.includes('outdent')" - class="gl-display-none" - :button-title=" - /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ - sprintf(s__('MarkdownEditor|Outdent line (%{modifierKey}[)'), { - modifierKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */, - }) - " - :shortcuts="$options.shortcuts.outdent" - command="outdentLines" - icon="list-outdent" - /> - <toolbar-button - v-if="!restrictedToolBarItems.includes('collapsible-section')" - :tag="mdCollapsibleSection" - :prepend="true" - tag-select="Click to expand" - :button-title="__('Add a collapsible section')" - icon="details-block" - /> - <toolbar-button - v-if="!restrictedToolBarItems.includes('table')" - :tag="mdTable" - :prepend="true" - :button-title="__('Add a table')" - icon="table" - /> - <gl-button - v-if="!restrictedToolBarItems.includes('attach-file')" - v-gl-tooltip - :title="__('Attach a file or image')" - data-testid="button-attach-file" - category="tertiary" - icon="paperclip" - @click="handleAttachFile" - /> - <drawio-toolbar-button - v-if="drawioEnabled" - :uploads-path="uploadsPath" - :markdown-preview-path="markdownPreviewPath" - /> - <comment-templates-dropdown - v-if="newCommentTemplatePath && glFeatures.savedReplies" - :new-comment-template-path="newCommentTemplatePath" - /> - <toolbar-button - v-if="!restrictedToolBarItems.includes('full-screen')" - class="js-zen-enter" - :prepend="true" - :button-title="__('Go full screen')" - icon="maximize" - /> - </div> - </template> - </gl-tabs> + <gl-popover + v-if="suggestPopoverVisible" + :target="$refs.suggestButton.$el" + :css-classes="['diff-suggest-popover']" + placement="bottom" + :show="suggestPopoverVisible" + > + <strong>{{ __('New! Suggest changes directly') }}</strong> + <p class="mb-2"> + {{ + __( + 'Suggest code changes which can be immediately applied in one click. Try it out!', + ) + }} + </p> + <gl-button + variant="confirm" + category="primary" + size="small" + data-qa-selector="dismiss_suggestion_popover_button" + @click="handleSuggestDismissed" + > + {{ __('Got it') }} + </gl-button> + </gl-popover> + </template> + <ai-actions-dropdown + v-if="editorAiActions.length" + :actions="editorAiActions" + @input="insertIntoTextarea" + /> + <toolbar-button + tag="**" + :button-title=" + /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ + sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), { + modifierKey, + }) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */ + " + :shortcuts="$options.shortcuts.bold" + icon="bold" + /> + <toolbar-button + tag="_" + :button-title=" + /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ + sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), { + modifierKey, + }) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */ + " + :shortcuts="$options.shortcuts.italic" + icon="italic" + /> + <toolbar-button + v-if="!restrictedToolBarItems.includes('strikethrough')" + tag="~~" + :button-title=" + /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ + sprintf(s__('MarkdownEditor|Add strikethrough text (%{modifierKey}⇧X)'), { + modifierKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */, + }) + " + :shortcuts="$options.shortcuts.strikethrough" + icon="strikethrough" + /> + <toolbar-button + v-if="!restrictedToolBarItems.includes('quote')" + :prepend="true" + :tag="tag" + :button-title="__('Insert a quote')" + icon="quote" + @click="handleQuote" + /> + <toolbar-button tag="`" tag-block="```" :button-title="__('Insert code')" icon="code" /> + <toolbar-button + tag="[{text}](url)" + tag-select="url" + :button-title=" + /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ + sprintf(s__('MarkdownEditor|Add a link (%{modifierKey}K)'), { + modifierKey, + }) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */ + " + :shortcuts="$options.shortcuts.link" + icon="link" + /> + <toolbar-button + v-if="!restrictedToolBarItems.includes('bullet-list')" + :prepend="true" + tag="- " + :button-title="__('Add a bullet list')" + icon="list-bulleted" + /> + <toolbar-button + v-if="!restrictedToolBarItems.includes('numbered-list')" + :prepend="true" + tag="1. " + :button-title="__('Add a numbered list')" + icon="list-numbered" + /> + <toolbar-button + v-if="!restrictedToolBarItems.includes('task-list')" + :prepend="true" + tag="- [ ] " + :button-title="__('Add a checklist')" + icon="list-task" + /> + <toolbar-button + v-if="!restrictedToolBarItems.includes('indent')" + class="gl-display-none" + :button-title=" + /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ + sprintf(s__('MarkdownEditor|Indent line (%{modifierKey}])'), { + modifierKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */, + }) + " + :shortcuts="$options.shortcuts.indent" + command="indentLines" + icon="list-indent" + /> + <toolbar-button + v-if="!restrictedToolBarItems.includes('outdent')" + class="gl-display-none" + :button-title=" + /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ + sprintf(s__('MarkdownEditor|Outdent line (%{modifierKey}[)'), { + modifierKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */, + }) + " + :shortcuts="$options.shortcuts.outdent" + command="outdentLines" + icon="list-outdent" + /> + <toolbar-button + v-if="!restrictedToolBarItems.includes('collapsible-section')" + :tag="mdCollapsibleSection" + :prepend="true" + tag-select="Click to expand" + :button-title="__('Add a collapsible section')" + icon="details-block" + /> + <toolbar-button + v-if="!restrictedToolBarItems.includes('table')" + :tag="mdTable" + :prepend="true" + :button-title="__('Add a table')" + icon="table" + /> + <gl-button + v-if="!restrictedToolBarItems.includes('attach-file')" + v-gl-tooltip + :title="__('Attach a file or image')" + class="gl-mr-2" + data-testid="button-attach-file" + category="tertiary" + icon="paperclip" + size="small" + @click="handleAttachFile" + /> + <drawio-toolbar-button + v-if="drawioEnabled" + :uploads-path="uploadsPath" + :markdown-preview-path="markdownPreviewPath" + /> + <comment-templates-dropdown + v-if="newCommentTemplatePath && glFeatures.savedReplies" + :new-comment-template-path="newCommentTemplatePath" + /> + </div> + <div class="switch-preview gl-py-2 gl-display-flex gl-align-items-center gl-ml-auto"> + <editor-mode-switcher + v-if="showEditorModeSwitcher" + size="small" + class="gl-mr-2" + value="markdown" + @input="handleEditorModeChanged" + /> + <gl-button + v-if="enablePreview" + data-testid="preview-toggle" + value="preview" + :label="$options.i18n.previewTabTitle" + class="js-md-preview-button gl-flex-direction-row-reverse gl-align-items-center gl-font-weight-normal!" + size="small" + category="tertiary" + @click="switchPreview" + >{{ previewMarkdown ? $options.i18n.hidePreview : $options.i18n.preview }}</gl-button + > + <gl-button + v-if="!restrictedToolBarItems.includes('full-screen')" + v-gl-tooltip + :class="{ 'gl-display-none!': previewMarkdown }" + class="js-zen-enter gl-ml-2" + category="tertiary" + icon="maximize" + size="small" + :title="__('Go full screen')" + :prepend="true" + :button-title="__('Go full screen')" + /> + </div> + </div> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue index e8be242f660511204996027ca7e2ba85f8f50698..4733afb750443d06bdb38dba849802bd4e143e29 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue @@ -1,6 +1,5 @@ <script> import { GlButton, GlLink, GlLoadingIcon, GlSprintf, GlIcon } from '@gitlab/ui'; -import EditorModeDropdown from './editor_mode_dropdown.vue'; export default { components: { @@ -9,7 +8,6 @@ export default { GlLoadingIcon, GlSprintf, GlIcon, - EditorModeDropdown, }, props: { markdownDocsPath: { @@ -31,30 +29,21 @@ export default { required: false, default: true, }, - showContentEditorSwitcher: { - type: Boolean, - required: false, - default: false, - }, }, computed: { hasQuickActionsDocsPath() { return this.quickActionsDocsPath !== ''; }, }, - methods: { - handleEditorModeChanged(mode) { - if (mode === 'richText') { - this.$emit('enableContentEditor'); - } - }, - }, }; </script> <template> - <div v-if="showCommentToolBar" class="comment-toolbar clearfix"> - <div class="toolbar-text"> + <div + v-if="showCommentToolBar" + class="comment-toolbar gl-mx-2 gl-mb-2 gl-px-4 gl-bg-gray-10 gl-rounded-bottom-left-base gl-rounded-bottom-right-base clearfix" + > + <div class="toolbar-text gl-font-sm"> <template v-if="!hasQuickActionsDocsPath && markdownDocsPath"> <gl-sprintf :message=" @@ -62,7 +51,9 @@ export default { " > <template #markdownDocsLink="{ content }"> - <gl-link :href="markdownDocsPath" target="_blank">{{ content }}</gl-link> + <gl-link :href="markdownDocsPath" target="_blank" class="gl-font-sm">{{ + content + }}</gl-link> </template> </gl-sprintf> </template> @@ -75,18 +66,22 @@ export default { " > <template #markdownDocsLink="{ content }"> - <gl-link :href="markdownDocsPath" target="_blank">{{ content }}</gl-link> + <gl-link :href="markdownDocsPath" target="_blank" class="gl-font-sm">{{ + content + }}</gl-link> </template> <template #keyboard="{ content }"> <kbd>{{ content }}</kbd> </template> <template #quickActionsDocsLink="{ content }"> - <gl-link :href="quickActionsDocsPath" target="_blank">{{ content }}</gl-link> + <gl-link :href="quickActionsDocsPath" target="_blank" class="gl-font-sm">{{ + content + }}</gl-link> </template> </gl-sprintf> </template> </div> - <span v-if="canAttachFile" class="uploading-container gl-line-height-32"> + <span v-if="canAttachFile" class="uploading-container gl-font-sm gl-line-height-32"> <span class="uploading-progress-container hide"> <gl-icon name="paperclip" /> <span class="attaching-file-message"></span> @@ -111,7 +106,7 @@ export default { <gl-button variant="link" category="primary" - class="retry-uploading-link gl-vertical-align-baseline" + class="retry-uploading-link gl-vertical-align-baseline gl-font-sm!" > {{ content }} </gl-button> @@ -120,7 +115,7 @@ export default { <gl-button variant="link" category="primary" - class="markdown-selector attach-new-file gl-vertical-align-baseline" + class="markdown-selector attach-new-file gl-vertical-align-baseline gl-font-sm!" > {{ content }} </gl-button> @@ -130,17 +125,10 @@ export default { <gl-button variant="link" category="primary" - class="button-cancel-uploading-files gl-vertical-align-baseline hide" + class="button-cancel-uploading-files gl-vertical-align-baseline hide gl-font-sm!" > {{ __('Cancel') }} </gl-button> </span> - <editor-mode-dropdown - v-if="showContentEditorSwitcher" - size="small" - class="gl-float-right gl-line-height-28 gl-display-block" - value="markdown" - @input="handleEditorModeChanged" - /> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue index 5ca21522d33f53fa58d4b61aaedb42b6bb5ea60b..636c89c99d450fb332f785a591c26fe029a0d144 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue @@ -92,7 +92,8 @@ export default { :icon="icon" type="button" category="tertiary" - class="js-md" + size="small" + class="js-md gl-mr-3" data-container="body" @click="$emit('click', $event)" /> diff --git a/app/assets/javascripts/work_items/components/notes/work_item_add_note.vue b/app/assets/javascripts/work_items/components/notes/work_item_add_note.vue index cb682e8b94419681b0ab3094a8e91b83d5345455..32ac5daf5de2efb8437c5d9dce4bb9125bd96f28 100644 --- a/app/assets/javascripts/work_items/components/notes/work_item_add_note.vue +++ b/app/assets/javascripts/work_items/components/notes/work_item_add_note.vue @@ -145,7 +145,7 @@ export default { return this.isNewDiscussion ? 'timeline-entry note-form' : // eslint-disable-next-line @gitlab/require-i18n-strings - 'note note-wrapper note-comment discussion-reply-holder gl-border-t-0! clearfix'; + 'note note-wrapper note-comment discussion-reply-holder gl-border-t-0! clearfix gl-bg-white! gl-pt-0!'; }, }, watch: { diff --git a/app/assets/stylesheets/components/content_editor.scss b/app/assets/stylesheets/components/content_editor.scss index 44429b439d9485c71ab84b812aae73637a15a408..a0cbf4fcd434b149227cb308f3dc613a48ff381f 100644 --- a/app/assets/stylesheets/components/content_editor.scss +++ b/app/assets/stylesheets/components/content_editor.scss @@ -1,5 +1,6 @@ .ProseMirror { - min-height: 128px; + padding-top: $gl-spacing-scale-4; + min-height: 140px; max-height: 55vh; overflow-y: auto; @@ -116,14 +117,8 @@ } } -.content-editor-dropdown .dropdown-menu { - width: auto !important; - - @include gl-min-w-0; - - button { - @include gl-white-space-nowrap; - } +.content-editor-switcher { + min-height: 32px; } diff --git a/app/assets/stylesheets/framework/diffs.scss b/app/assets/stylesheets/framework/diffs.scss index 9ec87b4f304db6cad0ffdc189cb8e39d22a41e93..54a4769f66da2c1ba2989d4b18e3fbde3752dca0 100644 --- a/app/assets/stylesheets/framework/diffs.scss +++ b/app/assets/stylesheets/framework/diffs.scss @@ -946,7 +946,7 @@ table.code { &.popover { width: 250px; min-width: 250px; - z-index: 210; + z-index: 610; } .popover-header { diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 48aacc9606e18f416785ab220b7fb989d97f7916..e57dad9e4cbecaebe49a0aad6155677a1a73f38f 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -44,46 +44,6 @@ } } -.md-header { - .nav-links { - a { - width: 100%; - padding-top: 0; - line-height: 19px; - - &.btn.btn-sm { - padding: 2px 5px; - } - - &:focus { - margin-top: -10px; - padding-top: 10px; - } - } - } - - .gl-tabs-nav { - @include media-breakpoint-down(xs) { - .nav-item { - flex: 1; - } - - .gl-tab-nav-item { - padding-top: $gl-padding-4; - padding-bottom: $gl-padding-8; - } - - .md-header-toolbar { - width: 100%; - display: flex; - flex-wrap: wrap; - padding-top: $gl-padding-8; - border-top: 1px solid $border-color; - } - } - } -} - .md-header-tab { @include media-breakpoint-down(xs) { flex: 1; @@ -131,7 +91,7 @@ } .md-preview-holder { - min-height: 172px; + min-height: 176px; padding: 10px 0; overflow-x: auto; } diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss index 00ef659dcf46b058c2f66792fe020e8846f752a3..d3ebc06a1dd0573c9772beae107813967d676566 100644 --- a/app/assets/stylesheets/page_bundles/merge_requests.scss +++ b/app/assets/stylesheets/page_bundles/merge_requests.scss @@ -1027,7 +1027,7 @@ $tabs-holder-z-index: 250; } .md-preview-holder { - max-height: 172px; + max-height: 182px; } } diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index adeab227670342d8600a2b28530abf9031dba9e4..d029aa01e377a416635e47010335b3fab0b51f0a 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -31,7 +31,7 @@ .note-textarea { display: block; - padding: 10px 1px; + padding: 10px 16px; color: $gl-text-color; font-family: $regular-font; border: 0; @@ -48,9 +48,8 @@ .common-note-form { .md-area { - padding: 0 $gl-padding; border: 1px solid $border-color; - border-radius: $border-radius-base; + border-radius: $border-radius-large; transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; background-color: $white; @@ -81,6 +80,10 @@ @include gl-focus; } +.md-header { + min-height: 32px; +} + .md-header .nav-links { display: flex; flex-flow: row wrap; @@ -92,6 +95,11 @@ } } + +.md-header .gl-tabs-nav { + border-bottom: 0; +} + .issuable-note-warning { color: $orange-600; background-color: $orange-50; @@ -305,7 +313,6 @@ table { .comment-toolbar { color: $gl-text-color-secondary; - border-top: 1px solid $border-color; } .toolbar-button { @@ -394,6 +401,10 @@ table { float: left; margin-top: 5px; } + + button { + font-size: $gl-font-size-sm !important; + } } .uploading-error-icon, diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb index bec6cccb977e8052eeb865ada065381acb1c2ad0..91fce6d6820f07482092be79599d8d94685a0bef 100644 --- a/app/helpers/markup_helper.rb +++ b/app/helpers/markup_helper.rb @@ -192,7 +192,7 @@ def render_links(text) def markdown_toolbar_button(options = {}) data = options[:data].merge({ container: 'body' }) - css_classes = %w[gl-button btn btn-default-tertiary btn-icon js-md has-tooltip] << options[:css_class].to_s + css_classes = %w[gl-button btn btn-default-tertiary btn-icon btn-sm js-md has-tooltip] << options[:css_class].to_s content_tag :button, type: 'button', class: css_classes.join(' '), diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index 5e1a979e2061d57b231278941802e0abb48067a7..621cd251bdf547b3ef8d7e76c03caf52d04a15e4 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -29,7 +29,10 @@ - unless Feature.enabled?(:source_editor_toolbar, current_user) .file-buttons.gl-display-flex.gl-align-items-center.gl-justify-content-end - if is_markdown - = render 'shared/blob/markdown_buttons', show_fullscreen_button: false, supports_file_upload: false + .md-header.gl-display-flex.gl-px-2.gl-rounded-base.gl-mx-2.gl-mt-2 + .gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-justify-content-space-between + .md-header-toolbar.gl-display-flex.gl-py-2.gl-flex-wrap{ class: "gl-m-0!" } + = render 'shared/blob/markdown_buttons', supports_file_upload: false %span.soft-wrap-toggle = render Pajamas::ButtonComponent.new(icon: 'soft-unwrap', button_options: { class: 'no-wrap' }) do = _("No wrap") diff --git a/app/views/shared/_md_preview.html.haml b/app/views/shared/_md_preview.html.haml index 2fff70cdc741845eab4a4f0fa1f9580c9d1ce677..dd3a31f5a592dc9f9032b9ff7b191faf82f3319e 100644 --- a/app/views/shared/_md_preview.html.haml +++ b/app/views/shared/_md_preview.html.haml @@ -8,21 +8,18 @@ = _('Only project members can comment.') .md-area.position-relative - .md-header - = gl_tabs_nav({ class: 'clearfix nav-links'}) do - %li.md-header-tab.active - %button.js-md-write-button{ class: 'gl-py-3!' } - = _("Write") - %li.md-header-tab - %button.js-md-preview-button{ class: 'gl-py-3!' } - = _("Preview") - - %li.md-header-toolbar.active.gl-py-2 - = render 'shared/blob/markdown_buttons', show_fullscreen_button: true + .md-header.gl-bg-gray-50.gl-px-2.gl-rounded-base.gl-mx-2.gl-mt-2 + .gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-justify-content-space-between + .md-header-toolbar.gl-display-flex.gl-py-2.gl-flex-wrap + = render 'shared/blob/markdown_buttons' + .switch-preview.gl-py-2.gl-display-flex.gl-align-items-center.gl-ml-auto + = render Pajamas::ButtonComponent.new(category: :tertiary, size: :small, button_options: { class: 'js-md-preview-button', value: 'preview' }) do + = _('Preview') + = render Pajamas::ButtonComponent.new(icon: 'maximize', category: :tertiary, size: :small, button_options: { 'tabindex': -1, 'aria-label': _("Go full screen"), class: 'has-tooltip js-zen-enter gl-ml-2', data: { container: 'body' } }) .md-write-holder = yield - .md.md-preview-holder.js-md-preview.hide{ data: { url: url } } + .md.md-preview-holder.gl-px-5.js-md-preview.hide{ data: { url: url } } .referenced-commands.hide - if referenced_users diff --git a/app/views/shared/_zen.html.haml b/app/views/shared/_zen.html.haml index 5a4efe7fe7f63a40fa1f72f7a83f97f112458717..05bee9e4d4250f14f985c4fcf92bc7ac2b4e3b33 100644 --- a/app/views/shared/_zen.html.haml +++ b/app/views/shared/_zen.html.haml @@ -4,6 +4,7 @@ - supports_quick_actions = local_assigns.fetch(:supports_quick_actions, false) - qa_selector = local_assigns.fetch(:qa_selector, '') - autofocus = local_assigns.fetch(:autofocus, false) + .zen-backdrop - classes << ' js-gfm-input js-autosize markdown-area' - if defined?(f) && f diff --git a/app/views/shared/blob/_markdown_buttons.html.haml b/app/views/shared/blob/_markdown_buttons.html.haml index db53d78dadb8c1e951206f30292013b6c9c9c23c..a3d3c1c82312db23e427a2da1c312c8d8d8936ed 100644 --- a/app/views/shared/blob/_markdown_buttons.html.haml +++ b/app/views/shared/blob/_markdown_buttons.html.haml @@ -1,42 +1,44 @@ - modifier_key = client_js_flags[:isMac] ? '⌘' : s_('KeyboardKey|Ctrl+') - supports_file_upload = local_assigns.fetch(:supports_file_upload, true) -.md-header-toolbar.active - = markdown_toolbar_button({ icon: "bold", - data: { "md-tag" => "**", "md-shortcuts": '["mod+b"]' }, - title: sprintf(s_("MarkdownEditor|Add bold text (%{modifier_key}B)") % { modifier_key: modifier_key }) }) += markdown_toolbar_button({ icon: "bold", + css_class: 'gl-mr-3', + data: { "md-tag" => "**", "md-shortcuts": '["mod+b"]' }, + title: sprintf(s_("MarkdownEditor|Add bold text (%{modifier_key}B)") % { modifier_key: modifier_key }) }) - = markdown_toolbar_button({ icon: "italic", - data: { "md-tag" => "_", "md-shortcuts": '["mod+i"]' }, - title: sprintf(s_("MarkdownEditor|Add italic text (%{modifier_key}I)") % { modifier_key: modifier_key }) }) += markdown_toolbar_button({ icon: "italic", + css_class: 'gl-mr-3', + data: { "md-tag" => "_", "md-shortcuts": '["mod+i"]' }, + title: sprintf(s_("MarkdownEditor|Add italic text (%{modifier_key}I)") % { modifier_key: modifier_key }) }) - = markdown_toolbar_button({ icon: "strikethrough", - data: { "md-tag" => "~~", "md-shortcuts": '["mod+shift+x"]' }, - title: sprintf(s_("MarkdownEditor|Add strikethrough text (%{modifier_key}⇧X)") % { modifier_key: modifier_key }) }) += markdown_toolbar_button({ icon: "strikethrough", + css_class: 'gl-mr-3', + data: { "md-tag" => "~~", "md-shortcuts": '["mod+shift+x"]' }, + title: sprintf(s_("MarkdownEditor|Add strikethrough text (%{modifier_key}⇧X)") % { modifier_key: modifier_key }) }) - = markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: _("Insert a quote") }) - = markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: _("Insert code") }) += markdown_toolbar_button({ icon: "quote", css_class: 'gl-mr-3', data: { "md-tag" => "> ", "md-prepend" => true }, title: _("Insert a quote") }) += markdown_toolbar_button({ icon: "code", css_class: 'gl-mr-3', data: { "md-tag" => "`", "md-block" => "```" }, title: _("Insert code") }) - = markdown_toolbar_button({ icon: "link", - data: { "md-tag" => "[{text}](url)", "md-select" => "url", "md-shortcuts": '["mod+k"]' }, - title: sprintf(s_("MarkdownEditor|Add a link (%{modifier_key}K)") % { modifier_key: modifier_key }) }) += markdown_toolbar_button({ icon: "link", + css_class: 'gl-mr-3', + data: { "md-tag" => "[{text}](url)", "md-select" => "url", "md-shortcuts": '["mod+k"]' }, + title: sprintf(s_("MarkdownEditor|Add a link (%{modifier_key}K)") % { modifier_key: modifier_key }) }) - = markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "- ", "md-prepend" => true }, title: _("Add a bullet list") }) - = markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: _("Add a numbered list") }) - = markdown_toolbar_button({ icon: "list-task", data: { "md-tag" => "- [ ] ", "md-prepend" => true }, title: _("Add a checklist") }) - = markdown_toolbar_button({ icon: "list-indent", - data: { "md-command" => 'indentLines', "md-shortcuts": '["mod+]"]' }, - css_class: 'gl-display-none', - title: sprintf(s_("MarkdownEditor|Indent line (%{modifier_key}])") % { modifier_key: modifier_key }) }) - = markdown_toolbar_button({ icon: "list-outdent", - data: { "md-command" => 'outdentLines', "md-shortcuts": '["mod+["]' }, - css_class: 'gl-display-none', - title: sprintf(s_("MarkdownEditor|Outdent line (%{modifier_key}[)") % { modifier_key: modifier_key }) }) - = markdown_toolbar_button({ icon: "details-block", - data: { "md-tag" => "<details><summary>Click to expand</summary>\n{text}\n</details>", "md-prepend" => true, "md-select" => "Click to expand" }, - title: _("Add a collapsible section") }) - = markdown_toolbar_button({ icon: "table", data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| | |\n| | |", "md-prepend" => true }, title: _("Add a table") }) - - if supports_file_upload - = render Pajamas::ButtonComponent.new(icon: 'paperclip', category: :tertiary, button_options: { 'aria-label': _("Attach a file or image"), class: 'has-tooltip js-attach-file-button', data: { testid: 'button-attach-file', container: 'body' } }) - - if show_fullscreen_button - = render Pajamas::ButtonComponent.new(icon: 'maximize', category: :tertiary, button_options: { 'tabindex': -1, 'aria-label': _("Go full screen"), class: 'has-tooltip js-zen-enter', data: { container: 'body' } }) += markdown_toolbar_button({ icon: "list-bulleted", css_class: 'gl-mr-3', data: { "md-tag" => "- ", "md-prepend" => true }, title: _("Add a bullet list") }) += markdown_toolbar_button({ icon: "list-numbered", css_class: 'gl-mr-3', data: { "md-tag" => "1. ", "md-prepend" => true }, title: _("Add a numbered list") }) += markdown_toolbar_button({ icon: "list-task", css_class: 'gl-mr-3', data: { "md-tag" => "- [ ] ", "md-prepend" => true }, title: _("Add a checklist") }) += markdown_toolbar_button({ icon: "list-indent", + css_class: 'gl-display-none gl-mr-3', + data: { "md-command" => 'indentLines', "md-shortcuts": '["mod+]"]' }, + title: sprintf(s_("MarkdownEditor|Indent line (%{modifier_key}])") % { modifier_key: modifier_key }) }) += markdown_toolbar_button({ icon: "list-outdent", + css_class: 'gl-display-none gl-mr-3', + data: { "md-command" => 'outdentLines', "md-shortcuts": '["mod+["]' }, + title: sprintf(s_("MarkdownEditor|Outdent line (%{modifier_key}[)") % { modifier_key: modifier_key }) }) += markdown_toolbar_button({ icon: "details-block", + css_class: 'gl-mr-3', + data: { "md-tag" => "<details><summary>Click to expand</summary>\n{text}\n</details>", "md-prepend" => true, "md-select" => "Click to expand" }, + title: _("Add a collapsible section") }) += markdown_toolbar_button({ icon: "table", css_class: 'gl-mr-3', data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| | |\n| | |", "md-prepend" => true }, title: _("Add a table") }) +- if supports_file_upload + = render Pajamas::ButtonComponent.new(icon: 'paperclip', category: :tertiary, size: :small, button_options: { 'aria-label': _("Attach a file or image"), class: 'has-tooltip js-attach-file-button gl-mr-3', data: { testid: 'button-attach-file', container: 'body' } }) diff --git a/app/views/shared/notes/_hints.html.haml b/app/views/shared/notes/_hints.html.haml index fb000b9aab18e28e3877f457633e624da4ca4357..d7d6e477ab1c70082cdeabde331960fea7fbb43d 100644 --- a/app/views/shared/notes/_hints.html.haml +++ b/app/views/shared/notes/_hints.html.haml @@ -1,7 +1,7 @@ - supports_quick_actions = local_assigns.fetch(:supports_quick_actions, false) - supports_file_upload = local_assigns.fetch(:supports_file_upload, true) -.comment-toolbar.clearfix - .toolbar-text +.comment-toolbar.gl-mx-2.gl-mb-2.gl-px-4.gl-bg-gray-10.gl-rounded-bottom-left-base.gl-rounded-bottom-right-base.clearfix + .toolbar-text.gl-font-sm - markdownLinkStart = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/markdown') } - quickActionsLinkStart = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/quick_actions') } - if supports_quick_actions @@ -9,7 +9,7 @@ - else = html_escape(s_('MarkdownToolbar|Supports %{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd}')) % { markdownDocsLinkStart: markdownLinkStart, markdownDocsLinkEnd: '</a>'.html_safe } - if supports_file_upload - %span.uploading-container.gl-line-height-32 + %span.uploading-container.gl-line-height-32.gl-font-sm %span.uploading-progress-container.hide = sprite_icon('paperclip', css_class: 'gl-icon gl-vertical-align-text-bottom') %span.attaching-file-message diff --git a/ee/app/assets/javascripts/ai/components/ai_actions_dropdown.vue b/ee/app/assets/javascripts/ai/components/ai_actions_dropdown.vue index eb90d04e6fbf4e67aab7a1de5babcb547fe5be4a..e3c0718e8eb57797b85f1f4da108537dc92c05f5 100644 --- a/ee/app/assets/javascripts/ai/components/ai_actions_dropdown.vue +++ b/ee/app/assets/javascripts/ai/components/ai_actions_dropdown.vue @@ -158,7 +158,7 @@ export default { @action="$refs.dropdown.close()" > <template #toggle> - <gl-button category="tertiary" class="gl-px-3!"> + <gl-button category="tertiary" size="small" class="gl-mr-3 gl-px-2!"> <gl-loading-icon v-if="loading" /> <gl-icon v-else name="tanuki" /> </gl-button> diff --git a/ee/app/assets/javascripts/vulnerabilities/components/new_vulnerability/section_name.vue b/ee/app/assets/javascripts/vulnerabilities/components/new_vulnerability/section_name.vue index ebbcd272c64b1c1f63b2ce6f867d80a2c98cd421..fc973c78036f058548b709e6435333d7fb3d24e8 100644 --- a/ee/app/assets/javascripts/vulnerabilities/components/new_vulnerability/section_name.vue +++ b/ee/app/assets/javascripts/vulnerabilities/components/new_vulnerability/section_name.vue @@ -77,7 +77,6 @@ export default { <div class="gl-border-solid gl-border-gray-100 gl-border-1 gl-rounded-base"> <markdown-field ref="markdownField" - class="gl-px-4" :can-attach-file="false" :add-spacing-classes="false" :markdown-preview-path="markdownPreviewPath" @@ -91,7 +90,7 @@ export default { id="form-vulnerability-desc" v-model.trim="vulnerabilityDesc" rows="8" - class="gl-shadow-none! gl-px-0! gl-py-4! gl-h-auto!" + class="gl-shadow-none! gl-py-4! gl-h-auto!" :aria-label="$options.i18n.vulnerabilityDesc.description" :placeholder="$options.i18n.vulnerabilityDesc.description" @input="emitChanges" diff --git a/ee/spec/features/epics/update_epic_spec.rb b/ee/spec/features/epics/update_epic_spec.rb index 98ea47f05f18f88f1a33f96cf2ccffb27bb3ce8c..2f522dbd289c4cd32889aa4978229f5ddc98f6e3 100644 --- a/ee/spec/features/epics/update_epic_spec.rb +++ b/ee/spec/features/epics/update_epic_spec.rb @@ -50,7 +50,7 @@ fill_in 'issue-description', with: 'New epic description' page.within('.detail-page-description') do - click_link('Preview') + click_button("Preview") expect(find('.md-preview-holder')).to have_content('New epic description') end @@ -65,7 +65,7 @@ fill_in 'issue-description', with: 'New epic description' page.within('.detail-page-description') do - click_link('Preview') + click_button("Preview") expect(find('.md-preview-holder')).to have_content('New epic description') end @@ -78,7 +78,7 @@ find('.js-issuable-edit').click page.within('.detail-page-description') do - click_link('Preview') + click_button("Preview") expect(find('.md-preview-holder')).to have_content('New epic description') end end @@ -124,7 +124,7 @@ expect(page.find_field("issue-description").value).to have_content('banana_sample') page.within('.detail-page-description') do - click_link('Preview') + click_button("Preview") wait_for_requests within('.md-preview-holder') do diff --git a/ee/spec/features/epics/user_comments_on_epic_spec.rb b/ee/spec/features/epics/user_comments_on_epic_spec.rb index 8aa8bce88a7b47ae40bee15a30682f9efdb39b5c..ea39d7e3e1b1f42b87e698dae091214b1f2d1a9a 100644 --- a/ee/spec/features/epics/user_comments_on_epic_spec.rb +++ b/ee/spec/features/epics/user_comments_on_epic_spec.rb @@ -40,7 +40,7 @@ fill_in 'Comment', with: "#{epic2.to_reference(full: true)}+" page.within('.new-note') do - click_link('Preview') + click_button("Preview") wait_for_requests within('.md-preview-holder') do diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 75dfdac6414f2e0b8dc83d12a0fc860ef1f5428f..f061e22dff687613361974bf8a25ded1ab3d6128 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -16251,12 +16251,6 @@ msgstr "" msgid "Editing" msgstr "" -msgid "Editing markdown" -msgstr "" - -msgid "Editing rich text" -msgstr "" - msgid "Edits" msgstr "" @@ -27044,9 +27038,6 @@ msgstr "" msgid "Mark to do as done" msgstr "" -msgid "Markdown" -msgstr "" - msgid "Markdown Help" msgstr "" @@ -38333,9 +38324,6 @@ msgstr "" msgid "Revoked personal access token %{personal_access_token_name}!" msgstr "" -msgid "Rich text" -msgstr "" - msgid "RightSidebar|Copy email address" msgstr "" @@ -43871,6 +43859,12 @@ msgstr "" msgid "Switch to GitLab Next" msgstr "" +msgid "Switch to Markdown" +msgstr "" + +msgid "Switch to rich text" +msgstr "" + msgid "Switch to the source to copy the file contents" msgstr "" @@ -49090,9 +49084,6 @@ msgstr "" msgid "View all projects" msgstr "" -msgid "View and edit markdown, with the option to preview the formatted output." -msgstr "" - msgid "View blame" msgstr "" @@ -49222,9 +49213,6 @@ msgstr "" msgid "View the documentation" msgstr "" -msgid "View the formatted output in real-time as you edit." -msgstr "" - msgid "View the latest successful deployment to this environment" msgstr "" diff --git a/qa/qa/page/component/wiki_page_form.rb b/qa/qa/page/component/wiki_page_form.rb index 9143a25d9ab2412a7c198168c4a8691c1c475b67..335790c5b27f27b15fa653b3109096ba34b9f53b 100644 --- a/qa/qa/page/component/wiki_page_form.rb +++ b/qa/qa/page/component/wiki_page_form.rb @@ -19,7 +19,7 @@ def self.included(base) element :markdown_editor_form_field end - base.view 'app/assets/javascripts/vue_shared/components/markdown/editor_mode_dropdown.vue' do + base.view 'app/assets/javascripts/vue_shared/components/markdown/editor_mode_switcher.vue' do element :editing_mode_switcher end @@ -59,9 +59,6 @@ def delete_page def use_new_editor click_element(:editing_mode_switcher) - within_element(:editing_mode_switcher) do - find('button', text: 'Rich text').click - end wait_until(reload: false) do has_element?(:content_editor_container) diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb index a70a1e2e70ba42a78b0bc4221011b646cebe826a..376e1e6063fd4ab27af4830075e2fdc6390bf5b1 100644 --- a/spec/features/groups/milestone_spec.rb +++ b/spec/features/groups/milestone_spec.rb @@ -25,17 +25,17 @@ description.native.send_keys('') - click_button('Preview') + click_button("Preview") preview = find('.js-md-preview') expect(preview).to have_content('Nothing to preview.') - click_button('Write') + click_button("Continue editing") description.native.send_keys(':+1: Nice') - click_button('Preview') + click_button("Preview") expect(preview).to have_css('gl-emoji') expect(find('#milestone_description', visible: false)).not_to be_visible diff --git a/spec/features/issuables/markdown_references/jira_spec.rb b/spec/features/issuables/markdown_references/jira_spec.rb index 52464c6be8b01414cdbdcf5086be7385fc8fe085..887bc7d0c87c0aa098c6bfceba0fbbf4787ea08e 100644 --- a/spec/features/issuables/markdown_references/jira_spec.rb +++ b/spec/features/issuables/markdown_references/jira_spec.rb @@ -29,7 +29,7 @@ end it "creates a link to the referenced issue on the preview" do - find(".js-md-preview-button").click + click_button("Preview") wait_for_requests diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb index 3b1716230cd6e4f02b33c954f5e48286dd2489aa..56c395091d926571288d6d28a434bf4fd2ebcba8 100644 --- a/spec/features/issues/user_creates_issue_spec.rb +++ b/spec/features/issues/user_creates_issue_spec.rb @@ -61,22 +61,22 @@ textarea = first(".gfm-form textarea") page.within(form) do - click_link("Preview") + click_button("Preview") preview = find(".js-vue-md-preview") # this element is findable only when the "Preview" link is clicked. expect(preview).to have_content("Nothing to preview.") - click_link("Write") + click_button("Continue editing") fill_in("Description", with: "Bug fixed :smile:") - click_link("Preview") + click_button("Preview") expect(preview).to have_css("gl-emoji") expect(textarea).not_to be_visible - click_link("Write") + click_button("Continue editing") fill_in("Description", with: "/confidential") - click_link("Preview") + click_button("Preview") expect(form).to have_content('Makes this issue confidential.') end diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb index 4ef58918a2bbd80c67fdeebc1fa22899a6f8eefc..c1cf8fada26148b48ede9158aa545cd94d4c02b2 100644 --- a/spec/features/issues/user_edits_issue_spec.rb +++ b/spec/features/issues/user_edits_issue_spec.rb @@ -39,9 +39,7 @@ click_button("Preview") end - expect(form).to have_button("Write") - - click_button("Write") + click_button("Continue editing") fill_in("Description", with: "/confidential") click_button("Preview") @@ -121,8 +119,7 @@ def click_edit_issue_description expect(issuable_form).to have_selector(markdown_field_focused_selector) page.within issuable_form do - click_on _('Editing markdown') - click_on _('Rich text') + click_button("Switch to rich text") end expect(issuable_form).not_to have_selector(content_editor_focused_selector) @@ -134,8 +131,7 @@ def click_edit_issue_description expect(issuable_form).to have_selector(content_editor_focused_selector) page.within issuable_form do - click_on _('Editing rich text') - click_on _('Markdown') + click_button("Switch to Markdown") end expect(issuable_form).not_to have_selector(markdown_field_focused_selector) diff --git a/spec/features/merge_request/user_posts_notes_spec.rb b/spec/features/merge_request/user_posts_notes_spec.rb index f167ab8fe8a4a77262f69a87b49324793d22c778..03b01ef4b7a1d75415097f7032638ee8e419107f 100644 --- a/spec/features/merge_request/user_posts_notes_spec.rb +++ b/spec/features/merge_request/user_posts_notes_spec.rb @@ -62,7 +62,7 @@ before do page.within('.js-main-target-form') do fill_in 'note[note]', with: 'This is awesome!' - find('.js-md-preview-button').click + click_button("Preview") click_button 'Comment' end end @@ -138,7 +138,7 @@ it 'hides the toolbar buttons when previewing a note' do wait_for_requests - find('.js-md-preview-button').click + click_button("Preview") page.within('.js-main-target-form') do expect(page).not_to have_css('.md-header-toolbar') end diff --git a/spec/features/merge_request/user_views_open_merge_request_spec.rb b/spec/features/merge_request/user_views_open_merge_request_spec.rb index 6118f59df3cabee9a66dffd44538336a43663b9a..1a9d40ae92648ebdcd8c5b853f2aa2b5b4a2fb6d 100644 --- a/spec/features/merge_request/user_views_open_merge_request_spec.rb +++ b/spec/features/merge_request/user_views_open_merge_request_spec.rb @@ -59,7 +59,7 @@ fill_in(:merge_request_description, with: '') page.within('.js-vue-markdown-field') do - click_link('Preview') + click_button("Preview") expect(find('.js-vue-md-preview')).to have_content('Nothing to preview.') end @@ -69,12 +69,12 @@ fill_in(:merge_request_description, with: ':+1: Nice') page.within('.js-vue-markdown-field') do - click_link('Preview') + click_button("Preview") expect(find('.js-vue-md-preview')).to have_css('gl-emoji') end - expect(find('.js-vue-markdown-field')).to have_css('.js-vue-md-preview').and have_link('Write') + expect(find('.js-vue-markdown-field')).to have_css('.js-md-preview-button') expect(find('#merge_request_description', visible: false)).not_to be_visible end end diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb index 2b6b09ccc10526358e0884dbe60b744577a2ac56..6e335871ed1e6d786fe27c4f13f5b33816d9beed 100644 --- a/spec/features/projects/blobs/edit_spec.rb +++ b/spec/features/projects/blobs/edit_spec.rb @@ -142,7 +142,7 @@ def fill_editor(content: 'class NextFeature\\nend\\n') it 'renders content with CommonMark' do visit project_edit_blob_path(project, tree_join(branch, readme_file_path)) fill_editor(content: '1. one\\n - sublist\\n') - click_link 'Preview' + click_on "Preview" wait_for_requests # the above generates two separate lists (not embedded) in CommonMark diff --git a/spec/features/projects/commit/comments/user_adds_comment_spec.rb b/spec/features/projects/commit/comments/user_adds_comment_spec.rb index 91b838116e9facca16e764c108ded69000a7dcf7..b0cb57f158dd7183b5b3bb4177df2407784506c0 100644 --- a/spec/features/projects/commit/comments/user_adds_comment_spec.rb +++ b/spec/features/projects/commit/comments/user_adds_comment_spec.rb @@ -36,7 +36,7 @@ expect(page).not_to have_css(".js-note-text") # Check on the `Write` tab - click_button("Write") + click_button("Continue editing") expect(page).to have_field("note[note]", with: "#{comment_text} #{emoji}") @@ -107,7 +107,7 @@ # Test UI elements, then submit. page.within("form[data-line-code='#{sample_commit.line_code}']") do expect(find(".js-note-text", visible: false).text).to eq("") - expect(page).to have_css('.js-md-write-button') + expect(page).to have_css('.js-md-preview') click_button("Comment") end diff --git a/spec/features/projects/commit/user_comments_on_commit_spec.rb b/spec/features/projects/commit/user_comments_on_commit_spec.rb index c4019b4d123f109d4cf516fd032a384c3118936e..c1d994ca837587d295c2a9e821b95061d4d5aace 100644 --- a/spec/features/projects/commit/user_comments_on_commit_spec.rb +++ b/spec/features/projects/commit/user_comments_on_commit_spec.rb @@ -38,7 +38,7 @@ expect(page).not_to have_css(".js-note-text") # Check on `Write` tab - click_button("Write") + click_button("Continue editing") expect(page).to have_field("note[note]", with: "#{comment_text} #{emoji_code}") diff --git a/spec/features/projects/releases/user_creates_release_spec.rb b/spec/features/projects/releases/user_creates_release_spec.rb index c282067f3adcee6e21a7e442a8aa64a05be2a03a..ffc319c845335efb76d95568c71803eaf1bbf91c 100644 --- a/spec/features/projects/releases/user_creates_release_spec.rb +++ b/spec/features/projects/releases/user_creates_release_spec.rb @@ -108,7 +108,7 @@ fill_release_notes('**some** _markdown_ [content](https://example.com)') - click_on 'Preview' + click_button("Preview") wait_for_all_requests end diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb index 5aac27a71e4b533aa40c3f55c979a4af97154ba2..5c1ee729346a548c302a1fcf0420a8eec061b398 100644 --- a/spec/features/snippets/notes_on_personal_snippets_spec.rb +++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb @@ -81,6 +81,7 @@ it 'previews a note' do fill_in 'note[note]', with: 'This is **awesome**!' + find('.js-md-preview-button').click page.within('.new-note .md-preview-holder') do diff --git a/spec/frontend/content_editor/components/__snapshots__/toolbar_button_spec.js.snap b/spec/frontend/content_editor/components/__snapshots__/toolbar_button_spec.js.snap index b8e6bcbc3c4f71e09b844316c1be3dbe08c7e918..a328f79e4e7d55d829e8809de5dfb4630bcf0162 100644 --- a/spec/frontend/content_editor/components/__snapshots__/toolbar_button_spec.js.snap +++ b/spec/frontend/content_editor/components/__snapshots__/toolbar_button_spec.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`content_editor/components/toolbar_button displays tertiary, medium button with a provided label and icon 1`] = ` -"<b-button-stub size=\\"md\\" tag=\\"button\\" type=\\"button\\" variant=\\"default\\" aria-label=\\"Bold\\" title=\\"Bold\\" class=\\"gl-button btn-default-tertiary btn-icon\\"> +"<b-button-stub size=\\"sm\\" tag=\\"button\\" type=\\"button\\" variant=\\"default\\" aria-label=\\"Bold\\" title=\\"Bold\\" class=\\"gl-mr-3 gl-button btn-default-tertiary btn-icon\\"> <!----> <gl-icon-stub name=\\"bold\\" size=\\"16\\" class=\\"gl-button-icon\\"></gl-icon-stub> <!----> diff --git a/spec/frontend/content_editor/components/content_editor_spec.js b/spec/frontend/content_editor/components/content_editor_spec.js index b642ac9c46bf102b3552f0e39d88ec745ab329e2..8bbd79a61af9670acd53cb4fe0c25ebfd8721e52 100644 --- a/spec/frontend/content_editor/components/content_editor_spec.js +++ b/spec/frontend/content_editor/components/content_editor_spec.js @@ -2,7 +2,6 @@ import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui'; import { EditorContent, Editor } from '@tiptap/vue-2'; import { nextTick } from 'vue'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import EditorModeDropdown from '~/vue_shared/components/markdown/editor_mode_dropdown.vue'; import ContentEditor from '~/content_editor/components/content_editor.vue'; import ContentEditorAlert from '~/content_editor/components/content_editor_alert.vue'; import ContentEditorProvider from '~/content_editor/components/content_editor_provider.vue'; @@ -44,7 +43,6 @@ describe('ContentEditor', () => { ContentEditorAlert, GlLink, GlSprintf, - EditorModeDropdown, }, }); }; @@ -107,12 +105,6 @@ describe('ContentEditor', () => { expect(findEditorElement().text()).not.toContain('For quick actions, type /'); }); - it('renders an editor mode dropdown', () => { - createWrapper(); - - expect(wrapper.findComponent(EditorModeDropdown).exists()).toBe(true); - }); - describe('when setting initial content', () => { it('displays loading indicator', async () => { createWrapper(); diff --git a/spec/frontend/content_editor/components/formatting_toolbar_spec.js b/spec/frontend/content_editor/components/formatting_toolbar_spec.js index 5d2a9e493e5aa8bc8cfb8563067ffa57d058f300..2fc7e5e2e1be1fca1dff5576044fe9c889c1173e 100644 --- a/spec/frontend/content_editor/components/formatting_toolbar_spec.js +++ b/spec/frontend/content_editor/components/formatting_toolbar_spec.js @@ -6,6 +6,7 @@ import { TOOLBAR_CONTROL_TRACKING_ACTION, CONTENT_EDITOR_TRACKING_LABEL, } from '~/content_editor/constants'; +import EditorModeSwitcher from '~/vue_shared/components/markdown/editor_mode_switcher.vue'; describe('content_editor/components/formatting_toolbar', () => { let wrapper; @@ -16,6 +17,7 @@ describe('content_editor/components/formatting_toolbar', () => { stubs: { GlTabs, GlTab, + EditorModeSwitcher, }, }); }; @@ -64,4 +66,10 @@ describe('content_editor/components/formatting_toolbar', () => { }); }); }); + + it('renders an editor mode dropdown', () => { + buildWrapper(); + + expect(wrapper.findComponent(EditorModeSwitcher).exists()).toBe(true); + }); }); diff --git a/spec/frontend/notes/components/comment_form_spec.js b/spec/frontend/notes/components/comment_form_spec.js index fb64551c76b3b262904749b3248526f048768a38..70f25afc5ba23096fc5c5ed77e8eaca5ff9679a7 100644 --- a/spec/frontend/notes/components/comment_form_spec.js +++ b/spec/frontend/notes/components/comment_form_spec.js @@ -264,13 +264,13 @@ describe('issue_comment_form component', () => { it('hides content editor switcher if feature flag content_editor_on_issues is off', () => { mountComponent({ mountFunction: mount, features: { contentEditorOnIssues: false } }); - expect(wrapper.text()).not.toContain('Rich text'); + expect(wrapper.text()).not.toContain('Switch to rich text'); }); it('shows content editor switcher if feature flag content_editor_on_issues is on', () => { mountComponent({ mountFunction: mount, features: { contentEditorOnIssues: true } }); - expect(wrapper.text()).toContain('Rich text'); + expect(wrapper.text()).toContain('Switch to rich text'); }); describe('textarea', () => { diff --git a/spec/frontend/notes/components/note_form_spec.js b/spec/frontend/notes/components/note_form_spec.js index 9423af4f05873095f399791ecff8ffe31fac0e50..b5b33607282d021de947073e397227eeea3ce19c 100644 --- a/spec/frontend/notes/components/note_form_spec.js +++ b/spec/frontend/notes/components/note_form_spec.js @@ -66,13 +66,13 @@ describe('issue_note_form component', () => { it('hides content editor switcher if feature flag content_editor_on_issues is off', () => { createComponentWrapper({}, { contentEditorOnIssues: false }); - expect(wrapper.text()).not.toContain('Rich text'); + expect(wrapper.text()).not.toContain('Switch to rich text'); }); it('shows content editor switcher if feature flag content_editor_on_issues is on', () => { createComponentWrapper({}, { contentEditorOnIssues: true }); - expect(wrapper.text()).toContain('Rich text'); + expect(wrapper.text()).toContain('Switch to rich text'); }); describe('conflicts editing', () => { diff --git a/spec/frontend/shortcuts_spec.js b/spec/frontend/shortcuts_spec.js index 52615ac3c65adbdc3c2670e65a5866537dfd153e..e72de11d9219c34eee77f655c78647c4c8d032e2 100644 --- a/spec/frontend/shortcuts_spec.js +++ b/spec/frontend/shortcuts_spec.js @@ -1,11 +1,10 @@ import $ from 'jquery'; -import htmlSnippetsShow from 'test_fixtures/snippets/show.html'; import { flatten } from 'lodash'; +import htmlSnippetsShow from 'test_fixtures/snippets/show.html'; import { Mousetrap } from '~/lib/mousetrap'; import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import Shortcuts, { LOCAL_MOUSETRAP_DATA_KEY } from '~/behaviors/shortcuts/shortcuts'; - -jest.mock('mousetrap/plugins/pause/mousetrap-pause', () => {}); +import MarkdownPreview from '~/behaviors/preview_markdown'; describe('Shortcuts', () => { const createEvent = (type, target) => @@ -21,6 +20,9 @@ describe('Shortcuts', () => { beforeEach(() => { setHTMLFixture(htmlSnippetsShow); + new Shortcuts(); // eslint-disable-line no-new + new MarkdownPreview(); // eslint-disable-line no-new + jest.spyOn(document.querySelector('.js-new-note-form .js-md-preview-button'), 'focus'); jest.spyOn(document.querySelector('.edit-note .js-md-preview-button'), 'focus'); jest.spyOn(document.querySelector('#search'), 'focus'); diff --git a/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap b/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap index 7eb0468c5bee574dfa9e26e7473c7b49c968c58d..af4af45109c11b9f949631f6be8bdcd52870525c 100644 --- a/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap +++ b/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap @@ -90,7 +90,7 @@ exports[`Snippet Description Edit component rendering matches the snapshot 1`] = </div> <div - class="js-vue-md-preview md md-preview-holder" + class="js-vue-md-preview md md-preview-holder gl-px-5" style="display: none;" /> diff --git a/spec/frontend/vue_shared/components/markdown/editor_mode_dropdown_spec.js b/spec/frontend/vue_shared/components/markdown/editor_mode_dropdown_spec.js deleted file mode 100644 index fd8493e0911e2905d533da2e9b5fa1f86122de48..0000000000000000000000000000000000000000 --- a/spec/frontend/vue_shared/components/markdown/editor_mode_dropdown_spec.js +++ /dev/null @@ -1,54 +0,0 @@ -import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import EditorModeDropdown from '~/vue_shared/components/markdown/editor_mode_dropdown.vue'; - -describe('vue_shared/component/markdown/editor_mode_dropdown', () => { - let wrapper; - - const createComponent = ({ value, size } = {}) => { - wrapper = shallowMount(EditorModeDropdown, { - propsData: { - value, - size, - }, - }); - }; - - const findDropdown = () => wrapper.findComponent(GlDropdown); - const findDropdownItem = (text) => - wrapper - .findAllComponents(GlDropdownItem) - .filter((item) => item.text().startsWith(text)) - .at(0); - - describe.each` - modeText | value | dropdownText | otherMode - ${'Rich text'} | ${'richText'} | ${'Editing rich text'} | ${'Markdown'} - ${'Markdown'} | ${'markdown'} | ${'Editing markdown'} | ${'Rich text'} - `('$modeText', ({ modeText, value, dropdownText, otherMode }) => { - beforeEach(() => { - createComponent({ value }); - }); - - it('shows correct dropdown label', () => { - expect(findDropdown().props('text')).toEqual(dropdownText); - }); - - it('checks correct checked dropdown item', () => { - expect(findDropdownItem(modeText).props().isChecked).toBe(true); - expect(findDropdownItem(otherMode).props().isChecked).toBe(false); - }); - - it('emits event on click', () => { - findDropdownItem(modeText).vm.$emit('click'); - - expect(wrapper.emitted().input).toEqual([[value]]); - }); - }); - - it('passes size to dropdown', () => { - createComponent({ size: 'small', value: 'markdown' }); - - expect(findDropdown().props('size')).toEqual('small'); - }); -}); diff --git a/spec/frontend/vue_shared/components/markdown/editor_mode_switcher_spec.js b/spec/frontend/vue_shared/components/markdown/editor_mode_switcher_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..693353ed604261050841e56add426cd7a6b6223d --- /dev/null +++ b/spec/frontend/vue_shared/components/markdown/editor_mode_switcher_spec.js @@ -0,0 +1,37 @@ +import { GlButton } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import EditorModeSwitcher from '~/vue_shared/components/markdown/editor_mode_switcher.vue'; + +describe('vue_shared/component/markdown/editor_mode_switcher', () => { + let wrapper; + + const createComponent = ({ value } = {}) => { + wrapper = shallowMount(EditorModeSwitcher, { + propsData: { + value, + }, + }); + }; + + const findSwitcherButton = () => wrapper.findComponent(GlButton); + + describe.each` + modeText | value | buttonText + ${'Rich text'} | ${'richText'} | ${'Switch to Markdown'} + ${'Markdown'} | ${'markdown'} | ${'Switch to rich text'} + `('when $modeText', ({ modeText, value, buttonText }) => { + beforeEach(() => { + createComponent({ value }); + }); + + it('shows correct button label', () => { + expect(findSwitcherButton().text()).toEqual(buttonText); + }); + + it('emits event on click', () => { + findSwitcherButton(modeText).vm.$emit('click'); + + expect(wrapper.emitted().input).toEqual([[]]); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/markdown/field_spec.js b/spec/frontend/vue_shared/components/markdown/field_spec.js index 68ce07f86b98033f8957ce3b1c57d0fcdb3eb3e1..b29f0d58d777b2152247adcc9fca5288b9b9e488 100644 --- a/spec/frontend/vue_shared/components/markdown/field_spec.js +++ b/spec/frontend/vue_shared/components/markdown/field_spec.js @@ -18,12 +18,6 @@ const textareaValue = 'testing\n123'; const uploadsPath = 'test/uploads'; const restrictedToolBarItems = ['quote']; -function assertMarkdownTabs(isWrite, writeLink, previewLink, wrapper) { - expect(writeLink.element.children[0].classList.contains('active')).toBe(isWrite); - expect(previewLink.element.children[0].classList.contains('active')).toBe(!isWrite); - expect(wrapper.find('.md-preview-holder').element.style.display).toBe(isWrite ? 'none' : ''); -} - describe('Markdown field component', () => { let axiosMock; let subject; @@ -92,8 +86,7 @@ describe('Markdown field component', () => { }); } - const getPreviewLink = () => subject.findByTestId('preview-tab'); - const getWriteLink = () => subject.findByTestId('write-tab'); + const getPreviewToggle = () => subject.findByTestId('preview-toggle'); const getMarkdownButton = () => subject.find('.js-md'); const getListBulletedButton = () => subject.findAll('.js-md[title="Add a bullet list"]'); const getVideo = () => subject.find('video'); @@ -109,8 +102,7 @@ describe('Markdown field component', () => { <p>markdown preview</p> <video src="${FIXTURES_PATH}/static/mock-video.mp4"></video> `; - let previewLink; - let writeLink; + let previewToggle; let dropzoneSpy; beforeEach(() => { @@ -140,8 +132,8 @@ describe('Markdown field component', () => { .onPost(markdownPreviewPath) .reply(HTTP_STATUS_OK, { references: { users: [], commands: 'test command' } }); - previewLink = getPreviewLink(); - previewLink.vm.$emit('click', { target: {} }); + previewToggle = getPreviewToggle(); + previewToggle.vm.$emit('click', true); await axios.waitFor(markdownPreviewPath); const referencedCommands = subject.find('[data-testid="referenced-commands"]'); @@ -155,26 +147,29 @@ describe('Markdown field component', () => { axiosMock.onPost(markdownPreviewPath).reply(HTTP_STATUS_OK, { body: previewHTML }); }); - it('sets preview link as active', async () => { - previewLink = getPreviewLink(); - previewLink.vm.$emit('click', { target: {} }); + it('sets preview toggle as active', async () => { + previewToggle = getPreviewToggle(); + + expect(previewToggle.text()).toBe('Preview'); + + previewToggle.vm.$emit('click', true); await nextTick(); - expect(previewLink.element.children[0].classList.contains('active')).toBe(true); + expect(previewToggle.text()).toBe('Continue editing'); }); it('shows preview loading text', async () => { - previewLink = getPreviewLink(); - previewLink.vm.$emit('click', { target: {} }); + previewToggle = getPreviewToggle(); + previewToggle.vm.$emit('click', true); await nextTick(); expect(subject.find('.md-preview-holder').element.textContent.trim()).toContain('Loading…'); }); it('renders markdown preview and GFM', async () => { - previewLink = getPreviewLink(); + previewToggle = getPreviewToggle(); - previewLink.vm.$emit('click', { target: {} }); + previewToggle.vm.$emit('click', true); await axios.waitFor(markdownPreviewPath); expect(subject.find('.md-preview-holder').element.innerHTML).toContain(previewHTML); @@ -182,8 +177,8 @@ describe('Markdown field component', () => { }); it('calls video.pause() on comment input when isSubmitting is changed to true', async () => { - previewLink = getPreviewLink(); - previewLink.vm.$emit('click', { target: {} }); + previewToggle = getPreviewToggle(); + previewToggle.vm.$emit('click', true); await axios.waitFor(markdownPreviewPath); const video = getVideo(); @@ -195,34 +190,27 @@ describe('Markdown field component', () => { expect(callPause).toHaveBeenCalled(); }); - it('clicking already active write or preview link does nothing', async () => { - writeLink = getWriteLink(); - previewLink = getPreviewLink(); - - writeLink.vm.$emit('click', { target: {} }); - await nextTick(); - - assertMarkdownTabs(true, writeLink, previewLink, subject); - writeLink.vm.$emit('click', { target: {} }); - await nextTick(); + it('switches between preview/write on toggle', async () => { + previewToggle = getPreviewToggle(); - assertMarkdownTabs(true, writeLink, previewLink, subject); - previewLink.vm.$emit('click', { target: {} }); + previewToggle.vm.$emit('click', true); await nextTick(); + expect(subject.find('.md-preview-holder').element.style.display).toBe(''); // visible - assertMarkdownTabs(false, writeLink, previewLink, subject); - previewLink.vm.$emit('click', { target: {} }); + previewToggle.vm.$emit('click', false); await nextTick(); - - assertMarkdownTabs(false, writeLink, previewLink, subject); + expect(subject.find('.md-preview-holder').element.style.display).toBe('none'); }); - it('passes correct props to MarkdownToolbar', () => { + it('passes correct props to MarkdownHeader and MarkdownToolbar', () => { expect(findMarkdownToolbar().props()).toEqual({ canAttachFile: true, markdownDocsPath, quickActionsDocsPath: '', showCommentToolBar: true, + }); + + expect(findMarkdownHeader().props()).toMatchObject({ showContentEditorSwitcher: false, }); }); @@ -380,13 +368,13 @@ describe('Markdown field component', () => { it('defaults to false', () => { createSubject(); - expect(findMarkdownToolbar().props('showContentEditorSwitcher')).toBe(false); + expect(findMarkdownHeader().props('showContentEditorSwitcher')).toBe(false); }); it('passes showContentEditorSwitcher', () => { createSubject({ showContentEditorSwitcher: true }); - expect(findMarkdownToolbar().props('showContentEditorSwitcher')).toBe(true); + expect(findMarkdownHeader().props('showContentEditorSwitcher')).toBe(true); }); }); }); diff --git a/spec/frontend/vue_shared/components/markdown/header_spec.js b/spec/frontend/vue_shared/components/markdown/header_spec.js index 68f05e5119d0d0f6e7e89ca984aa9de94b116792..48fe5452e7485f3d52625a8a3fdffa09f9e03fbc 100644 --- a/spec/frontend/vue_shared/components/markdown/header_spec.js +++ b/spec/frontend/vue_shared/components/markdown/header_spec.js @@ -1,10 +1,11 @@ import $ from 'jquery'; import { nextTick } from 'vue'; -import { GlTabs } from '@gitlab/ui'; +import { GlToggle } from '@gitlab/ui'; import HeaderComponent from '~/vue_shared/components/markdown/header.vue'; import ToolbarButton from '~/vue_shared/components/markdown/toolbar_button.vue'; import DrawioToolbarButton from '~/vue_shared/components/markdown/drawio_toolbar_button.vue'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import EditorModeSwitcher from '~/vue_shared/components/markdown/editor_mode_switcher.vue'; describe('Markdown field header component', () => { let wrapper; @@ -15,12 +16,11 @@ describe('Markdown field header component', () => { previewMarkdown: false, ...props, }, - stubs: { GlTabs }, + stubs: { GlToggle }, }); }; - const findWriteTab = () => wrapper.findByTestId('write-tab'); - const findPreviewTab = () => wrapper.findByTestId('preview-tab'); + const findPreviewToggle = () => wrapper.findByTestId('preview-toggle'); const findToolbar = () => wrapper.findByTestId('md-header-toolbar'); const findToolbarButtons = () => wrapper.findAllComponents(ToolbarButton); const findToolbarButtonByProp = (prop, value) => @@ -87,16 +87,14 @@ describe('Markdown field header component', () => { }); }); - it('activates `write` tab when previewMarkdown is false', () => { - expect(findWriteTab().attributes('active')).toBe('true'); - expect(findPreviewTab().attributes('active')).toBeUndefined(); + it('hides markdown preview when previewMarkdown is false', () => { + expect(findPreviewToggle().text()).toBe('Preview'); }); - it('activates `preview` tab when previewMarkdown is true', () => { + it('shows markdown preview when previewMarkdown is true', () => { createWrapper({ previewMarkdown: true }); - expect(findWriteTab().attributes('active')).toBeUndefined(); - expect(findPreviewTab().attributes('active')).toBe('true'); + expect(findPreviewToggle().text()).toBe('Continue editing'); }); it('hides toolbar in preview mode', () => { @@ -105,17 +103,16 @@ describe('Markdown field header component', () => { expect(findToolbar().classes().includes('gl-display-none!')).toBe(true); }); - it('emits toggle markdown event when clicking preview tab', async () => { - const eventData = { target: {} }; - findPreviewTab().vm.$emit('click', eventData); + it('emits toggle markdown event when clicking preview toggle', async () => { + findPreviewToggle().vm.$emit('click', true); await nextTick(); - expect(wrapper.emitted('preview-markdown').length).toEqual(1); + expect(wrapper.emitted('showPreview').length).toEqual(1); - findWriteTab().vm.$emit('click', eventData); + findPreviewToggle().vm.$emit('click', false); await nextTick(); - expect(wrapper.emitted('write-markdown').length).toEqual(1); + expect(wrapper.emitted('showPreview').length).toEqual(2); }); it('does not emit toggle markdown event when triggered from another form', () => { @@ -125,15 +122,8 @@ describe('Markdown field header component', () => { ), ]); - expect(wrapper.emitted('preview-markdown')).toBeUndefined(); - expect(wrapper.emitted('write-markdown')).toBeUndefined(); - }); - - it('blurs preview link after click', () => { - const target = { blur: jest.fn() }; - findPreviewTab().vm.$emit('click', { target }); - - expect(target.blur).toHaveBeenCalled(); + expect(wrapper.emitted('showPreview')).toBeUndefined(); + expect(wrapper.emitted('hidePreview')).toBeUndefined(); }); it('renders markdown table template', () => { @@ -166,12 +156,12 @@ describe('Markdown field header component', () => { expect(wrapper.find('.js-suggestion-btn').exists()).toBe(false); }); - it('hides preview tab when previewMarkdown property is false', () => { + it('hides markdown preview when previewMarkdown property is false', () => { createWrapper({ enablePreview: false, }); - expect(wrapper.findByTestId('preview-tab').exists()).toBe(false); + expect(wrapper.findByTestId('preview-toggle').exists()).toBe(false); }); describe('restricted tool bar items', () => { @@ -215,4 +205,18 @@ describe('Markdown field header component', () => { }); }); }); + + describe('with content editor switcher', () => { + beforeEach(() => { + createWrapper({ + showContentEditorSwitcher: true, + }); + }); + + it('re-emits event from switcher', () => { + wrapper.findComponent(EditorModeSwitcher).vm.$emit('input', 'richText'); + + expect(wrapper.emitted('enableContentEditor')).toEqual([[]]); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js index 63a689088c78c7ffa365febc4193b1518cf6cfab..dec2327db0f426b59eaaa0bf3cd9a54163b8f860 100644 --- a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js +++ b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js @@ -122,13 +122,13 @@ describe('vue_shared/component/markdown/markdown_editor', () => { it('enables content editor switcher when contentEditorEnabled prop is true', () => { buildWrapper({ propsData: { enableContentEditor: true } }); - expect(findMarkdownField().text()).toContain('Rich text'); + expect(findMarkdownField().text()).toContain('Switch to rich text'); }); it('hides content editor switcher when contentEditorEnabled prop is false', () => { buildWrapper({ propsData: { enableContentEditor: false } }); - expect(findMarkdownField().text()).not.toContain('Rich text'); + expect(findMarkdownField().text()).not.toContain('Switch to rich text'); }); it('passes down any additional props to markdown field component', () => { diff --git a/spec/frontend/vue_shared/components/markdown/toolbar_spec.js b/spec/frontend/vue_shared/components/markdown/toolbar_spec.js index fea14f80496a907febdbf39a7ea8e439c2f4c274..2489421b697194d458c4f12f18e9f2cb22dbea68 100644 --- a/spec/frontend/vue_shared/components/markdown/toolbar_spec.js +++ b/spec/frontend/vue_shared/components/markdown/toolbar_spec.js @@ -1,6 +1,5 @@ import { mount } from '@vue/test-utils'; import Toolbar from '~/vue_shared/components/markdown/toolbar.vue'; -import EditorModeDropdown from '~/vue_shared/components/markdown/editor_mode_dropdown.vue'; describe('toolbar', () => { let wrapper; @@ -44,18 +43,4 @@ describe('toolbar', () => { expect(wrapper.find('.comment-toolbar').exists()).toBe(true); }); }); - - describe('with content editor switcher', () => { - beforeEach(() => { - createMountedWrapper({ - showContentEditorSwitcher: true, - }); - }); - - it('re-emits event from switcher', () => { - wrapper.findComponent(EditorModeDropdown).vm.$emit('input', 'richText'); - - expect(wrapper.emitted('enableContentEditor')).toEqual([[]]); - }); - }); }); diff --git a/spec/support/helpers/content_editor_helpers.rb b/spec/support/helpers/content_editor_helpers.rb index 1bbc05cc05ac64213911150da205a3d053f54d9f..f19af0c9af8d459f38a67f51a9ef226098021c3c 100644 --- a/spec/support/helpers/content_editor_helpers.rb +++ b/spec/support/helpers/content_editor_helpers.rb @@ -2,8 +2,7 @@ module ContentEditorHelpers def switch_to_content_editor - click_button _('Editing markdown') - click_button _('Rich text') + click_button("Switch to rich text") end def type_in_content_editor(keys) diff --git a/spec/support/helpers/features/notes_helpers.rb b/spec/support/helpers/features/notes_helpers.rb index 78774b515df9aeb6f46352917e6177e331f427fc..7973d541f9c7b60066db812d423845dff1e327f2 100644 --- a/spec/support/helpers/features/notes_helpers.rb +++ b/spec/support/helpers/features/notes_helpers.rb @@ -41,7 +41,7 @@ def preview_note(text) wait_for_requests filled_text.send_keys(:escape) - click_on('Preview') + click_button("Preview") yield if block_given? end diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb index d2dfb468485893c566459166c67634962700cc54..2bcbd5e51905dd3386a91d2203a0b9a32c64a2b7 100644 --- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb +++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb @@ -104,8 +104,8 @@ fill_in 'merge_request_description', with: long_description height = get_textarea_height - find('.js-md-preview-button').click - find('.js-md-write-button').click + click_button("Preview") + click_button("Continue editing") new_height = get_textarea_height expect(height).to eq(new_height) diff --git a/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb b/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb index 7a3b94ad81df22987422a8e9e2e1315cd4f5bbd2..6451c531aecb0af1f4d708ddc792b73516db7627 100644 --- a/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb @@ -62,7 +62,7 @@ def attach_with_dropzone(wait = false) attach_with_dropzone(true) wait_for_requests - find('.js-md-preview-button').click + click_button("Preview") file_path = page.find('input[name="files[]"]', visible: :hidden).value link = page.find('a.no-attachment-icon')['href'] img_link = page.find('a.no-attachment-icon img')['src'] diff --git a/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb index 3e285bb8ad7920aae922b97462ee08d47bb72ba5..ca68df9a89ba475ea5d761dfec4f7671dbb51aef 100644 --- a/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb @@ -78,7 +78,7 @@ def relative_path(path) it_behaves_like 'relative links' do before do - click_on 'Preview' + click_button("Preview") end let(:element) { preview } @@ -88,7 +88,7 @@ def relative_path(path) # using two `\n` ensures we're sublist to it's own line due # to list auto-continue fill_in :wiki_content, with: "1. one\n\n - sublist\n" - click_on "Preview" + click_button("Preview") # the above generates two separate lists (not embedded) in CommonMark expect(preview).to have_content("sublist") @@ -102,7 +102,7 @@ def relative_path(path) [[also_do_not_linkify]] ``` HEREDOC - click_on "Preview" + click_button("Preview") expect(preview).to have_content("do_not_linkify") expect(preview).to have_content('[[do_not_linkify]]')