@gitlab/ui 95.0.0 → 95.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/bin/migrate_custom_utils_to_tw.bundled.mjs +0 -19
  3. package/dist/components/experimental/duo/chat/components/duo_chat_context/constants.js +4 -1
  4. package/dist/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_details_modal/duo_chat_context_item_details_modal.js +120 -0
  5. package/dist/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu.js +11 -1
  6. package/dist/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.js +29 -2
  7. package/dist/components/experimental/duo/chat/components/duo_chat_context/mock_context_data.js +14 -1
  8. package/dist/components/experimental/duo/chat/components/duo_chat_conversation/duo_chat_conversation.js +4 -1
  9. package/dist/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.js +7 -1
  10. package/dist/components/experimental/duo/chat/duo_chat.js +9 -1
  11. package/dist/index.css +1 -1
  12. package/dist/index.css.map +1 -1
  13. package/package.json +1 -1
  14. package/src/components/base/dropdown/dropdown.scss +1 -1
  15. package/src/components/base/dropdown/dropdown_section_header.scss +1 -1
  16. package/src/components/experimental/duo/chat/components/duo_chat_context/constants.js +4 -0
  17. package/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_details_modal/duo_chat_context_item_details_modal.vue +114 -0
  18. package/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu.vue +10 -0
  19. package/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.vue +42 -0
  20. package/src/components/experimental/duo/chat/components/duo_chat_context/mock_context_data.js +15 -0
  21. package/src/components/experimental/duo/chat/components/duo_chat_conversation/duo_chat_conversation.vue +4 -0
  22. package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.vue +8 -0
  23. package/src/components/experimental/duo/chat/duo_chat.vue +9 -0
  24. package/translations.js +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "95.0.0",
3
+ "version": "95.1.1",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -41,7 +41,7 @@
41
41
  @apply gl-border-1 gl-border-b-solid gl-border-dropdown;
42
42
  @apply gl-flex;
43
43
  @apply gl-text-base;
44
- @apply gl-text-heading;
44
+ @apply gl-text-strong;
45
45
  @apply gl-font-bold;
46
46
  @apply gl-justify-center;
47
47
  @apply gl-leading-normal;
