1<script>
2import { GlPopover, GlButton, GlTooltipDirective } from '@gitlab/ui';
3import $ from 'jquery';
4import { keysFor, BOLD_TEXT, ITALIC_TEXT, LINK_TEXT } from '~/behaviors/shortcuts/keybindings';
5import { getSelectedFragment } from '~/lib/utils/common_utils';
6import { s__ } from '~/locale';
7import { CopyAsGFM } from '../../../behaviors/markdown/copy_as_gfm';
8import ToolbarButton from './toolbar_button.vue';
9
10export default {
11  components: {
12    ToolbarButton,
13    GlPopover,
14    GlButton,
15  },
16  directives: {
17    GlTooltip: GlTooltipDirective,
18  },
19  props: {
20    previewMarkdown: {
21      type: Boolean,
22      required: true,
23    },
24    lineContent: {
25      type: String,
26      required: false,
27      default: '',
28    },
29    canSuggest: {
30      type: Boolean,
31      required: false,
32      default: true,
33    },
34    showSuggestPopover: {
35      type: Boolean,
36      required: false,
37      default: false,
38    },
39    suggestionStartIndex: {
40      type: Number,
41      required: false,
42      default: 0,
43    },
44  },
45  data() {
46    return {
47      tag: '> ',
48      suggestPopoverVisible: false,
49    };
50  },
51  computed: {
52    mdTable() {
53      return [
54        // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
55        '| header | header |', // eslint-disable-line @gitlab/require-i18n-strings
56        '| ------ | ------ |',
57        '| cell | cell |', // eslint-disable-line @gitlab/require-i18n-strings
58        '| cell | cell |', // eslint-disable-line @gitlab/require-i18n-strings
59      ].join('\n');
60    },
61    mdSuggestion() {
62      return [['```', `suggestion:-${this.suggestionStartIndex}+0`].join(''), `{text}`, '```'].join(
63        '\n',
64      );
65    },
66    mdCollapsibleSection() {
67      return ['<details><summary>Click to expand</summary>', `{text}`, '</details>'].join('\n');
68    },
69    isMac() {
70      // Accessing properties using ?. to allow tests to use
71      // this component without setting up window.gl.client.
72      // In production, window.gl.client should always be present.
73      return Boolean(window.gl?.client?.isMac);
74    },
75    modifierKey() {
76      return this.isMac ? '⌘' : s__('KeyboardKey|Ctrl+');
77    },
78  },
79  watch: {
80    showSuggestPopover() {
81      this.updateSuggestPopoverVisibility();
82    },
83  },
84  mounted() {
85    $(document).on('markdown-preview:show.vue', this.previewMarkdownTab);
86    $(document).on('markdown-preview:hide.vue', this.writeMarkdownTab);
87
88    this.updateSuggestPopoverVisibility();
89  },
90  beforeDestroy() {
91    $(document).off('markdown-preview:show.vue', this.previewMarkdownTab);
92    $(document).off('markdown-preview:hide.vue', this.writeMarkdownTab);
93  },
94  methods: {
95    async updateSuggestPopoverVisibility() {
96      await this.$nextTick();
97
98      this.suggestPopoverVisible = this.showSuggestPopover && this.canSuggest;
99    },
100    isValid(form) {
101      return (
102        !form ||
103        (form.find('.js-vue-markdown-field').length && $(this.$el).closest('form')[0] === form[0])
104      );
105    },
106
107    previewMarkdownTab(event, form) {
108      if (event.target.blur) event.target.blur();
109      if (!this.isValid(form)) return;
110
111      this.$emit('preview-markdown');
112    },
113
114    writeMarkdownTab(event, form) {
115      if (event.target.blur) event.target.blur();
116      if (!this.isValid(form)) return;
117
118      this.$emit('write-markdown');
119    },
120    handleSuggestDismissed() {
121      this.$emit('handleSuggestDismissed');
122    },
123    handleQuote() {
124      const documentFragment = getSelectedFragment();
125
126      if (!documentFragment || !documentFragment.textContent) {
127        this.tag = '> ';
128        return;
129      }
130      this.tag = '';
131
132      const transformed = CopyAsGFM.transformGFMSelection(documentFragment);
133      const area = this.$el.parentNode.querySelector('textarea');
134
135      CopyAsGFM.nodeToGFM(transformed)
136        .then((gfm) => {
137          CopyAsGFM.insertPastedText(area, documentFragment.textContent, CopyAsGFM.quoted(gfm));
138        })
139        .catch(() => {});
140    },
141  },
142  shortcuts: {
143    bold: keysFor(BOLD_TEXT),
144    italic: keysFor(ITALIC_TEXT),
145    link: keysFor(LINK_TEXT),
146  },
147};
148</script>
149
150<template>
151  <div class="md-header">
152    <ul class="nav-links clearfix">
153      <li :class="{ active: !previewMarkdown }" class="md-header-tab">
154        <button class="js-write-link" type="button" @click="writeMarkdownTab($event)">
155          {{ __('Write') }}
156        </button>
157      </li>
158      <li :class="{ active: previewMarkdown }" class="md-header-tab">
159        <button
160          class="js-preview-link js-md-preview-button"
161          type="button"
162          @click="previewMarkdownTab($event)"
163        >
164          {{ __('Preview') }}
165        </button>
166      </li>
167      <li :class="{ active: !previewMarkdown }" class="md-header-toolbar">
168        <toolbar-button
169          tag="**"
170          :button-title="
171            sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), { modifierKey })
172          "
173          :shortcuts="$options.shortcuts.bold"
174          icon="bold"
175        />
176        <toolbar-button
177          tag="_"
178          :button-title="
179            sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), { modifierKey })
180          "
181          :shortcuts="$options.shortcuts.italic"
182          icon="italic"
183        />
184        <toolbar-button
185          :prepend="true"
186          :tag="tag"
187          :button-title="__('Insert a quote')"
188          icon="quote"
189          @click="handleQuote"
190        />
191        <template v-if="canSuggest">
192          <toolbar-button
193            ref="suggestButton"
194            :tag="mdSuggestion"
195            :prepend="true"
196            :button-title="__('Insert suggestion')"
197            :cursor-offset="4"
198            :tag-content="lineContent"
199            icon="doc-code"
200            data-qa-selector="suggestion_button"
201            class="js-suggestion-btn"
202            @click="handleSuggestDismissed"
203          />
204          <gl-popover
205            v-if="suggestPopoverVisible"
206            :target="$refs.suggestButton.$el"
207            :css-classes="['diff-suggest-popover']"
208            placement="bottom"
209            :show="suggestPopoverVisible"
210          >
211            <strong>{{ __('New! Suggest changes directly') }}</strong>
212            <p class="mb-2">
213              {{
214                __(
215                  'Suggest code changes which can be immediately applied in one click. Try it out!',
216                )
217              }}
218            </p>
219            <gl-button
220              variant="info"
221              category="primary"
222              size="small"
223              @click="handleSuggestDismissed"
224            >
225              {{ __('Got it') }}
226            </gl-button>
227          </gl-popover>
228        </template>
229        <toolbar-button tag="`" tag-block="```" :button-title="__('Insert code')" icon="code" />
230        <toolbar-button
231          tag="[{text}](url)"
232          tag-select="url"
233          :button-title="
234            sprintf(s__('MarkdownEditor|Add a link (%{modifierKey}K)'), { modifierKey })
235          "
236          :shortcuts="$options.shortcuts.link"
237          icon="link"
238        />
239        <toolbar-button
240          :prepend="true"
241          tag="- "
242          :button-title="__('Add a bullet list')"
243          icon="list-bulleted"
244        />
245        <toolbar-button
246          :prepend="true"
247          tag="1. "
248          :button-title="__('Add a numbered list')"
249          icon="list-numbered"
250        />
251        <toolbar-button
252          :prepend="true"
253          tag="- [ ] "
254          :button-title="__('Add a task list')"
255          icon="list-task"
256        />
257        <toolbar-button
258          :tag="mdCollapsibleSection"
259          :prepend="true"
260          tag-select="Click to expand"
261          :button-title="__('Add a collapsible section')"
262          icon="details-block"
263        />
264        <toolbar-button
265          :tag="mdTable"
266          :prepend="true"
267          :button-title="__('Add a table')"
268          icon="table"
269        />
270        <toolbar-button
271          class="js-zen-enter"
272          :prepend="true"
273          :button-title="__('Go full screen')"
274          icon="maximize"
275        />
276      </li>
277    </ul>
278  </div>
279</template>
280