@gitlab/ui 66.35.0 → 66.35.2

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## [66.35.2](https://gitlab.com/gitlab-org/gitlab-ui/compare/v66.35.1...v66.35.2) (2023-10-24)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * rendering chunks after the final message ([b745c98](https://gitlab.com/gitlab-org/gitlab-ui/commit/b745c98cce77355e3efef2ec08867cb2e2cacb2a))
7
+ * **GlDiscreteChatter:** Fix popover handling ([3a9deb0](https://gitlab.com/gitlab-org/gitlab-ui/commit/3a9deb05e7fd9fd6f63ae18c4bed565170735038))
8
+
9
+ ## [66.35.1](https://gitlab.com/gitlab-org/gitlab-ui/compare/v66.35.0...v66.35.1) (2023-10-20)
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * **GlDuoChatMessage:** nextTick() when rendering GFM ([835900c](https://gitlab.com/gitlab-org/gitlab-ui/commit/835900cb9cf01e26b4e24120be3abc6f48c3c371))
15
+
1
16
  # [66.35.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v66.34.0...v66.35.0) (2023-10-20)
2
17
 
3
18
 
@@ -48,7 +48,7 @@ var script = {
48
48
  disableTooltip: {
49
49
  type: Boolean,
50
50
  required: false,
51
- default: () => false
51
+ default: false
52
52
  },
53
53
  /**
54
54
  * Sets the chart's height in pixels. Set to `"auto"` to use the height of the container.
@@ -91,7 +91,7 @@ var script = {
91
91
  },
92
92
  options() {
93
93
  const mergedOptions = merge({}, defaultChartOptions, {
94
- tooltip: {
94
+ tooltip: this.disableTooltip ? undefined : {
95
95
  formatter: this.onLabelChange
96
96
  },
97
97
  xAxis: {
@@ -127,9 +127,7 @@ var script = {
127
127
  },
128
128
  methods: {
129
129
  defaultFormatTooltipText(params) {
130
- const {
131
- data
132
- } = params;
130
+ const data = this.getChartData(params);
133
131
  const [title, content] = data;
134
132
  this.tooltipTitle = title;
135
133
  const seriesName = this.yAxisTitle;
@@ -152,9 +150,7 @@ var script = {
152
150
  },
153
151
  onLabelChange(params) {
154
152
  this.selectedFormatTooltipText(params);
155
- const {
156
- data = []
157
- } = params;
153
+ const data = this.getChartData(params);
158
154
  if (data.length) {
159
155
  const [left, top] = this.chart.convertToPixel('grid', data);
160
156
  this.tooltipPosition = {
@@ -162,6 +158,13 @@ var script = {
162
158
  top: `${top}px`
163
159
  };
164
160
  }
161
+ },
162
+ getChartData(_ref) {
163
+ let {
164
+ data
165
+ } = _ref;
166
+ const chartData = (data === null || data === void 0 ? void 0 : data.value) || data;
167
+ return Array.isArray(chartData) ? chartData : [];
165
168
  }
166
169
  },
167
170
  HEIGHT_AUTO_CLASSES
@@ -1,3 +1,4 @@
1
+ import { nextTick } from 'vue';
1
2
  import GlDuoUserFeedback from '../../../user_feedback/user_feedback';
2
3
  import { SafeHtmlDirective } from '../../../../../../directives/safe_html/safe_html';
3
4
  import { MESSAGE_MODEL_ROLES } from '../../constants';
@@ -32,7 +33,8 @@ var script = {
32
33
  },
33
34
  data() {
34
35
  return {
35
- messageContent: ''
36
+ messageContent: '',
37
+ messageWatcher: null
36
38
  };
37
39
  },
38
40
  computed: {
@@ -50,24 +52,10 @@ var script = {
50
52
  return this.message.contentHtml || this.renderMarkdown(this.message.content || this.message.errors.join('; '));
51
53
  }
52
54
  },
53
- watch: {
54
- message: {
55
- handler() {
56
- const {
57
- chunkId,
58
- content
59
- } = this.message;
60
- if (!chunkId) {
61
- this.messageChunks = [];
62
- this.messageContent = this.content;
63
- this.renderGFM(this.$refs.content);
64
- } else {
65
- this.messageChunks[chunkId] = content;
66
- this.messageContent = this.renderMarkdown(concatIndicesUntilEmpty(this.messageChunks));
67
- }
68
- },
55
+ created() {
56
+ this.messageWatcher = this.$watch('message', this.messageUpdateHandler, {
69
57
  deep: true
70
- }
58
+ });
71
59
  },
72
60
  beforeCreate() {
73
61
  /**
@@ -81,7 +69,28 @@ var script = {
81
69
  if (this.message.chunkId) {
82
70
  this.messageChunks[this.message.chunkId] = this.message.content;
83
71
  }
84
- this.renderGFM(this.$refs.content);
72
+ this.hydrateContentWithGFM();
73
+ },
74
+ methods: {
75
+ async hydrateContentWithGFM() {
76
+ await nextTick();
77
+ this.renderGFM(this.$refs.content);
78
+ },
79
+ async messageUpdateHandler() {
80
+ const {
81
+ chunkId,
82
+ content
83
+ } = this.message;
84
+ if (!chunkId) {
85
+ this.messageChunks = [];
86
+ this.messageContent = this.content;
87
+ this.messageWatcher();
88
+ this.hydrateContentWithGFM();
89
+ } else {
90
+ this.messageChunks[chunkId] = content;
91
+ this.messageContent = this.renderMarkdown(concatIndicesUntilEmpty(this.messageChunks));
92
+ }
93
+ }
85
94
  }
86
95
  };
87
96
 
@@ -80,5 +80,12 @@ const MOCK_USER_PROMPT_MESSAGE = {
80
80
  timestamp: '2021-04-21T12:00:00.000Z',
81
81
  extras: null
82
82
  };
83
+ const renderMarkdown = content => content;
84
+ const renderGFM = el => {
85
+ const codeBlock = el.querySelectorAll('.markdown-code-block');
86
+ codeBlock.forEach(block => {
87
+ block === null || block === void 0 ? void 0 : block.classList.add('gl-bg-purple-50', 'gl-p-3', 'gl-mb-3');
88
+ });
89
+ };
83
90
 
84
- export { MOCK_CHUNK_RESPONSE_MESSAGE, MOCK_RESPONSE_MESSAGE, MOCK_RESPONSE_MESSAGE_FOR_STREAMING, MOCK_USER_PROMPT_MESSAGE, generateMockResponseChunks };
91
+ export { MOCK_CHUNK_RESPONSE_MESSAGE, MOCK_RESPONSE_MESSAGE, MOCK_RESPONSE_MESSAGE_FOR_STREAMING, MOCK_USER_PROMPT_MESSAGE, generateMockResponseChunks, renderGFM, renderMarkdown };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Fri, 20 Oct 2023 08:03:38 GMT
3
+ * Generated on Tue, 24 Oct 2023 08:49:42 GMT
4
4
  */
5
5
 
6
6
  :root {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Fri, 20 Oct 2023 08:03:38 GMT
3
+ * Generated on Tue, 24 Oct 2023 08:49:43 GMT
4
4
  */
5
5
 
6
6
  :root.gl-dark {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Fri, 20 Oct 2023 08:03:38 GMT
3
+ * Generated on Tue, 24 Oct 2023 08:49:43 GMT
4
4
  */
5
5
 
6
6
  export const BLACK = "#fff";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Fri, 20 Oct 2023 08:03:38 GMT
3
+ * Generated on Tue, 24 Oct 2023 08:49:42 GMT
4
4
  */
5
5
 
6
6
  export const BLACK = "#000";
@@ -1,6 +1,6 @@
1
1
 
2
2
  // Do not edit directly
3
- // Generated on Fri, 20 Oct 2023 08:03:38 GMT
3
+ // Generated on Tue, 24 Oct 2023 08:49:43 GMT
4
4
 
5
5
  $red-950: #fff4f3;
6
6
  $red-900: #fcf1ef;
@@ -1,6 +1,6 @@
1
1
 
2
2
  // Do not edit directly
3
- // Generated on Fri, 20 Oct 2023 08:03:38 GMT
3
+ // Generated on Tue, 24 Oct 2023 08:49:43 GMT
4
4
 
5
5
  $gl-line-height-52: 3.25rem;
6
6
  $gl-line-height-44: 2.75rem;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "66.35.0",
3
+ "version": "66.35.2",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -98,18 +98,18 @@
98
98
  "@rollup/plugin-commonjs": "^11.1.0",
99
99
  "@rollup/plugin-node-resolve": "^7.1.3",
100
100
  "@rollup/plugin-replace": "^2.3.2",
101
- "@storybook/addon-a11y": "7.4.6",
102
- "@storybook/addon-docs": "7.4.6",
103
- "@storybook/addon-essentials": "7.4.6",
104
- "@storybook/addon-storyshots": "7.4.6",
105
- "@storybook/addon-storyshots-puppeteer": "7.4.6",
106
- "@storybook/addon-viewport": "7.4.6",
107
- "@storybook/builder-webpack5": "7.4.6",
108
- "@storybook/theming": "7.4.6",
109
- "@storybook/vue": "7.4.6",
110
- "@storybook/vue-webpack5": "7.4.6",
111
- "@storybook/vue3": "7.4.6",
112
- "@storybook/vue3-webpack5": "7.4.6",
101
+ "@storybook/addon-a11y": "7.5.1",
102
+ "@storybook/addon-docs": "7.5.1",
103
+ "@storybook/addon-essentials": "7.5.1",
104
+ "@storybook/addon-storyshots": "7.5.1",
105
+ "@storybook/addon-storyshots-puppeteer": "7.5.1",
106
+ "@storybook/addon-viewport": "7.5.1",
107
+ "@storybook/builder-webpack5": "7.5.1",
108
+ "@storybook/theming": "7.5.1",
109
+ "@storybook/vue": "7.5.1",
110
+ "@storybook/vue-webpack5": "7.5.1",
111
+ "@storybook/vue3": "7.5.1",
112
+ "@storybook/vue3-webpack5": "7.5.1",
113
113
  "@vue/compat": "^3.2.40",
114
114
  "@vue/compiler-sfc": "^3.2.40",
115
115
  "@vue/test-utils": "1.3.0",
@@ -122,11 +122,11 @@
122
122
  "babel-loader": "^8.0.5",
123
123
  "babel-plugin-require-context-hook": "^1.0.0",
124
124
  "bootstrap": "4.6.2",
125
- "cypress": "13.3.1",
125
+ "cypress": "13.3.2",
126
126
  "cypress-axe": "^1.4.0",
127
127
  "dompurify": "^3.0.0",
128
128
  "emoji-regex": "^10.0.0",
129
- "eslint": "8.51.0",
129
+ "eslint": "8.52.0",
130
130
  "eslint-import-resolver-jest": "3.0.2",
131
131
  "eslint-plugin-cypress": "2.15.1",
132
132
  "eslint-plugin-storybook": "0.6.15",
@@ -160,7 +160,7 @@
160
160
  "sass-loader": "^10.2.0",
161
161
  "sass-true": "^6.1.0",
162
162
  "start-server-and-test": "^1.10.6",
163
- "storybook": "7.4.6",
163
+ "storybook": "7.5.1",
164
164
  "storybook-dark-mode": "3.0.1",
165
165
  "style-dictionary": "^3.8.0",
166
166
  "stylelint": "15.10.2",
@@ -1,8 +1,10 @@
1
1
  import { shallowMount } from '@vue/test-utils';
2
+ import { nextTick } from 'vue';
2
3
  import { createMockChartInstance } from '~helpers/chart_stubs';
3
4
  import { expectHeightAutoClasses } from '~helpers/chart_height';
4
5
  import Chart from '../chart/chart.vue';
5
6
  import ChartTooltip from '../tooltip/tooltip.vue';
7
+ import TooltipDefaultFormat from '../../shared_components/charts/tooltip_default_format.vue';
6
8
  import DiscreteScatterChart from './discrete_scatter.vue';
7
9
 
8
10
  let mockChartInstance;
@@ -12,11 +14,21 @@ jest.mock('echarts', () => ({
12
14
  }));
13
15
 
14
16
  describe('column chart component', () => {
17
+ const mockData = {
18
+ x: '19 May',
19
+ y: 6.95,
20
+ };
21
+
22
+ const mockDataPixel = {
23
+ x: 1,
24
+ y: 2,
25
+ };
26
+
15
27
  const defaultChartProps = {
16
28
  xAxisTitle: 'x axis',
17
29
  yAxisTitle: 'y axis',
18
30
  xAxisType: 'category',
19
- data: [['19 May', 6.95]],
31
+ data: [[mockData.x, mockData.y]],
20
32
  };
21
33
  let wrapper;
22
34
 
@@ -30,6 +42,7 @@ describe('column chart component', () => {
30
42
 
31
43
  beforeEach(() => {
32
44
  mockChartInstance = createMockChartInstance();
45
+ mockChartInstance.convertToPixel.mockReturnValue([mockDataPixel.x, mockDataPixel.y]);
33
46
  });
34
47
 
35
48
  describe('height', () => {
@@ -40,19 +53,121 @@ describe('column chart component', () => {
40
53
  });
41
54
  });
42
55
 
43
- describe('disable-tooltip', () => {
44
- it('is set to false by default', async () => {
56
+ describe('tooltip formatter', () => {
57
+ let onLabelChange;
58
+
59
+ beforeEach(async () => {
45
60
  createComponent();
46
61
 
47
62
  await findChart().vm.$emit('created', mockChartInstance);
63
+ onLabelChange = findChart().props('options').tooltip.formatter;
64
+ });
65
+
66
+ describe.each([
67
+ {
68
+ params: {
69
+ data: [mockData.x, mockData.y],
70
+ },
71
+ descr: 'when data is an array',
72
+ },
73
+ {
74
+ params: {
75
+ data: { value: [mockData.x, mockData.y] },
76
+ },
77
+ descr: 'when data is an object',
78
+ },
79
+ ])('$descr', ({ params }) => {
80
+ it('sets the popover content', async () => {
81
+ onLabelChange(params);
82
+ await nextTick();
48
83
 
49
- expect(wrapper.findComponent(ChartTooltip).exists()).toBe(true);
84
+ expect(mockChartInstance.convertToPixel).toHaveBeenCalledTimes(1);
85
+ expect(mockChartInstance.convertToPixel).toHaveBeenLastCalledWith('grid', [
86
+ mockData.x,
87
+ mockData.y,
88
+ ]);
89
+
90
+ expect(wrapper.findComponent(ChartTooltip).props('left')).toBe(`${mockDataPixel.x}px`);
91
+ expect(wrapper.findComponent(ChartTooltip).props('top')).toBe(`${mockDataPixel.y}px`);
92
+ expect(wrapper.findComponent(TooltipDefaultFormat).props('tooltipContent')).toEqual({
93
+ 'y axis': { color: '', value: mockData.y },
94
+ });
95
+ });
50
96
  });
51
97
 
52
- it('disables the tooltip', () => {
53
- createComponent({ disableTooltip: true });
98
+ describe.each([
99
+ {
100
+ params: {},
101
+ descr: 'when data is missing',
102
+ },
103
+ {
104
+ params: {
105
+ data: [],
106
+ },
107
+ descr: 'when data is an empty array',
108
+ },
109
+ {
110
+ params: { data: 'foo' },
111
+ descr: 'when data is not an array',
112
+ },
113
+ {
114
+ params: {
115
+ data: { value: [] },
116
+ },
117
+ descr: 'when data.value is an empty array',
118
+ },
119
+ {
120
+ params: { data: { value: undefined } },
121
+ descr: 'when data.value is undefined',
122
+ },
123
+ {
124
+ params: { data: { value: 'foo' } },
125
+ descr: 'when data.value is not an array',
126
+ },
127
+ ])('$descr', ({ params }) => {
128
+ it('does not set the tooltip content', async () => {
129
+ onLabelChange(params);
130
+ await nextTick();
131
+
132
+ expect(mockChartInstance.convertToPixel).not.toHaveBeenCalled();
133
+ expect(wrapper.findComponent(ChartTooltip).props('left')).toBe('0');
134
+ expect(wrapper.findComponent(ChartTooltip).props('top')).toBe('0');
135
+ expect(wrapper.findComponent(TooltipDefaultFormat).props('tooltipContent')).toEqual({
136
+ 'y axis': { color: '', value: undefined },
137
+ });
138
+ });
139
+ });
140
+ });
141
+
142
+ describe('disable-tooltip', () => {
143
+ describe('when false', () => {
144
+ beforeEach(async () => {
145
+ createComponent();
146
+ await findChart().vm.$emit('created', mockChartInstance);
147
+ });
148
+
149
+ it('renders the ChartTooltip component', async () => {
150
+ expect(wrapper.findComponent(ChartTooltip).exists()).toBe(true);
151
+ });
152
+
153
+ it('sets chart tooltip options', () => {
154
+ expect(findChart().props('options').tooltip).not.toBeUndefined();
155
+ });
156
+ });
157
+
158
+ describe('when true', () => {
159
+ beforeEach(async () => {
160
+ createComponent({ disableTooltip: true });
161
+ await findChart().vm.$emit('created', mockChartInstance);
162
+ });
163
+
164
+ it('does not render the ChartTooltip component', async () => {
165
+ expect(wrapper.findComponent(ChartTooltip).exists()).toBe(false);
166
+ });
54
167
 
55
- expect(wrapper.findComponent(ChartTooltip).exists()).toBe(false);
168
+ it('does not set chart tooltip options', () => {
169
+ expect(findChart().props('options').tooltip).toBeUndefined();
170
+ });
56
171
  });
57
172
  });
58
173
  });
@@ -52,7 +52,7 @@ export default {
52
52
  disableTooltip: {
53
53
  type: Boolean,
54
54
  required: false,
55
- default: () => false,
55
+ default: false,
56
56
  },
57
57
  /**
58
58
  * Sets the chart's height in pixels. Set to `"auto"` to use the height of the container.
@@ -103,9 +103,11 @@ export default {
103
103
  {},
104
104
  defaultChartOptions,
105
105
  {
106
- tooltip: {
107
- formatter: this.onLabelChange,
108
- },
106
+ tooltip: this.disableTooltip
107
+ ? undefined
108
+ : {
109
+ formatter: this.onLabelChange,
110
+ },
109
111
  xAxis: {
110
112
  type: 'category',
111
113
  name: this.xAxisTitle,
@@ -142,7 +144,7 @@ export default {
142
144
  },
143
145
  methods: {
144
146
  defaultFormatTooltipText(params) {
145
- const { data } = params;
147
+ const data = this.getChartData(params);
146
148
  const [title, content] = data;
147
149
  this.tooltipTitle = title;
148
150
  const seriesName = this.yAxisTitle;
@@ -166,7 +168,7 @@ export default {
166
168
  onLabelChange(params) {
167
169
  this.selectedFormatTooltipText(params);
168
170
 
169
- const { data = [] } = params;
171
+ const data = this.getChartData(params);
170
172
 
171
173
  if (data.length) {
172
174
  const [left, top] = this.chart.convertToPixel('grid', data);
@@ -177,6 +179,10 @@ export default {
177
179
  };
178
180
  }
179
181
  },
182
+ getChartData({ data }) {
183
+ const chartData = data?.value || data;
184
+ return Array.isArray(chartData) ? chartData : [];
185
+ },
180
186
  },
181
187
  HEIGHT_AUTO_CLASSES,
182
188
  };
@@ -1,10 +1,13 @@
1
- import { MOCK_USER_PROMPT_MESSAGE, MOCK_RESPONSE_MESSAGE } from '../../mock_data';
1
+ import {
2
+ MOCK_USER_PROMPT_MESSAGE,
3
+ MOCK_RESPONSE_MESSAGE,
4
+ renderGFM,
5
+ renderMarkdown,
6
+ } from '../../mock_data';
2
7
  import GlDuoChatConversation from './duo_chat_conversation.vue';
3
8
  import readme from './duo_chat_conversation.md';
4
9
 
5
10
  const defaultValue = (prop) => GlDuoChatConversation.props[prop].default;
6
- const renderMarkdown = (content) => content;
7
- const renderGFM = () => {};
8
11
 
9
12
  const generateProps = ({ messages = [], showDelimiter = defaultValue('showDelimiter') } = {}) => ({
10
13
  messages,
@@ -109,8 +109,9 @@ describe('DuoChatMessage', () => {
109
109
  });
110
110
 
111
111
  describe('message output', () => {
112
- it('hydrates the message with GLFM when mounting the component', () => {
112
+ it('hydrates the message with GLFM when mounting the component', async () => {
113
113
  createComponent();
114
+ await nextTick();
114
115
  expect(renderGFM).toHaveBeenCalled();
115
116
  });
116
117
 
@@ -327,6 +328,7 @@ describe('DuoChatMessage', () => {
327
328
  message: chunk1,
328
329
  },
329
330
  });
331
+ await nextTick();
330
332
  renderGFM.mockClear();
331
333
  expect(renderGFM).not.toHaveBeenCalled();
332
334
 
@@ -340,6 +342,33 @@ describe('DuoChatMessage', () => {
340
342
  expect(renderGFM).not.toHaveBeenCalled();
341
343
  });
342
344
 
345
+ it('does not re-render when chunk is received after final message', async () => {
346
+ const finalMessageContent = content1 + content2;
347
+
348
+ // setProps is justified here because we are testing the component's
349
+ // reactive behavior which consistutes an exception
350
+ // See https://docs.gitlab.com/ee/development/fe_guide/style/vue.html#setting-component-state
351
+ await wrapper.setProps({
352
+ message: chunk1,
353
+ });
354
+ expect(findContent().text()).toBe(content1);
355
+
356
+ await wrapper.setProps({
357
+ message: {
358
+ ...MOCK_RESPONSE_MESSAGE,
359
+ content: finalMessageContent,
360
+ contentHtml: finalMessageContent,
361
+ chunkId: null,
362
+ },
363
+ });
364
+ expect(findContent().text()).toBe(finalMessageContent);
365
+
366
+ await wrapper.setProps({
367
+ message: chunk2,
368
+ });
369
+ expect(findContent().text()).toBe(finalMessageContent);
370
+ });
371
+
343
372
  it.each`
344
373
  content | contentHtml | errors | expectedContent
345
374
  ${'alpha'} | ${'beta'} | ${['foo', 'bar']} | ${'beta'}
@@ -1,10 +1,12 @@
1
- import { MOCK_RESPONSE_MESSAGE, MOCK_USER_PROMPT_MESSAGE } from '../../mock_data';
1
+ import {
2
+ MOCK_RESPONSE_MESSAGE,
3
+ MOCK_USER_PROMPT_MESSAGE,
4
+ renderGFM,
5
+ renderMarkdown,
6
+ } from '../../mock_data';
2
7
  import GlDuoChatMessage from './duo_chat_message.vue';
3
8
  import readme from './duo_chat_message.md';
4
9
 
5
- const renderMarkdown = (content) => content;
6
- const renderGFM = () => {};
7
-
8
10
  const generateProps = ({ message = MOCK_RESPONSE_MESSAGE } = {}) => ({
9
11
  message,
10
12
  });
@@ -1,4 +1,5 @@
1
1
  <script>
2
+ import { nextTick } from 'vue';
2
3
  import GlDuoUserFeedback from '../../../user_feedback/user_feedback.vue';
3
4
  import { SafeHtmlDirective as SafeHtml } from '../../../../../../directives/safe_html/safe_html';
4
5
  import { MESSAGE_MODEL_ROLES } from '../../constants';
@@ -34,6 +35,7 @@ export default {
34
35
  data() {
35
36
  return {
36
37
  messageContent: '',
38
+ messageWatcher: null,
37
39
  };
38
40
  },
39
41
  computed: {
@@ -53,21 +55,8 @@ export default {
53
55
  );
54
56
  },
55
57
  },
56
- watch: {
57
- message: {
58
- handler() {
59
- const { chunkId, content } = this.message;
60
- if (!chunkId) {
61
- this.messageChunks = [];
62
- this.messageContent = this.content;
63
- this.renderGFM(this.$refs.content);
64
- } else {
65
- this.messageChunks[chunkId] = content;
66
- this.messageContent = this.renderMarkdown(concatIndicesUntilEmpty(this.messageChunks));
67
- }
68
- },
69
- deep: true,
70
- },
58
+ created() {
59
+ this.messageWatcher = this.$watch('message', this.messageUpdateHandler, { deep: true });
71
60
  },
72
61
  beforeCreate() {
73
62
  /**
@@ -81,7 +70,26 @@ export default {
81
70
  if (this.message.chunkId) {
82
71
  this.messageChunks[this.message.chunkId] = this.message.content;
83
72
  }
84
- this.renderGFM(this.$refs.content);
73
+ this.hydrateContentWithGFM();
74
+ },
75
+ methods: {
76
+ async hydrateContentWithGFM() {
77
+ await nextTick();
78
+ this.renderGFM(this.$refs.content);
79
+ },
80
+ async messageUpdateHandler() {
81
+ const { chunkId, content } = this.message;
82
+ if (!chunkId) {
83
+ this.messageChunks = [];
84
+ this.messageContent = this.content;
85
+
86
+ this.messageWatcher();
87
+ this.hydrateContentWithGFM();
88
+ } else {
89
+ this.messageChunks[chunkId] = content;
90
+ this.messageContent = this.renderMarkdown(concatIndicesUntilEmpty(this.messageChunks));
91
+ }
92
+ },
85
93
  },
86
94
  };
87
95
  </script>
@@ -8,11 +8,10 @@ import {
8
8
  MOCK_RESPONSE_MESSAGE,
9
9
  MOCK_USER_PROMPT_MESSAGE,
10
10
  generateMockResponseChunks,
11
+ renderGFM,
12
+ renderMarkdown,
11
13
  } from './mock_data';
12
14
 
13
- const renderMarkdown = (content) => content;
14
- const renderGFM = () => {};
15
-
16
15
  const defaultValue = (prop) =>
17
16
  typeof GlDuoChat.props[prop].default === 'function'
18
17
  ? GlDuoChat.props[prop].default()
@@ -94,3 +94,11 @@ export const MOCK_USER_PROMPT_MESSAGE = {
94
94
  timestamp: '2021-04-21T12:00:00.000Z',
95
95
  extras: null,
96
96
  };
97
+
98
+ export const renderMarkdown = (content) => content;
99
+ export const renderGFM = (el) => {
100
+ const codeBlock = el.querySelectorAll('.markdown-code-block');
101
+ codeBlock.forEach((block) => {
102
+ block?.classList.add('gl-bg-purple-50', 'gl-p-3', 'gl-mb-3');
103
+ });
104
+ };