@@ -1,6 +1,6 @@
1
1
  @mixin gl-tmp-dropdown-section-header-style {
2
2
  @apply gl-text-base;
3
- @apply gl-text-heading;
3
+ @apply gl-text-strong;
4
4
  @apply gl-leading-normal;
5
5
  @apply gl-text-left;
6
6
  @apply gl-font-bold;
@@ -5,3 +5,7 @@ export const CONTEXT_ITEM_CATEGORY_LOCAL_GIT = 'local_git';
5
5
 
6
6
  export const CONTEXT_ITEM_LOCAL_GIT_COMMIT = 'commit';
7
7
  export const CONTEXT_ITEM_LOCAL_GIT_DIFF = 'diff';
8
+
9
+ export const LANGUAGE_IDENTIFIER_PREFIX = 'language-';
10
+ export const LANGUAGE_IDENTIFIER_DIFF = 'language-diff';
11
+ export const LANGUAGE_IDENTIFIER_PLAINTEXT = 'language-plaintext';
@@ -0,0 +1,114 @@
1
+ <script>
2
+ import { nextTick } from 'vue';
3
+ import { contextItemValidator } from '../utils';
4
+ import GlModal from '../../../../../../base/modal/modal.vue';
5
+ import { SafeHtmlDirective as SafeHtml } from '../../../../../../../directives/safe_html/safe_html';
6
+ import GlSkeletonLoader from '../../../../../../base/skeleton_loader/skeleton_loader.vue';
7
+ import { translate } from '../../../../../../../utils/i18n';
8
+ import {
9
+ CONTEXT_ITEM_CATEGORY_LOCAL_GIT,
10
+ LANGUAGE_IDENTIFIER_DIFF,
11
+ LANGUAGE_IDENTIFIER_PLAINTEXT,
12
+ LANGUAGE_IDENTIFIER_PREFIX,
13
+ } from '../constants';
14
+
15
+ export default {
16
+ name: 'GlDuoChatContextItemDetailsModal',
17
+ components: {
18
+ GlSkeletonLoader,
19
+ GlModal,
20
+ },
21
+ directives: {
22
+ SafeHtml,
23
+ },
24
+ inject: {
25
+ renderGFM: {
26
+ from: 'renderGFM',
27
+ default: () => (element) => {
28
+ element.classList.add('gl-markdown', 'gl-compact-markdown');
29
+ },
30
+ },
31
+ },
32
+ props: {
33
+ /**
34
+ * Context items to preview. If it has no `content`, the loading state will be displayed.
35
+ */
36
+ contextItem: {
37
+ type: Object,
38
+ required: true,
39
+ validator: contextItemValidator,
40
+ },
41
+ },
42
+ computed: {
43
+ isLoadingContent() {
44
+ return this.contextItem.content === undefined;
45
+ },
46
+ languageIdentifierClass() {
47
+ if (this.contextItem.category === CONTEXT_ITEM_CATEGORY_LOCAL_GIT) {
48
+ return LANGUAGE_IDENTIFIER_DIFF;
49
+ }
50
+
51
+ const fileExtension = this.contextItem.metadata?.relativePath?.split('.').at(-1);
52
+ if (fileExtension && fileExtension !== this.contextItem.metadata?.relativePath) {
53
+ return `${LANGUAGE_IDENTIFIER_PREFIX}${fileExtension}`;
54
+ }
55
+
56
+ return LANGUAGE_IDENTIFIER_PLAINTEXT;
57
+ },
58
+ title() {
59
+ return (
60
+ this.contextItem.metadata?.title ||
61
+ this.contextItem.metadata?.relativePath ||
62
+ translate('GlDuoChatContextItemDetailsModal.title', 'Preview')
63
+ );
64
+ },
65
+ },
66
+ watch: {
67
+ contextItem: {
68
+ async handler(newVal, oldVal) {
69
+ const shouldFormat = newVal?.content !== oldVal?.content && newVal?.content;
70
+ if (shouldFormat) {
71
+ await nextTick();
72
+ await this.hydrateContentWithGFM();
73
+ }
74
+ },
75
+ immediate: true,
76
+ },
77
+ },
78
+ methods: {
79
+ async hydrateContentWithGFM() {
80
+ await nextTick();
81
+
82
+ if (this.$refs.content) {
83
+ this.renderGFM(this.$refs.content);
84
+ }
85
+ },
86
+ onModalVisibilityChange(isVisible) {
87
+ if (!isVisible) {
88
+ this.$emit('close');
89
+ }
90
+ },
91
+ },
92
+ };
93
+ </script>
94
+
95
+ <template>
96
+ <gl-modal
97
+ modal-id="context-item-details-modal"
98
+ :title="title"
99
+ :visible="true"
100
+ :scrollable="true"
101
+ hide-footer
102
+ size="lg"
103
+ @change="onModalVisibilityChange"
104
+ >
105
+ <gl-skeleton-loader v-if="isLoadingContent" />
106
+ <div v-else ref="content" data-testid="context-item-content">
107
+ <pre
108
+ v-safe-html="contextItem.content"
109
+ class="code js-syntax-highlight gl-p-3"
110
+ :class="languageIdentifierClass"
111
+ ></pre>
112
+ </div>
113
+ </gl-modal>
114
+ </template>
@@ -218,6 +218,14 @@ export default {
218
218
 
219
219
  this.activeIndex = newIndex;
220
220
  },
221
+ onGetContextItemContent(contextItem) {
222
+ /**
223
+ * Emit get-context-item-content event that tells clients to load the full file content for a selected context item.
224
+ * The fully hydrated context item should be updated in the context item selections.
225
+ * @param {*} event An event containing the context item to hydrate
226
+ */
227
+ this.$emit('get-context-item-content', { contextItem });
228
+ },
221
229
  },
