Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add code block copy button #5334

Merged
merged 37 commits into from
Oct 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
38ef52e
Add code block copy button
compulim Oct 25, 2024
54ec57f
Merge branch 'main' into feat-code-block-copy-button
compulim Oct 25, 2024
b3c157a
Add tests and accessibility
compulim Oct 25, 2024
776ebc6
Add snapshots
compulim Oct 25, 2024
69740d1
Add entry
compulim Oct 25, 2024
a589447
Simpler tag name
compulim Oct 25, 2024
1bad7de
Revert name
compulim Oct 25, 2024
8a3b46c
Move snapshot to local
compulim Oct 25, 2024
d9d2b59
Add Adaptive Cards test
compulim Oct 25, 2024
d8e4965
Use pre-wrap
compulim Oct 25, 2024
7b988b9
Change button to square
compulim Oct 25, 2024
af54b6f
Fix tests
compulim Oct 25, 2024
fddf9e0
Move props to HTML attributes
compulim Oct 26, 2024
00fa40f
Add test
compulim Oct 26, 2024
eda1232
Longer text
compulim Oct 26, 2024
90679e9
Move class outside
compulim Oct 26, 2024
2c33324
Add observed attributes
compulim Oct 26, 2024
6ca02c9
Cache observedAttributes
compulim Oct 26, 2024
3beb51d
Change attribute name to "class" from "className"
compulim Oct 26, 2024
5a8f0e1
Merge branch 'main' into feat-code-block-copy-button
compulim Oct 26, 2024
9b36420
Anonymous class
compulim Oct 26, 2024
bdd8c09
Add static
compulim Oct 26, 2024
1e149cb
Add margin before button
compulim Oct 26, 2024
afb9b95
Use inline-end
compulim Oct 26, 2024
dd93d68
Use right
compulim Oct 26, 2024
aff7d54
Fix tests
compulim Oct 26, 2024
693348d
Fix screenshots
compulim Oct 26, 2024
b766eed
Fix screenshots
compulim Oct 26, 2024
95ff06c
FIx screenshots
compulim Oct 26, 2024
6d81131
Fix screenshots
compulim Oct 26, 2024
b3984f4
Fix tests
compulim Oct 26, 2024
51a7c62
Update icon
compulim Oct 26, 2024
7962478
Update icon
compulim Oct 26, 2024
b5dbbca
Simplify
compulim Oct 26, 2024
5d86cb8
Generalize custom element registration
compulim Oct 26, 2024
ccc358c
Create React wrapper for custom element
compulim Oct 26, 2024
cedc26f
No root element
compulim Oct 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/
- (Experimental) Added more CSS variables support, in PR [#5321](https://github.com/microsoft/BotFramework-WebChat/pull/5321), by [@compulim](https://github.com/compulim)
- Added MathML/TeX block support in Markdown via [`micromark-extension-math`](https://npmjs.com/package/micromark-extension-math) and [`katex`](https://katex.org/), in PR [#5332](https://github.com/microsoft/BotFramework-WebChat/pull/5332), by [@compulim](https://github.com/compulim)
- Added code viewer dialog with syntax highlighting, in PR [#5335](https://github.com/microsoft/BotFramework-WebChat/pull/5335), by [@OEvgeny](https://github.com/OEvgeny)
- Added copy button to code blocks, in PR [#5334](https://github.com/microsoft/BotFramework-WebChat/pull/5334), by [@compulim](https://github.com/compulim)

### Changed

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<!doctype html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script crossorigin="anonymous" src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<script crossorigin="anonymous" src="/test-harness.js"></script>
<script crossorigin="anonymous" src="/test-page-object.js"></script>
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
</head>
<body>
<main id="webchat" style="position: relative"></main>
<script type="text/babel">
run(async function () {
await host.sendDevToolsCommand('Browser.setPermission', {
permission: { name: 'clipboard-write' },
setting: 'granted'
});

await expect(navigator.permissions.query({ name: 'clipboard-write' })).resolves.toHaveProperty(
'state',
'granted'
);

const {
ReactDOM: { render },
WebChat: { ReactWebChat, testIds }
} = window; // Imports in UMD fashion.

const { directLine, store } = testHelpers.createDirectLineEmulator();

const App = () => (
<React.Fragment>
<ReactWebChat directLine={directLine} store={store} />
<div style={{ gap: 8, position: 'absolute', top: 0, width: '100%' }}>
<label>
<div>Paste box</div>
<textarea
data-testid="paste box"
spellCheck={false}
style={{ background: '#f0f0f0', border: 0, height: 100, padding: 0, resize: 'none', width: '100%' }}
/>
</label>
</div>
</React.Fragment>
);

render(<App />, document.getElementById('webchat'));

await pageConditions.uiConnected();

await directLine.emulateIncomingActivity({
attachments: [
{
content: {
type: 'AdaptiveCard',
$schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
version: '1.5',
body: [
{
style: 'heading',
type: 'TextBlock',
text: 'This is an Adaptive Card'
},
{
type: 'TextBlock',
text: `Laboris ut proident dolore nisi sint ullamco proident veniam est.

\`\`\`
In do pariatur id enim nisi adipisicing incididunt consectetur do velit quis aliqua ad.

Et proident est fugiat duis exercitation qui sunt amet ipsum.
\`\`\`

Ea sint elit anim enim voluptate aliquip aliqua nulla veniam.

<pre>
Ea et pariatur sint Lorem ex veniam adipisicing.

Aliqua magna aliquip nisi quis.
</pre>

Cupidatat nulla duis dolor nulla ut pariatur minim incididunt quis adipisicing velit id Lorem.`,
wrap: true
}
]
},
contentType: 'application/vnd.microsoft.card.adaptive'
}
],
type: 'message'
});

await pageConditions.numActivitiesShown(1);

// WHEN: Focus on the "Copy" button via keyboard.
await host.click(document.querySelector(`[data-testid="${WebChat.testIds.sendBoxTextBox}"]`));
await host.sendShiftTab(3);
await host.sendKeys('ENTER');

// THEN: Should focus on the "Copy" button
const copyButton = document.querySelector(`[data-testid="${WebChat.testIds.codeBlockCopyButton}"]`);

expect(document.activeElement).toBe(copyButton);
await host.snapshot('local');

// WHEN: Press ENTER on the "Copy" button.
await host.sendKeys('ENTER');

// THEN: The copy button should change to "Copied".
await host.snapshot('local');

// WHEN: Paste into plain text and rich text text box.
await host.click(document.querySelector('[data-testid="paste box"]'));
await host.sendKeys('+CONTROL', 'v', '-CONTROL');

await host.click(document.querySelector(`[data-testid="${WebChat.testIds.sendBoxTextBox}"]`));

// Sleep for 1 second for the checkmark to go away.
await testHelpers.sleep(500);

// WHEN: Hiding Web Chat and showing it back.
document.getElementById('webchat').style.display = 'none';
document.body.offsetWidth; // Need for browser to refresh the layout.
document.getElementById('webchat').style.display = '';

// THEN: The "Copy" button should kept at normal.
await host.snapshot('local');
});
</script>
</body>
</html>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<!doctype html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script crossorigin="anonymous" src="/test-harness.js"></script>
<script crossorigin="anonymous" src="/test-page-object.js"></script>
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
</head>

<body>
<main id="webchat"></main>
<script>
run(async function () {
await host.sendDevToolsCommand('Browser.setPermission', {
permission: { name: 'clipboard-write' },
setting: 'granted'
});

await expect(navigator.permissions.query({ name: 'clipboard-write' })).resolves.toHaveProperty(
'state',
'granted'
);

const {
WebChat: { renderWebChat, testIds }
} = window; // Imports in UMD fashion.

const { directLine, store } = testHelpers.createDirectLineEmulator();

renderWebChat({ directLine, store }, document.getElementById('webchat'));

await pageConditions.uiConnected();

await directLine.emulateIncomingActivity({
attachments: [
{
content: {
type: 'AdaptiveCard',
$schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
version: '1.5',
body: [
{
style: 'heading',
type: 'TextBlock',
text: 'This is an Adaptive Card'
},
{
type: 'TextBlock',
text: `Laboris ut proident dolore nisi sint ullamco proident veniam est.

\`\`\`
In do pariatur id enim nisi adipisicing incididunt consectetur do velit quis aliqua ad.

Et proident est fugiat duis exercitation qui sunt amet ipsum.
\`\`\`

Ea sint elit anim enim voluptate aliquip aliqua nulla veniam.

<pre>
Ea et pariatur sint Lorem ex veniam adipisicing.

Aliqua magna aliquip nisi quis.
</pre>

Cupidatat nulla duis dolor nulla ut pariatur minim incididunt quis adipisicing velit id Lorem.`,
wrap: true
}
]
},
contentType: 'application/vnd.microsoft.card.adaptive'
}
],
type: 'message'
});

await pageConditions.numActivitiesShown(1);

// THEN: Should show 2 copy button, one for triple-backtick codeblock, one for <pre> codeblock.
expect(
pageElements.activities()[0].querySelectorAll(`[data-testid="${testIds.codeBlockCopyButton}"]`)
).toHaveLength(2);

await host.snapshot('local');
});
</script>
</body>
</html>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
103 changes: 103 additions & 0 deletions __tests__/html2/markdown/codeBlockCopyButton/behavior.denied.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<!doctype html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script crossorigin="anonymous" src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<script crossorigin="anonymous" src="/test-harness.js"></script>
<script crossorigin="anonymous" src="/test-page-object.js"></script>
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
</head>
<body>
<main id="webchat" style="position: relative"></main>
<script type="text/babel">
run(async function () {
await host.sendDevToolsCommand('Browser.setPermission', {
permission: { name: 'clipboard-write' },
setting: 'denied'
});

await expect(navigator.permissions.query({ name: 'clipboard-write' })).resolves.toHaveProperty(
'state',
'denied'
);

const {
ReactDOM: { render },
WebChat: { ReactWebChat, testIds }
} = window; // Imports in UMD fashion.

const { directLine, store } = testHelpers.createDirectLineEmulator();

const App = () => (
<React.Fragment>
<ReactWebChat directLine={directLine} store={store} />
<div style={{ gap: 8, position: 'absolute', top: 0, width: '100%' }}>
<label>
<div>Paste box</div>
<textarea
data-testid="paste box"
spellCheck={false}
style={{ background: '#f0f0f0', border: 0, height: 100, padding: 0, resize: 'none', width: '100%' }}
/>
</label>
</div>
</React.Fragment>
);

render(<App />, document.getElementById('webchat'));

await pageConditions.uiConnected();

await directLine.emulateIncomingActivity({
text: `Laboris ut proident dolore nisi sint ullamco proident veniam est.

\`\`\`
In do pariatur id enim nisi adipisicing incididunt consectetur do velit quis aliqua ad.

Et proident est fugiat duis exercitation qui sunt amet ipsum.
\`\`\`

Ea sint elit anim enim voluptate aliquip aliqua nulla veniam.

<pre>
Ea et pariatur sint Lorem ex veniam adipisicing.

Aliqua magna aliquip nisi quis.
</pre>

Cupidatat nulla duis dolor nulla ut pariatur minim incididunt quis adipisicing velit id Lorem.`,
type: 'message'
});

await pageConditions.numActivitiesShown(1);

// WHEN: Focus on the "Copy" button via keyboard.
await host.click(document.querySelector(`[data-testid="${testIds.sendBoxTextBox}"]`));
await host.sendShiftTab(3);
await host.sendKeys('ENTER');

// THEN: Should focus on the "Copy" button
const copyButton = document.querySelector(`[data-testid="${testIds.codeBlockCopyButton}"]`);

expect(document.activeElement).toBe(copyButton);
await host.snapshot('local');

// WHEN: Press ENTER on the "Copy" button.
await host.sendKeys('ENTER');

// THEN: The copy button should be disabled.
expect(copyButton.getAttribute('aria-disabled')).toBe('true');
await host.snapshot('local');

// WHEN: Press TAB twice.
await host.sendTab(2);

// THEN: It should stay on the second copy button.
expect(document.activeElement).toBe(document.querySelectorAll(`[data-testid="${testIds.codeBlockCopyButton}"]`)[1]);
await host.snapshot('local');
});
</script>
</body>
</html>
Loading
Loading