@gitlab/duo-ui 8.12.0 → 8.12.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.
- package/CHANGELOG.md +7 -0
- package/dist/components/chat/components/duo_chat_message/copy_code_element.js +32 -2
- package/dist/components/chat/components/duo_chat_message/insert_code_snippet_element.js +49 -24
- package/package.json +1 -1
- package/src/components/chat/components/duo_chat_message/copy_code_element.js +11 -3
- package/src/components/chat/components/duo_chat_message/insert_code_snippet_element.js +26 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [8.12.1](https://gitlab.com/gitlab-org/duo-ui/compare/v8.12.0...v8.12.1) (2025-04-09)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* make custom code buttons work when response html lacks code tag ([35ce601](https://gitlab.com/gitlab-org/duo-ui/commit/35ce60111be3494ba58a4da7dfe4f916cc488547))
|
|
7
|
+
|
|
1
8
|
# [8.12.0](https://gitlab.com/gitlab-org/duo-ui/compare/v8.11.0...v8.12.0) (2025-04-07)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -2,19 +2,49 @@ import { createButton } from './buttons_utils';
|
|
|
2
2
|
import { createTooltip } from './tooltips_utils';
|
|
3
3
|
import { checkClipboardPermissions } from './utils';
|
|
4
4
|
|
|
5
|
+
function _defineProperty(e, r, t) {
|
|
6
|
+
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
7
|
+
value: t,
|
|
8
|
+
enumerable: !0,
|
|
9
|
+
configurable: !0,
|
|
10
|
+
writable: !0
|
|
11
|
+
}) : e[r] = t, e;
|
|
12
|
+
}
|
|
13
|
+
function _toPrimitive(t, r) {
|
|
14
|
+
if ("object" != typeof t || !t) return t;
|
|
15
|
+
var e = t[Symbol.toPrimitive];
|
|
16
|
+
if (void 0 !== e) {
|
|
17
|
+
var i = e.call(t, r || "default");
|
|
18
|
+
if ("object" != typeof i) return i;
|
|
19
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
20
|
+
}
|
|
21
|
+
return ("string" === r ? String : Number)(t);
|
|
22
|
+
}
|
|
23
|
+
function _toPropertyKey(t) {
|
|
24
|
+
var i = _toPrimitive(t, "string");
|
|
25
|
+
return "symbol" == typeof i ? i : i + "";
|
|
26
|
+
}
|
|
27
|
+
|
|
5
28
|
class CopyCodeElement extends HTMLElement {
|
|
6
29
|
constructor() {
|
|
7
30
|
super();
|
|
31
|
+
_defineProperty(this, "getCodeElement", () => {
|
|
32
|
+
const wrapper = this.parentNode;
|
|
33
|
+
return wrapper.getElementsByTagName('code')[0] || wrapper.getElementsByTagName('pre')[0];
|
|
34
|
+
});
|
|
8
35
|
this.initialize();
|
|
9
36
|
}
|
|
10
37
|
async initialize() {
|
|
38
|
+
if (!this.getCodeElement()) return;
|
|
11
39
|
const btn = createButton('Copy to clipboard', 'copy-to-clipboard');
|
|
12
40
|
this.appendChild(btn);
|
|
13
41
|
const tooltip = await createTooltip(btn, 'Copy to clipboard');
|
|
14
42
|
this.appendChild(tooltip);
|
|
15
|
-
const wrapper = this.parentNode;
|
|
16
|
-
const [codeElement] = wrapper.getElementsByTagName('code');
|
|
17
43
|
btn.addEventListener('click', async () => {
|
|
44
|
+
// We fetch the code element on click instead because the structure of the HTML can change after
|
|
45
|
+
// the element is initialized (e.g. on sanitation), and we don't want to use an element that is
|
|
46
|
+
// not present in the DOM.
|
|
47
|
+
const codeElement = this.getCodeElement();
|
|
18
48
|
const textToCopy = codeElement.innerText;
|
|
19
49
|
const hasClipboardPermission = await checkClipboardPermissions();
|
|
20
50
|
try {
|
|
@@ -17,55 +17,80 @@ function _classPrivateFieldInitSpec(e, t, a) {
|
|
|
17
17
|
function _classPrivateFieldSet2(s, a, r) {
|
|
18
18
|
return s.set(_assertClassBrand(s, a), r), r;
|
|
19
19
|
}
|
|
20
|
-
function
|
|
21
|
-
e
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
function _defineProperty(e, r, t) {
|
|
21
|
+
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
22
|
+
value: t,
|
|
23
|
+
enumerable: !0,
|
|
24
|
+
configurable: !0,
|
|
25
|
+
writable: !0
|
|
26
|
+
}) : e[r] = t, e;
|
|
27
|
+
}
|
|
28
|
+
function _toPrimitive(t, r) {
|
|
29
|
+
if ("object" != typeof t || !t) return t;
|
|
30
|
+
var e = t[Symbol.toPrimitive];
|
|
31
|
+
if (void 0 !== e) {
|
|
32
|
+
var i = e.call(t, r || "default");
|
|
33
|
+
if ("object" != typeof i) return i;
|
|
34
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
35
|
+
}
|
|
36
|
+
return ("string" === r ? String : Number)(t);
|
|
37
|
+
}
|
|
38
|
+
function _toPropertyKey(t) {
|
|
39
|
+
var i = _toPrimitive(t, "string");
|
|
40
|
+
return "symbol" == typeof i ? i : i + "";
|
|
28
41
|
}
|
|
29
42
|
|
|
30
43
|
const CODE_MARKDOWN_CLASS = 'js-markdown-code';
|
|
31
44
|
var _actionButton = /*#__PURE__*/new WeakMap();
|
|
32
|
-
var
|
|
45
|
+
var _codeBlock = /*#__PURE__*/new WeakMap();
|
|
33
46
|
var _handleClick = /*#__PURE__*/new WeakMap();
|
|
34
47
|
class InsertCodeSnippetElement extends HTMLElement {
|
|
35
|
-
constructor(
|
|
48
|
+
constructor(_codeBlock2) {
|
|
36
49
|
super();
|
|
37
50
|
_classPrivateFieldInitSpec(this, _actionButton, void 0);
|
|
38
|
-
_classPrivateFieldInitSpec(this,
|
|
51
|
+
_classPrivateFieldInitSpec(this, _codeBlock, void 0);
|
|
39
52
|
_classPrivateFieldInitSpec(this, _handleClick, () => {
|
|
40
|
-
|
|
41
|
-
|
|
53
|
+
// We fetch the code element on click instead because the structure of the HTML can change after
|
|
54
|
+
// the element is initialized (e.g. on sanitation), and we don't want to use an element that is
|
|
55
|
+
// not present in the DOM.
|
|
56
|
+
const codeElement = this.getCodeElement(_classPrivateFieldGet2(_codeBlock, this));
|
|
57
|
+
if (codeElement) {
|
|
58
|
+
codeElement.dispatchEvent(new CustomEvent('insert-code-snippet', {
|
|
42
59
|
bubbles: true,
|
|
43
60
|
cancelable: true,
|
|
44
61
|
detail: {
|
|
45
|
-
code:
|
|
62
|
+
code: codeElement.textContent.trim()
|
|
46
63
|
}
|
|
47
64
|
}));
|
|
48
65
|
}
|
|
49
66
|
});
|
|
50
|
-
this
|
|
67
|
+
_defineProperty(this, "getCodeElement", codeBlock => {
|
|
68
|
+
const wrapper = codeBlock !== null && codeBlock !== void 0 ? codeBlock : this.closest(`.${CODE_MARKDOWN_CLASS}`);
|
|
69
|
+
return wrapper.getElementsByTagName('code')[0] || wrapper.getElementsByTagName('pre')[0];
|
|
70
|
+
});
|
|
71
|
+
this.initialize(_codeBlock2);
|
|
51
72
|
}
|
|
73
|
+
|
|
74
|
+
// we handle two possible cases here:
|
|
75
|
+
// 1. we use constructor parameter if the element is created in Javascript and inserted in the document
|
|
76
|
+
// 2. we find the wrapping element containing code if the element is received from the server
|
|
52
77
|
async initialize(codeBlock) {
|
|
78
|
+
if (!this.getCodeElement(codeBlock)) return;
|
|
79
|
+
_classPrivateFieldSet2(_codeBlock, this, codeBlock);
|
|
53
80
|
_classPrivateFieldSet2(_actionButton, this, createButton());
|
|
54
|
-
|
|
55
|
-
// we handle two possible cases here:
|
|
56
|
-
// 1. we use constructor parameter if the element is created in Javscript and inserted in the document
|
|
57
|
-
// 2. we find the wrapping element containing code if the element is received from the server
|
|
58
|
-
const wrapper = codeBlock !== null && codeBlock !== void 0 ? codeBlock : this.closest(`.${CODE_MARKDOWN_CLASS}`);
|
|
59
|
-
[_toSetter(_classPrivateFieldSet2, [_codeElement, this])._] = wrapper.getElementsByTagName('code');
|
|
60
81
|
const tooltip = await createTooltip(_classPrivateFieldGet2(_actionButton, this), 'Insert at cursor');
|
|
61
82
|
this.appendChild(tooltip);
|
|
62
83
|
}
|
|
63
84
|
connectedCallback() {
|
|
64
|
-
|
|
65
|
-
|
|
85
|
+
if (_classPrivateFieldGet2(_actionButton, this)) {
|
|
86
|
+
this.appendChild(_classPrivateFieldGet2(_actionButton, this));
|
|
87
|
+
_classPrivateFieldGet2(_actionButton, this).addEventListener('click', _classPrivateFieldGet2(_handleClick, this));
|
|
88
|
+
}
|
|
66
89
|
}
|
|
67
90
|
disconnectedCallback() {
|
|
68
|
-
|
|
91
|
+
if (_classPrivateFieldGet2(_actionButton, this)) {
|
|
92
|
+
_classPrivateFieldGet2(_actionButton, this).removeEventListener('click', _classPrivateFieldGet2(_handleClick, this));
|
|
93
|
+
}
|
|
69
94
|
}
|
|
70
95
|
}
|
|
71
96
|
|
package/package.json
CHANGED
|
@@ -9,16 +9,19 @@ export class CopyCodeElement extends HTMLElement {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
async initialize() {
|
|
12
|
+
if (!this.getCodeElement()) return;
|
|
13
|
+
|
|
12
14
|
const btn = createButton('Copy to clipboard', 'copy-to-clipboard');
|
|
13
15
|
this.appendChild(btn);
|
|
14
16
|
|
|
15
17
|
const tooltip = await createTooltip(btn, 'Copy to clipboard');
|
|
16
18
|
this.appendChild(tooltip);
|
|
17
19
|
|
|
18
|
-
const wrapper = this.parentNode;
|
|
19
|
-
const [codeElement] = wrapper.getElementsByTagName('code');
|
|
20
|
-
|
|
21
20
|
btn.addEventListener('click', async () => {
|
|
21
|
+
// We fetch the code element on click instead because the structure of the HTML can change after
|
|
22
|
+
// the element is initialized (e.g. on sanitation), and we don't want to use an element that is
|
|
23
|
+
// not present in the DOM.
|
|
24
|
+
const codeElement = this.getCodeElement();
|
|
22
25
|
const textToCopy = codeElement.innerText;
|
|
23
26
|
const hasClipboardPermission = await checkClipboardPermissions();
|
|
24
27
|
|
|
@@ -42,4 +45,9 @@ export class CopyCodeElement extends HTMLElement {
|
|
|
42
45
|
}
|
|
43
46
|
});
|
|
44
47
|
}
|
|
48
|
+
|
|
49
|
+
getCodeElement = () => {
|
|
50
|
+
const wrapper = this.parentNode;
|
|
51
|
+
return wrapper.getElementsByTagName('code')[0] || wrapper.getElementsByTagName('pre')[0];
|
|
52
|
+
};
|
|
45
53
|
}
|
|
@@ -6,34 +6,38 @@ const CODE_MARKDOWN_CLASS = 'js-markdown-code';
|
|
|
6
6
|
export class InsertCodeSnippetElement extends HTMLElement {
|
|
7
7
|
#actionButton;
|
|
8
8
|
|
|
9
|
-
#
|
|
9
|
+
#codeBlock;
|
|
10
10
|
|
|
11
11
|
constructor(codeBlock) {
|
|
12
12
|
super();
|
|
13
13
|
this.initialize(codeBlock);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
// we handle two possible cases here:
|
|
17
|
+
// 1. we use constructor parameter if the element is created in Javascript and inserted in the document
|
|
18
|
+
// 2. we find the wrapping element containing code if the element is received from the server
|
|
16
19
|
async initialize(codeBlock) {
|
|
17
|
-
|
|
20
|
+
if (!this.getCodeElement(codeBlock)) return;
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// 2. we find the wrapping element containing code if the element is received from the server
|
|
22
|
-
const wrapper = codeBlock ?? this.closest(`.${CODE_MARKDOWN_CLASS}`);
|
|
23
|
-
[this.#codeElement] = wrapper.getElementsByTagName('code');
|
|
22
|
+
this.#codeBlock = codeBlock;
|
|
23
|
+
this.#actionButton = createButton();
|
|
24
24
|
|
|
25
25
|
const tooltip = await createTooltip(this.#actionButton, 'Insert at cursor');
|
|
26
26
|
this.appendChild(tooltip);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
#handleClick = () => {
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
// We fetch the code element on click instead because the structure of the HTML can change after
|
|
31
|
+
// the element is initialized (e.g. on sanitation), and we don't want to use an element that is
|
|
32
|
+
// not present in the DOM.
|
|
33
|
+
const codeElement = this.getCodeElement(this.#codeBlock);
|
|
34
|
+
if (codeElement) {
|
|
35
|
+
codeElement.dispatchEvent(
|
|
32
36
|
new CustomEvent('insert-code-snippet', {
|
|
33
37
|
bubbles: true,
|
|
34
38
|
cancelable: true,
|
|
35
39
|
detail: {
|
|
36
|
-
code:
|
|
40
|
+
code: codeElement.textContent.trim(),
|
|
37
41
|
},
|
|
38
42
|
})
|
|
39
43
|
);
|
|
@@ -41,11 +45,20 @@ export class InsertCodeSnippetElement extends HTMLElement {
|
|
|
41
45
|
};
|
|
42
46
|
|
|
43
47
|
connectedCallback() {
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
if (this.#actionButton) {
|
|
49
|
+
this.appendChild(this.#actionButton);
|
|
50
|
+
this.#actionButton.addEventListener('click', this.#handleClick);
|
|
51
|
+
}
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
disconnectedCallback() {
|
|
49
|
-
this.#actionButton
|
|
55
|
+
if (this.#actionButton) {
|
|
56
|
+
this.#actionButton.removeEventListener('click', this.#handleClick);
|
|
57
|
+
}
|
|
50
58
|
}
|
|
59
|
+
|
|
60
|
+
getCodeElement = (codeBlock) => {
|
|
61
|
+
const wrapper = codeBlock ?? this.closest(`.${CODE_MARKDOWN_CLASS}`);
|
|
62
|
+
return wrapper.getElementsByTagName('code')[0] || wrapper.getElementsByTagName('pre')[0];
|
|
63
|
+
};
|
|
51
64
|
}
|