222
230
  i18n: {
223
231
  selectedContextItemsTitle: translate(
@@ -233,11 +241,13 @@ export default {
233
241
  <gl-duo-chat-context-item-selections
234
242
  v-if="selections.length"
235
243
  :selections="selections"
244
+ :categories="categories"
236
245
  :removable="true"
237
246
  :title="$options.i18n.selectedContextItemsTitle"
238
247
  :default-collapsed="false"
239
248
  class="gl-mb-3"
240
249
  @remove="removeItem"
250
+ @get-content="onGetContextItemContent"
241
251
  />
242
252
  <gl-card
243
253
  v-if="open"
@@ -4,12 +4,15 @@ import GlIcon from '../../../../../../base/icon/icon.vue';
4
4
  import GlToken from '../../../../../../base/token/token.vue';
5
5
  import GlTruncate from '../../../../../../utilities/truncate/truncate.vue';
6
6
  import GlDuoChatContextItemPopover from '../duo_chat_context_item_popover/duo_chat_context_item_popover.vue';
7
+ import { CONTEXT_ITEM_CATEGORY_FILE, CONTEXT_ITEM_CATEGORY_LOCAL_GIT } from '../constants';
8
+ import GlDuoChatContextItemDetailsModal from '../duo_chat_context_item_details_modal/duo_chat_context_item_details_modal.vue';
7
9
  import { contextItemsValidator, getContextItemIcon } from '../utils';
8
10
 
9
11
  export default {
10
12
  name: 'GlDuoChatContextItemSelections',
11
13
  components: {
12
14
  GlTruncate,
15
+ GlDuoChatContextItemDetailsModal,
13
16
  GlIcon,
14
17
  GlDuoChatContextItemPopover,
15
18
  GlToken,
@@ -55,6 +58,7 @@ export default {
55
58
  return {
56
59
  isCollapsed: this.defaultCollapsed,
57
60
  selectionsId: uniqueId(),
61
+ previewContextItemId: null,
58
62
  };
59
63
  },
60
64
  computed: {
@@ -73,6 +77,9 @@ export default {
73
77
  }
74
78
  return '';
75
79
  },
80
+ contextItemPreview() {
81
+ return this.selections.find((item) => item.id === this.previewContextItemId);
82
+ },
76
83
  },
77
84
  methods: {
78
85
  getContextItemIcon,
@@ -86,6 +93,30 @@ export default {
86
93
  */
87
94
  this.$emit('remove', contextItem);
88
95
  },
96
+ onOpenItem(event, contextItem) {
97
+ const isKeypressOnCloseButton =
98
+ event.type === 'keydown' && event.target !== event.currentTarget;
99
+ if (isKeypressOnCloseButton) {
100
+ // don't respond to events triggered by the gl-token children (e.g. the close button)
101
+ return;
102
+ }
103
+
104
+ if (!this.canOpen(contextItem)) {
105
+ return;
106
+ }
107
+ if (!contextItem.content) {
108
+ this.$emit('get-content', contextItem);
109
+ }
110
+ this.previewContextItemId = contextItem.id;
111
+ },
112
+ canOpen(contextItem) {
113
+ return [CONTEXT_ITEM_CATEGORY_LOCAL_GIT, CONTEXT_ITEM_CATEGORY_FILE].includes(
114
+ contextItem.category
115
+ );
116
+ },
117
+ onClosePreview() {
118
+ this.previewContextItemId = null;
119
+ },
89
120
  },
90
121
  };
91
122
  </script>
@@ -114,6 +145,11 @@ export default {
114
145
  variant="default"
115
146
  class="gl-mb-2 gl-mr-2 gl-max-w-full"
116
147
  :class="tokenVariantClasses"
148
+ :tabindex="canOpen(item) ? 0 : -1"
149
+ :role="canOpen(item) ? 'button' : undefined"
150
+ @click="onOpenItem($event, item)"
151
+ @keydown.enter="onOpenItem($event, item)"
152
+ @keydown.space.prevent="onOpenItem($event, item)"
117
153
  @close="onRemoveItem(item)"
118
154
  >
119
155
  <div
@@ -132,8 +168,14 @@ export default {
132
168
  :context-item="item"
133
169
  :target="`context-item-${item.id}-${selectionsId}-token`"
134
170
  placement="bottom"
171
+ @show-git-diff="onOpenItem(item)"
135
172
  />
136
173
  </gl-token>
137
174
  </div>
175
+ <gl-duo-chat-context-item-details-modal
176
+ v-if="contextItemPreview"
177
+ :context-item="contextItemPreview"
178
+ @close="onClosePreview"
179
+ />
138
180
  </div>
139
181
  </template>
@@ -16,6 +16,21 @@ export function getMockCategory(categoryValue) {
16
16
  return MOCK_CATEGORIES.find((cat) => cat.value === categoryValue);
17
17
  }
18
18
 
19
+ export const MOCK_CONTEXT_FILE_CONTENT = `export function waterPlants() {
20
+ console.log('sprinkle');
21
+ }`;
22
+
23
+ export const MOCK_CONTEXT_FILE_DIFF_CONTENT = `diff --git a/src/plants/strawberry.ts b/src/plants/strawberry.ts
24
+ index 1234567..8901234 100644
25
+ --- a/src/plants/strawberry.ts
26
+ +++ b/src/plants/strawberry.ts
27
+ @@ -1,4 +1,4 @@
28
+ export const strawberry = {
29
+ name: 'Strawberry',
30
+ - waterNeeds: 'moderate',
31
+ + waterNeeds: 'high',
32
+ };`;
33
+
19
34
  export const MOCK_CONTEXT_ITEM_FILE = {
20
35
  id: '123e4567-e89b-12d3-a456-426614174000',
21
36
  category: CONTEXT_ITEM_CATEGORY_FILE,
@@ -57,6 +57,9 @@ export default {
57
57
  onInsertCodeSnippet(e) {
58
58
  this.$emit('insert-code-snippet', e);
59
59
  },
60
+ onGetContextItemContent(e) {
61
+ this.$emit('get-context-item-content', e);
62
+ },
60
63
  },
61
64
  i18n,
62
65
  };
@@ -81,6 +84,7 @@ export default {
81
84
  :is-cancelled="canceledRequestIds.includes(msg.requestId)"
82
85
  @track-feedback="onTrackFeedback"
83
86
  @insert-code-snippet="onInsertCodeSnippet"
87
+ @get-context-item-content="onGetContextItemContent"
84
88
  />
85
89
  </div>
86
90
  </template>
@@ -234,6 +234,12 @@ export default {
234
234
  onInsertCodeSnippet(e) {
235
235
  this.$emit('insert-code-snippet', e);
236
236
  },
237
+ onGetContextItemContent(contextItem) {
238
+ this.$emit('get-context-item-content', {
239
+ messageId: this.message.id,
240
+ contextItem,
241
+ });
242
+ },
237
243
  },
238
244
  };
239
245
  </script>
@@ -264,6 +270,7 @@ export default {
264
270
  :title="selectedContextItemsTitle"
265
271
  :default-collapsed="selectedContextItemsDefaultCollapsed"
266
272
  variant="assistant"
273
+ @get-content="onGetContextItemContent"
267
274
  />
268
275
  <div
269
276
  v-if="error"
@@ -309,6 +316,7 @@ export default {
309
316
  :title="selectedContextItemsTitle"
310
317
  :default-collapsed="selectedContextItemsDefaultCollapsed"
311
318
  variant="user"
319
+ @get-content="onGetContextItemContent"
312
320
  />
313
321
  </div>
314
322
  </div>
@@ -518,6 +518,14 @@ export default {
518
518
  */
519
519
  this.$emit('insert-code-snippet', e);
520
520
  },
521
+ onGetContextItemContent(event) {
522
+ /**
523
+ * Emit get-context-item-content event that tells clients to load the full file content for a selected context item.
524
+ * The fully hydrated context item should be updated in the chat message context item.
525
+ * @param {*} event An event containing the message ID and context item to hydrate
526
+ */
527
+ this.$emit('get-context-item-content', event);
528
+ },
521
529
  closeContextItemsMenuOpen() {
522
530
  this.contextItemsMenuIsOpen = false;
523
531
  this.setPromptAndFocus();
@@ -603,6 +611,7 @@ export default {
603
611
  :show-delimiter="index > 0"
604
612
  @track-feedback="onTrackFeedback"
605
613
  @insert-code-snippet="onInsertCodeSnippet"
614
+ @get-context-item-content="onGetContextItemContent"
606
615
  />
607
616
  <template v-if="!hasMessages && !isLoading">
608
617
  <gl-empty-state
package/translations.js CHANGED
@@ -27,6 +27,7 @@ export default {
27
27
  'GlDuoChat.chatPromptPlaceholderDefault': 'GitLab Duo Chat',
28
28
  'GlDuoChat.chatPromptPlaceholderWithCommands': 'Type "/" for slash commands',
29
29
  'GlDuoChat.chatSubmitLabel': 'Send chat message.',
30
+ 'GlDuoChatContextItemDetailsModal.title': 'Preview',
30
31
  'GlDuoChatContextItemMenu.emptyStateMessage': 'No results found',
31
32
  'GlDuoChatContextItemMenu.loadingMessage': 'Loading...',
32
33
  'GlDuoChatContextItemMenu.searchInputPlaceholder': 'Search %{categoryLabel}...',