Skip to content

Commit

Permalink
feat: add syntax highlighting into markdown code block (#5389)
Browse files Browse the repository at this point in the history
  • Loading branch information
OEvgeny authored Jan 7, 2025
1 parent 5289ce3 commit ad222dd
Show file tree
Hide file tree
Showing 110 changed files with 917 additions and 311 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.react.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ rules:
react-hooks/rules-of-hooks: error
react-hooks/exhaustive-deps:
- warn
- additionalHooks: ^(useLiveRegion|useMemoized)$
- additionalHooks: ^(useLiveRegion|useMemoized|use(\w+)Updater)$

# Conflicts with Adaptive Card schema.
# react/forbid-prop-types: error
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/
- Configure HTML sanitizer via `request.allowedTags`
- Added support for math blocks using `$$` delimiter alongside existing `\[...\]` and `\(...\)` notations, in PR [#5381](https://github.com/microsoft/BotFramework-WebChat/pull/5381), by [@OEvgeny](https://github.com/OEvgeny)
- Added support for speech recognition initial silence timeout when using Azure Speech, in PR [#5400](https://github.com/microsoft/BotFramework/WebChat/pull/5400), by [@compulim](https://github.com/compulim)
- Introduced syntax highlighting for markdown code blocks, in PR [#5389](https://github.com/microsoft/BotFramework-WebChat/pull/5389), by [@OEvgeny](https://github.com/OEvgeny)

### 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.
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.
22 changes: 20 additions & 2 deletions __tests__/html/fluentTheme/side-by-side.wide.dark.html
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
};

const waveSvg = `data:image/svg+xml;utf8,${encodeURIComponent(`
<svg width="400" height="200" viewBox="0 0 400 200" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 50 400 100" xmlns="http://www.w3.org/2000/svg">
<!-- Primary Wave -->
<path d="M0,100 C50,50 100,150 150,100 C200,50 250,150 300,100 C350,50 400,150 400,100"
stroke="#3B82F6" fill="none" stroke-width="2" opacity="0.5"/>
Expand Down Expand Up @@ -540,6 +540,17 @@
}
}
], [
{
timestamp: timestamp(),
from: { "role": "user" },
id: "6.0",
text: `Help me to create a beautiful visualization of harmonic waves using Python, complete the following code:
\`\`\`python
def plot_sine_waves():
t = np.linspace(0, 10, 1000)
\`\`\``,
type: "message"
},
{
timestamp: timestamp(),
from: { role: 'bot' },
Expand Down Expand Up @@ -594,7 +605,12 @@
}],
id: "a4c0c01d-c06e-4dde-9278-265c607b545b-82",
type: "message",
text: `This example demonstrates creating a beautiful visualization of harmonic waves using Python's Matplotlib library. The code generates three sine waves with different frequencies and phases, then combines them to show wave interference patterns.\n<img alt="wave plot" src="${waveSvg}">`,
text: `This example demonstrates creating a visualization of harmonic waves using Python's Matplotlib library. The code generates three sine waves with different frequencies and phases, then combines them to show wave interference patterns.
<img alt="wave plot" src="${waveSvg}">
Use the command to install required dependencies:
$ pip install numpy matplotlib`
}
]];

Expand Down Expand Up @@ -704,6 +720,8 @@
await host.sendKeys('ARROW_UP');
await host.sendKeys('ENTER');
await host.snapshot();
await host.sendKeys('TAB');
await host.snapshot();
await host.sendKeys('ENTER');
await host.snapshot();
await host.sendKeys('ENTER');
Expand Down
22 changes: 20 additions & 2 deletions __tests__/html/fluentTheme/side-by-side.wide.html
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
};

const waveSvg = `data:image/svg+xml;utf8,${encodeURIComponent(`
<svg width="400" height="200" viewBox="0 0 400 200" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 50 400 100" xmlns="http://www.w3.org/2000/svg">
<!-- Primary Wave -->
<path d="M0,100 C50,50 100,150 150,100 C200,50 250,150 300,100 C350,50 400,150 400,100"
stroke="#3B82F6" fill="none" stroke-width="2" opacity="0.5"/>
Expand Down Expand Up @@ -550,6 +550,17 @@
}
}
], [
{
timestamp: timestamp(),
from: { "role": "user" },
id: "6.0",
text: `Help me to create a beautiful visualization of harmonic waves using Python, complete the following code:
\`\`\`python
def plot_sine_waves():
t = np.linspace(0, 10, 1000)
\`\`\``,
type: "message"
},
{
timestamp: timestamp(),
from: { role: 'bot' },
Expand Down Expand Up @@ -604,7 +615,12 @@
}],
id: "a4c0c01d-c06e-4dde-9278-265c607b545b-82",
type: "message",
text: `This example demonstrates creating a beautiful visualization of harmonic waves using Python's Matplotlib library. The code generates three sine waves with different frequencies and phases, then combines them to show wave interference patterns.\n<img alt="wave plot" src="${waveSvg}">`,
text: `This example demonstrates creating a visualization of harmonic waves using Python's Matplotlib library. The code generates three sine waves with different frequencies and phases, then combines them to show wave interference patterns.
<img alt="wave plot" src="${waveSvg}">
Use the command to install required dependencies:
$ pip install numpy matplotlib`
}
]];

