@gitlab/ui 68.6.0 → 68.8.0
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 +14 -0
- package/dist/components/base/banner/banner.js +5 -1
- package/dist/components/experimental/duo/chat/components/duo_chat_message/copy_code_element.js +35 -0
- package/dist/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.js +4 -0
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/tokens/css/tokens.css +1 -1
- package/dist/tokens/css/tokens.dark.css +1 -1
- package/dist/tokens/js/tokens.dark.js +1 -1
- package/dist/tokens/js/tokens.js +1 -1
- package/dist/tokens/scss/_tokens.dark.scss +1 -1
- package/dist/tokens/scss/_tokens.scss +1 -1
- package/package.json +3 -3
- package/src/components/base/banner/banner.vue +5 -1
- package/src/components/experimental/duo/chat/components/duo_chat_message/copy_code_element.js +44 -0
- package/src/components/experimental/duo/chat/components/duo_chat_message/copy_code_element.spec.js +64 -0
- package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.scss +15 -0
- package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.spec.js +10 -4
- package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.vue +4 -0
package/dist/tokens/js/tokens.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitlab/ui",
|
|
3
|
-
"version": "68.
|
|
3
|
+
"version": "68.8.0",
|
|
4
4
|
"description": "GitLab UI Components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -167,10 +167,10 @@
|
|
|
167
167
|
"storybook-dark-mode": "3.0.1",
|
|
168
168
|
"style-dictionary": "^3.8.0",
|
|
169
169
|
"stylelint": "15.10.2",
|
|
170
|
-
"vue": "2.7.
|
|
170
|
+
"vue": "2.7.15",
|
|
171
171
|
"vue-loader": "^15.8.3",
|
|
172
172
|
"vue-loader-vue3": "npm:vue-loader@17",
|
|
173
|
-
"vue-template-compiler": "2.7.
|
|
173
|
+
"vue-template-compiler": "2.7.15",
|
|
174
174
|
"vue-test-utils-compat": "^0.0.10",
|
|
175
175
|
"webpack": "^5.9.0"
|
|
176
176
|
},
|
|
@@ -109,7 +109,11 @@ export default {
|
|
|
109
109
|
<template>
|
|
110
110
|
<gl-card
|
|
111
111
|
class="gl-pl-6 gl-pr-8 gl-py-6"
|
|
112
|
-
:class="{
|
|
112
|
+
:class="{
|
|
113
|
+
'gl-banner-introduction': isIntroducing,
|
|
114
|
+
'gl-border-none!': embedded,
|
|
115
|
+
'gl-bg-gray-10!': !isIntroducing,
|
|
116
|
+
}"
|
|
113
117
|
body-class="gl-display-flex gl-p-0!"
|
|
114
118
|
>
|
|
115
119
|
<div v-if="svgPath" class="gl-banner-illustration">
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import iconsPath from '@gitlab/svgs/dist/icons.svg';
|
|
2
|
+
|
|
3
|
+
const createButton = () => {
|
|
4
|
+
const button = document.createElement('button');
|
|
5
|
+
button.type = 'button';
|
|
6
|
+
button.classList.add(
|
|
7
|
+
'btn',
|
|
8
|
+
'btn-default',
|
|
9
|
+
'btn-md',
|
|
10
|
+
'gl-button',
|
|
11
|
+
'btn-default-secondary',
|
|
12
|
+
'btn-icon'
|
|
13
|
+
);
|
|
14
|
+
button.dataset.title = 'Copy to clipboard';
|
|
15
|
+
|
|
16
|
+
// Create an SVG element with the correct namespace
|
|
17
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
18
|
+
svg.setAttribute('role', 'img');
|
|
19
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
20
|
+
svg.classList.add('gl-button-icon', 'gl-icon', 's16');
|
|
21
|
+
|
|
22
|
+
// Create a 'use' element with the correct namespace
|
|
23
|
+
const use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
|
|
24
|
+
use.setAttribute('href', `${iconsPath}#copy-to-clipboard`);
|
|
25
|
+
|
|
26
|
+
svg.appendChild(use);
|
|
27
|
+
button.appendChild(svg);
|
|
28
|
+
|
|
29
|
+
return button;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export class CopyCodeElement extends HTMLElement {
|
|
33
|
+
constructor() {
|
|
34
|
+
super();
|
|
35
|
+
const btn = createButton();
|
|
36
|
+
const wrapper = this.parentNode;
|
|
37
|
+
|
|
38
|
+
this.appendChild(btn);
|
|
39
|
+
btn.addEventListener('click', async () => {
|
|
40
|
+
const textToCopy = wrapper.innerText;
|
|
41
|
+
await navigator.clipboard.writeText(textToCopy);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/components/experimental/duo/chat/components/duo_chat_message/copy_code_element.spec.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { CopyCodeElement } from './copy_code_element';
|
|
2
|
+
|
|
3
|
+
describe('copy-code element', () => {
|
|
4
|
+
customElements.define('copy-code', CopyCodeElement);
|
|
5
|
+
const code = 'function sum(a, b) {\n return a + b;\n}';
|
|
6
|
+
const findCustomElement = () => document.querySelector('copy-code');
|
|
7
|
+
const findButton = () => document.querySelector('copy-code button');
|
|
8
|
+
const findButtonIcon = () => document.querySelector('copy-code button svg use');
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
document.body.innerHTML = `<div><pre><code>${code}</code></pre><copy-code></copy-code></div>`;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should create a button', () => {
|
|
15
|
+
expect(customElements.get('copy-code')).toBeDefined();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('does not setup shadowDom on the custom element', () => {
|
|
19
|
+
expect(findCustomElement().shadowRoot).toBeNull();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('adds a button to the DOM as a direct child', () => {
|
|
23
|
+
expect(findButton()).toBeDefined();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('adds the correct icon to the button', () => {
|
|
27
|
+
expect(findButtonIcon().getAttribute('href')).toContain('#copy-to-clipboard');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('interaction', () => {
|
|
31
|
+
let copiedText = '';
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
Object.defineProperty(HTMLElement.prototype, 'innerText', {
|
|
35
|
+
get() {
|
|
36
|
+
return this.textContent.trim();
|
|
37
|
+
},
|
|
38
|
+
configurable: true,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
global.navigator.clipboard = {
|
|
42
|
+
writeText: jest.fn().mockImplementation((text) => {
|
|
43
|
+
copiedText = text;
|
|
44
|
+
return Promise.resolve();
|
|
45
|
+
}),
|
|
46
|
+
readText: jest.fn().mockImplementation(() => Promise.resolve(copiedText)),
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
afterEach(() => {
|
|
51
|
+
// In JSDOM, `innerText` doesn't exist on the prototype.
|
|
52
|
+
// However, we can not set it to `undefined` as the property description should be an object
|
|
53
|
+
Object.defineProperty(HTMLElement.prototype, 'innerText', {});
|
|
54
|
+
|
|
55
|
+
jest.resetAllMocks();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('copies the content of the parentNode to the clipboard when the button is clicked', async () => {
|
|
59
|
+
findButton().click();
|
|
60
|
+
const text = await navigator.clipboard.readText();
|
|
61
|
+
expect(text).toBe(code);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.scss
CHANGED
|
@@ -15,4 +15,19 @@
|
|
|
15
15
|
p:last-of-type {
|
|
16
16
|
@include gl-mb-0;
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
copy-code {
|
|
20
|
+
@include gl-absolute;
|
|
21
|
+
@include gl-transition-medium;
|
|
22
|
+
@include gl-opacity-0;
|
|
23
|
+
@include gl-right-4;
|
|
24
|
+
@include gl-top-3;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.js-markdown-code.markdown-code-block:hover {
|
|
28
|
+
copy-code,
|
|
29
|
+
copy-code:focus-within {
|
|
30
|
+
@include gl-opacity-10;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
18
33
|
}
|
package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.spec.js
CHANGED
|
@@ -47,6 +47,12 @@ describe('DuoChatMessage', () => {
|
|
|
47
47
|
jest.clearAllMocks();
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
+
it('registers the custom `copy-code` element', () => {
|
|
51
|
+
expect(customElements.get('copy-code')).toBeUndefined();
|
|
52
|
+
createComponent();
|
|
53
|
+
expect(customElements.get('copy-code')).toBeDefined();
|
|
54
|
+
});
|
|
55
|
+
|
|
50
56
|
describe('rendering', () => {
|
|
51
57
|
beforeEach(() => {
|
|
52
58
|
renderMarkdown.mockImplementation(() => mockMarkdownContent);
|
|
@@ -79,6 +85,10 @@ describe('DuoChatMessage', () => {
|
|
|
79
85
|
});
|
|
80
86
|
});
|
|
81
87
|
|
|
88
|
+
it('renders the `copy-code` button for the code snippet', () => {
|
|
89
|
+
expect(findCopyCodeButton().exists()).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
82
92
|
it('renders the documentation sources component by default', () => {
|
|
83
93
|
expect(findDocumentSources().exists()).toBe(true);
|
|
84
94
|
expect(findDocumentSources().props('sources')).toEqual(MOCK_RESPONSE_MESSAGE.extras.sources);
|
|
@@ -107,10 +117,6 @@ describe('DuoChatMessage', () => {
|
|
|
107
117
|
findUserFeedback().vm.$emit('feedback', 'foo');
|
|
108
118
|
expect(wrapper.emitted('track-feedback')).toEqual([['foo']]);
|
|
109
119
|
});
|
|
110
|
-
|
|
111
|
-
it('does not strip out the <copy-code/> element from HTML output', () => {
|
|
112
|
-
expect(findCopyCodeButton().exists()).toBe(true);
|
|
113
|
-
});
|
|
114
120
|
});
|
|
115
121
|
|
|
116
122
|
describe('message output', () => {
|
package/src/components/experimental/duo/chat/components/duo_chat_message/duo_chat_message.vue
CHANGED
|
@@ -3,6 +3,7 @@ import GlDuoUserFeedback from '../../../user_feedback/user_feedback.vue';
|
|
|
3
3
|
import { SafeHtmlDirective as SafeHtml } from '../../../../../../directives/safe_html/safe_html';
|
|
4
4
|
import { MESSAGE_MODEL_ROLES } from '../../constants';
|
|
5
5
|
import DocumentationSources from '../duo_chat_message_sources/duo_chat_message_sources.vue';
|
|
6
|
+
import { CopyCodeElement } from './copy_code_element';
|
|
6
7
|
|
|
7
8
|
const concatIndicesUntilEmpty = (arr) => {
|
|
8
9
|
const start = arr.findIndex((el) => el);
|
|
@@ -66,6 +67,9 @@ export default {
|
|
|
66
67
|
* Is intentionally non-reactive
|
|
67
68
|
*/
|
|
68
69
|
this.messageChunks = [];
|
|
70
|
+
if (!customElements.get('copy-code')) {
|
|
71
|
+
customElements.define('copy-code', CopyCodeElement);
|
|
72
|
+
}
|
|
69
73
|
},
|
|
70
74
|
mounted() {
|
|
71
75
|
this.messageContent = this.content;
|