header.vue 7.55 KB
Newer Older
1
<script>
2
import $ from 'jquery';
3
import { GlPopover, GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
4
import { s__ } from '~/locale';
5 6
import { getSelectedFragment } from '~/lib/utils/common_utils';
import { CopyAsGFM } from '../../../behaviors/markdown/copy_as_gfm';
7
import ToolbarButton from './toolbar_button.vue';
8

9 10
export default {
  components: {
11
    ToolbarButton,
12
    GlIcon,
13
    GlPopover,
14
    GlButton,
15
  },
16 17 18
  directives: {
    GlTooltip: GlTooltipDirective,
  },
19 20 21 22
  props: {
    previewMarkdown: {
      type: Boolean,
      required: true,
Filipa Lacerda's avatar
Filipa Lacerda committed
23
    },
24 25 26 27 28 29 30 31 32 33
    lineContent: {
      type: String,
      required: false,
      default: '',
    },
    canSuggest: {
      type: Boolean,
      required: false,
      default: true,
    },
34 35 36 37 38
    showSuggestPopover: {
      type: Boolean,
      required: false,
      default: false,
    },
39
  },
40 41 42 43 44
  data() {
    return {
      tag: '> ',
    };
  },
45 46 47
  computed: {
    mdTable() {
      return [
48
        // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
49
        '| header | header |', // eslint-disable-line @gitlab/require-i18n-strings
50
        '| ------ | ------ |',
51 52
        '| cell | cell |', // eslint-disable-line @gitlab/require-i18n-strings
        '| cell | cell |', // eslint-disable-line @gitlab/require-i18n-strings
53
      ].join('\n');
Filipa Lacerda's avatar
Filipa Lacerda committed
54
    },
55
    mdSuggestion() {
Oswaldo Ferreira's avatar
Oswaldo Ferreira committed
56
      return ['```suggestion:-0+0', `{text}`, '```'].join('\n');
57
    },
58 59 60 61 62 63 64 65 66
    isMac() {
      // Accessing properties using ?. to allow tests to use
      // this component without setting up window.gl.client.
      // In production, window.gl.client should always be present.
      return Boolean(window.gl?.client?.isMac);
    },
    modifierKey() {
      return this.isMac ? '' : s__('KeyboardKey|Ctrl+');
    },
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
  },
  mounted() {
    $(document).on('markdown-preview:show.vue', this.previewMarkdownTab);
    $(document).on('markdown-preview:hide.vue', this.writeMarkdownTab);
  },
  beforeDestroy() {
    $(document).off('markdown-preview:show.vue', this.previewMarkdownTab);
    $(document).off('markdown-preview:hide.vue', this.writeMarkdownTab);
  },
  methods: {
    isValid(form) {
      return (
        !form ||
        (form.find('.js-vue-markdown-field').length && $(this.$el).closest('form')[0] === form[0])
      );
82
    },
83

84 85 86
    previewMarkdownTab(event, form) {
      if (event.target.blur) event.target.blur();
      if (!this.isValid(form)) return;
87

88 89
      this.$emit('preview-markdown');
    },
90

91 92 93
    writeMarkdownTab(event, form) {
      if (event.target.blur) event.target.blur();
      if (!this.isValid(form)) return;
94

95
      this.$emit('write-markdown');
96
    },
97 98 99
    handleSuggestDismissed() {
      this.$emit('handleSuggestDismissed');
    },
100 101 102 103 104 105 106 107 108 109
    handleQuote() {
      const documentFragment = getSelectedFragment();

      if (!documentFragment || !documentFragment.textContent) {
        this.tag = '> ';
        return;
      }
      this.tag = '';

      const transformed = CopyAsGFM.transformGFMSelection(documentFragment);
110
      const area = this.$el.parentNode.querySelector('textarea');
111 112 113 114 115 116 117

      CopyAsGFM.nodeToGFM(transformed)
        .then(gfm => {
          CopyAsGFM.insertPastedText(area, documentFragment.textContent, CopyAsGFM.quoted(gfm));
        })
        .catch(() => {});
    },
118 119
  },
};
120 121 122 123 124
</script>

<template>
  <div class="md-header">
    <ul class="nav-links clearfix">
Mike Greiling's avatar
Mike Greiling committed
125
      <li :class="{ active: !previewMarkdown }" class="md-header-tab">
126
        <button class="js-write-link" type="button" @click="writeMarkdownTab($event)">
127
          {{ __('Write') }}
128
        </button>
129
      </li>
Mike Greiling's avatar
Mike Greiling committed
130
      <li :class="{ active: previewMarkdown }" class="md-header-tab">
131
        <button
Felipe Artur's avatar
Felipe Artur committed
132
          class="js-preview-link js-md-preview-button"
133
          type="button"
134
          @click="previewMarkdownTab($event)"
Filipa Lacerda's avatar
Filipa Lacerda committed
135
        >
136
          {{ __('Preview') }}
137
        </button>
138
      </li>
Mike Greiling's avatar
Mike Greiling committed
139
      <li :class="{ active: !previewMarkdown }" class="md-header-toolbar">
140
        <div class="d-inline-block">
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
          <toolbar-button
            tag="**"
            :button-title="
              sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), { modifierKey })
            "
            :shortcuts="['command+b', 'ctrl+b']"
            icon="bold"
          />
          <toolbar-button
            tag="_"
            :button-title="
              sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), { modifierKey })
            "
            :shortcuts="['command+i', 'ctrl+i']"
            icon="italic"
          />