Expand Down Expand Up @@ -687,6 +703,8 @@
await host.sendKeys('ARROW_UP');
await host.sendKeys('ENTER');
await host.snapshot();
await host.sendKeys('TAB');
await host.snapshot();
await host.sendKeys('ENTER');
await host.snapshot();
await host.sendKeys('ENTER');
Expand Down
5 changes: 0 additions & 5 deletions __tests__/html/toast.customizeDebounceTimeout.js

This file was deleted.

Binary file modified __tests__/html2/activity/viewCodeButton.html.snap-2.png
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!doctype html>
<html>
<head>
<script>
location = './layout?code-block-theme=github-dark-default';
</script>
</head>
<body></body>
</html>
68 changes: 68 additions & 0 deletions __tests__/html2/markdown/codeBlock/layout.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<!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>
<template id="messages">
<x-message snapshot-ignore>
```javascript
console.log('JavaScript code block using tripple ` delimiters');
```
----
Code block using indent
also has copy button
but no highlighting</x-message>
<x-message>
```javascript
export default function JavaScript(is, supported) { using shiki; }
```
```typescript
export default type TypeScript = (is, supported): { using: shiki; }
```
```python
def Python(is_supported): return {'using': 'shiki'}
```
</x-message>
</template>
<main id="webchat"></main>
<script>
run(async function () {
const {
WebChat: { renderWebChat, testIds }
} = window; // Imports in UMD fashion.

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

const params = new URLSearchParams(location.search);

renderWebChat({
directLine,
store,
styleOptions: {
codeBlockTheme: params.get('code-block-theme') ?? 'github-light-default'
}
}, document.getElementById('webchat'));

await pageConditions.uiConnected();

/** @type {HTMLElement[]} */
const messages = Array.from(window.messages.content.querySelectorAll('x-message'))
for (const message of messages) {
await directLine.emulateIncomingActivity({
text: message.innerText,
type: 'message'
});
if (!message.hasAttribute('snapshot-ignore')) {
await host.snapshot('local');
}
await pageConditions.numActivitiesShown(messages.indexOf(message) + 1);
}
});
</script>
</body>
</html>
115 changes: 115 additions & 0 deletions __tests__/html2/markdown/codeBlockCopyButton/behavior.highlighted.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<!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({
text: `Laboris ut proident dolore nisi sint ullamco proident veniam est.
\`\`\`python
import numpy as np
import matplotlib.pyplot as plt
def plot_sine_waves():
"""Create a beautiful visualization of sine waves with different frequencies."""
# Generate time points
t = np.linspace(0, 10, 1000)
\`\`\`
Ea sint elit anim enim voluptate aliquip aliqua nulla veniam.
<pre><code>Ea et pariatur sint Lorem ex veniam adipisicing.
Aliqua magna aliquip nisi quis.
</code></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="${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>
Binary file modified __tests__/html2/markdown/math/layout.spec.html.snap-1.png
Binary file modified __tests__/html2/markdown/math/layout.spec.html.snap-2.png
Binary file modified __tests__/html2/markdown/math/layout.spec.html.snap-3.png
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,12 @@
<main id="webchat"></main>
<script>
run(async function () {
const clock = lolex.createClock();
const { directLine, store } = testHelpers.createDirectLineEmulator();

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

WebChat.renderWebChat({ directLine, ponyfill: clock, store }, document.getElementById('webchat'));
WebChat.renderWebChat({ directLine, store }, document.getElementById('webchat'));

await pageConditions.webChatRendered();

clock.tick(600);

await pageConditions.uiConnected();

store.dispatch({
Expand All @@ -33,6 +29,7 @@

await pageConditions.toastShown(1);
await pageConditions.toastShown('Please read our privacy policy.');
await host.snapshot('local');

store.dispatch({
type: 'WEB_CHAT/SET_NOTIFICATION',
Expand All @@ -43,16 +40,9 @@
}
});

await pageConditions.toastShown(1);
await pageConditions.toastShown('Please read our privacy policy.');

clock.tick(400);

await pageConditions.toastShown('Please read our privacy policy.');

clock.tick(600);

await pageConditions.toastShown('Please read our privacy policy again.');
await host.snapshot('local');

store.dispatch({
type: 'WEB_CHAT/DISMISS_NOTIFICATION',
Expand All @@ -61,16 +51,8 @@
}
});

await pageConditions.toastShown(1);
await pageConditions.toastShown('Please read our privacy policy again.');

clock.tick(400);

await pageConditions.toastShown('Please read our privacy policy again.');

clock.tick(600);

await pageConditions.toastShown(0);
await host.snapshot('local');
});
</script>
</body>
Expand Down
Loading

0 comments on commit ad222dd

Please sign in to comment.