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

feat: add syntax highlighting into markdown code block #5389

Merged
merged 29 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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>
OEvgeny marked this conversation as resolved.
Show resolved Hide resolved
```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>
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/react@16.8.6/umd/react.production.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/react-dom@16.8.6/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
Loading