157 158
          <toolbar-button
            :prepend="true"
159
            :tag="tag"
160 161
            :button-title="__('Insert a quote')"
            icon="quote"
162
            @click="handleQuote"
163 164 165 166 167 168 169 170 171 172 173 174
          />
        </div>
        <div class="d-inline-block ml-md-2 ml-0">
          <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"
Mark Lapierre's avatar
Mark Lapierre committed
175
              class="js-suggestion-btn"
176 177 178
              @click="handleSuggestDismissed"
            />
            <gl-popover
179 180
              v-if="showSuggestPopover && $refs.suggestButton"
              :target="$refs.suggestButton"
181 182 183 184 185 186
              :css-classes="['diff-suggest-popover']"
              placement="bottom"
              :show="showSuggestPopover"
            >
              <strong>{{ __('New! Suggest changes directly') }}</strong>
              <p class="mb-2">
187 188 189 190 191
                {{
                  __(
                    'Suggest code changes which can be immediately applied in one click. Try it out!',
                  )
                }}
192
              </p>
193 194 195 196 197 198
              <gl-button
                variant="info"
                category="primary"
                size="sm"
                @click="handleSuggestDismissed"
              >
199
                {{ __('Got it') }}
200
              </gl-button>
201 202 203 204 205 206
            </gl-popover>
          </template>
          <toolbar-button tag="`" tag-block="```" :button-title="__('Insert code')" icon="code" />
          <toolbar-button
            tag="[{text}](url)"
            tag-select="url"
207 208 209 210
            :button-title="
              sprintf(s__('MarkdownEditor|Add a link (%{modifierKey}K)'), { modifierKey })
            "
            :shortcuts="['command+k', 'ctrl+k']"
211 212 213 214 215 216
            icon="link"
          />
        </div>
        <div class="d-inline-block ml-md-2 ml-0">
          <toolbar-button
            :prepend="true"
217
            tag="- "
218 219 220 221 222 223 224 225 226 227 228
            :button-title="__('Add a bullet list')"
            icon="list-bulleted"
          />
          <toolbar-button
            :prepend="true"
            tag="1. "
            :button-title="__('Add a numbered list')"
            icon="list-numbered"
          />
          <toolbar-button
            :prepend="true"
229
            tag="- [ ] "
230
            :button-title="__('Add a task list')"
231
            icon="list-task"
232 233 234 235 236 237 238 239 240 241 242 243 244 245
          />
          <toolbar-button
            :tag="mdTable"
            :prepend="true"
            :button-title="__('Add a table')"
            icon="table"
          />
        </div>
        <div class="d-inline-block ml-md-2 ml-0">
          <button
            v-gl-tooltip
            :aria-label="__('Go full screen')"
            class="toolbar-btn toolbar-fullscreen-btn js-zen-enter"
            data-container="body"
246
            tabindex="-1"
247 248 249
            :title="__('Go full screen')"
            type="button"
          >
250
            <gl-icon name="maximize" />
251 252
          </button>
        </div>
253 254 255 256
      </li>
    </ul>
  </div>
</template>