Skip to content
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@ Breaking changes in this release:
- Resolved [#5463](https://github.com/microsoft/BotFramework-WebChat/issues/5463). Added attachment preview for `sendAttachmentOn: "send"`, in PR [#5464](https://github.com/microsoft/BotFramework-WebChat/pull/5464), by [@compulim](https://github.com/compulim), in PR [#5492](https://github.com/microsoft/BotFramework-WebChat/pull/5492), by [@OEvgeny](https://github.com/OEvgeny)
- Attaching files will no longer remove previously attached files
- Updated Fluent theme to use the new attachment preview feature
- Added collapsible activity and activity with abstract handling, in PR [#5506](https://github.com/microsoft/BotFramework-WebChat/pull/5506), in PR [#5513](https://github.com/microsoft/BotFramework-WebChat/pull/5513), by [@OEvgeny](https://github.com/OEvgeny)
- Added collapsible activity and activity with abstract handling, in PR [#5506](https://github.com/microsoft/BotFramework-WebChat/pull/5506), in PR [#5513](https://github.com/microsoft/BotFramework-WebChat/pull/5513), in PR [#5771](https://github.com/microsoft/BotFramework-WebChat/pull/5771), by [@OEvgeny](https://github.com/OEvgeny)
- Added `styleOptions.partGroupDefaultOpen` to configure whether part groups are open by default, defaults to `true`
- Added `styleOptions.referenceListDefaultOpen` to configure whether references are open by default, defaults to `true`
- In the Fluent theme "copilot" variant, part groups and references now default to closed
- Added `disableFileUpload` flag to completelly disable file upload feature, in PR [#5508](https://github.com/microsoft/BotFramework-WebChat/pull/5508), by [@JamesNewbyAtMicrosoft](https://github.com/JamesNewbyAtMicrosoft)
- Deprecated `hideUploadButton` in favor of `disableFileUpload`.
- Updated `BasicSendBoxToolbar` to rely solely on `disableFileUpload`.
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.
10 changes: 9 additions & 1 deletion __tests__/html2/activity/citation.longRef.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
run(async function () {
const { directLine, store } = testHelpers.createDirectLineEmulator();

renderWebChat(
const { isCopilotVariant } = renderWebChat(
{
directLine,
store
Expand Down Expand Up @@ -70,6 +70,14 @@

expect(markdownButtons[0].getAttribute('type')).toBe('button');

// When (copilot): opening the link definitions
if (isCopilotVariant) {
await host.click(pageElements.linkDefinitions()[0].querySelector('summary'));

// Then (copilot): the link definitions should be opened
await host.snapshot('local');
}

const linkDefinitionItems = pageElements.linkDefinitions()[0].querySelectorAll('[role="listitem"] > *');

expect(linkDefinitionItems[0].getAttribute('href')).toBe(
Expand Down
10 changes: 10 additions & 0 deletions __tests__/html2/citation/accordion.copilot.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!doctype html>
<html>
<head>
<title>Citation accordion (copilot)</title>
<script>
location = './accordion?variant=copilot';
</script>
</head>
<body></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.
61 changes: 52 additions & 9 deletions __tests__/html2/citation/accordion.html
Original file line number Diff line number Diff line change
@@ -1,23 +1,66 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script type="importmap">
{
"imports": {
"@fluentui/react-provider": "/__dist__/packages/test/test-assets/out/@fluentui/react-provider.js",
"@fluentui/tokens": "/__dist__/packages/test/test-assets/out/@fluentui/tokens.js",
"react": "https://esm.sh/react@18",
"react-dom": "https://esm.sh/react-dom@18",
"react-dom/": "https://esm.sh/react-dom@18/",
"react-jsx-runtime": "https://esm.sh/react@18/jsx-runtime"
}
}
</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>
<script type="module">
import React from 'react';
window.React = React;
</script>
<script defer crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
<script defer crossorigin="anonymous" src="/__dist__/botframework-webchat-fluent-theme.production.min.js"></script>
<style type="text/css">
.fui-FluentProvider {
height: 100%;
}
</style>
</head>
<body>
<main id="webchat"></main>
<script>
<script type="module">
import { FluentProvider } from '@fluentui/react-provider';
import { webLightTheme } from '@fluentui/tokens';
import React from 'react';
import { createRoot } from 'react-dom/client';

run(async function () {
const {
WebChat: { FluentThemeProvider, ReactWebChat }
} = window;

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

WebChat.renderWebChat(
{
directLine,
store
},
document.getElementById('webchat')
const searchParams = new URLSearchParams(location.search);
const variant = searchParams.get('variant');

if (variant) {
window.checkAccessibility = async () => {};
}

const root = createRoot(document.getElementById('webchat'));
const webChatProps = { directLine, store };

root.render(
variant
? React.createElement(
FluentProvider,
{ className: 'fui-FluentProvider', theme: webLightTheme },
React.createElement(FluentThemeProvider, { variant }, React.createElement(ReactWebChat, webChatProps))
)
: React.createElement(ReactWebChat, webChatProps)
);

await pageConditions.uiConnected();
Expand Down
10 changes: 10 additions & 0 deletions __tests__/html2/citation/showModal.linkDefinitions.copilot.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!doctype html>
<html>
<head>
<title>Citation show modal link definitions (copilot)</title>
<script>
location = './showModal.linkDefinitions?variant=copilot';
</script>
</head>
<body></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.
69 changes: 60 additions & 9 deletions __tests__/html2/citation/showModal.linkDefinitions.html
Original file line number Diff line number Diff line change
@@ -1,23 +1,66 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script type="importmap">
{
"imports": {
"@fluentui/react-provider": "/__dist__/packages/test/test-assets/out/@fluentui/react-provider.js",
"@fluentui/tokens": "/__dist__/packages/test/test-assets/out/@fluentui/tokens.js",
"react": "https://esm.sh/react@18",
"react-dom": "https://esm.sh/react-dom@18",
"react-dom/": "https://esm.sh/react-dom@18/",
"react-jsx-runtime": "https://esm.sh/react@18/jsx-runtime"
}
}
</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>
<script type="module">
import React from 'react';
window.React = React;
</script>
<script defer crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
<script defer crossorigin="anonymous" src="/__dist__/botframework-webchat-fluent-theme.production.min.js"></script>
<style type="text/css">
.fui-FluentProvider {
height: 100%;
}
</style>
</head>
<body>
<main id="webchat"></main>
<script>
<script type="module">
import { FluentProvider } from '@fluentui/react-provider';
import { webLightTheme } from '@fluentui/tokens';
import React from 'react';
import { createRoot } from 'react-dom/client';

run(async function () {
const {
WebChat: { FluentThemeProvider, ReactWebChat }
} = window;

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

WebChat.renderWebChat(
{
directLine,
store
},
document.getElementById('webchat')
const searchParams = new URLSearchParams(location.search);
const variant = searchParams.get('variant');

if (variant) {
window.checkAccessibility = async () => {};
}

const root = createRoot(document.getElementById('webchat'));
const webChatProps = { directLine, store };

root.render(
variant
? React.createElement(
FluentProvider,
{ className: 'fui-FluentProvider', theme: webLightTheme },
React.createElement(FluentThemeProvider, { variant }, React.createElement(ReactWebChat, webChatProps))
)
: React.createElement(ReactWebChat, webChatProps)
);

await pageConditions.uiConnected();
Expand All @@ -42,6 +85,14 @@
type: 'message'
});

// When (copilot): opening the link definitions
if (variant === 'copilot') {
// Then (copilot): the link definitions should be closed
await host.snapshot('local');

await host.click(pageElements.linkDefinitions()[0].querySelector('summary'));
}

const linkDefinitionItems = pageElements.linkDefinitions()[0].querySelectorAll('[role="listitem"] > *');

await host.click(linkDefinitionItems[2]);
Expand Down
1 change: 1 addition & 0 deletions __tests__/html2/copyButton/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@

const styleOptions = {
botAvatarBackgroundColor: '#304E7A',
referenceListDefaultOpen: true,
botAvatarImage:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAK0lEQVQ4T2P8z8Dwn4GKgHHUQIpDczQMKQ5ChtEwHA1DMkJgNNmQEWhoWgBMAiftPRtHngAAAABJRU5ErkJggg=='
};
Expand Down
Binary file modified __tests__/html2/linkDefinition/badge.copilot.html.snap-1.png
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.
5 changes: 5 additions & 0 deletions __tests__/html2/linkDefinition/badge.html
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@

await host.snapshot('local');

// When (copilot): opening the link definitions since they start closed
if (variant) {
await host.click(pageElements.linkDefinitions()[0].querySelector('summary'));
}

const [firstActivityElement] = pageElements.activities();

const linkDefinitions = firstActivityElement.querySelectorAll('.link-definitions__list-item');
Expand Down
Binary file modified __tests__/html2/part-grouping/copilot.dark.html.snap-10.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified __tests__/html2/part-grouping/copilot.html.snap-10.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion __tests__/html2/part-grouping/folded.skip.html
Original file line number Diff line number Diff line change
Expand Up @@ -286,10 +286,14 @@
// 2nd is sent with status not set to show the work is in
directLine.emulateIncomingActivity(withCreativeWorkStatus(activities.at(1), undefined));

await pageConditions.numActivitiesShown(3);

// When (copilot): opening the group
await host.click(document.querySelector('.collapsible-grouping__toggle'));

// Then: show both activities in the list:
// 1st with the loading indicator
// 2nd with an empty status indicator
await pageConditions.numActivitiesShown(3);
await host.snapshot('local');

// When folding the list:
Expand Down
6 changes: 6 additions & 0 deletions __tests__/html2/part-grouping/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,12 @@
// 1st with the loading indicator
// 2nd with an empty status indicator
await pageConditions.numActivitiesShown(3);

// When (copilot): opening the group
if (variant === 'copilot') {
await host.click(document.querySelector('.collapsible-grouping__toggle'));
}

await host.snapshot('local');

// When marking the 1st activity as 'Published':
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.
6 changes: 6 additions & 0 deletions __tests__/html2/part-grouping/status.html
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,12 @@

// Then: show activities with corresponding status indicators
await pageConditions.numActivitiesShown(4);

// When (copilot): opening the group
if (variant === 'copilot') {
await host.click(document.querySelector('.collapsible-grouping__toggle'));
}

await host.snapshot('local');

// Test Case 2: Multiple activities being worked on
Expand Down
1 change: 1 addition & 0 deletions __tests__/html2/side-by-side/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,7 @@
},
styleOptions: {
groupTimestamp: 1,
referenceListDefaultOpen: true,
timestampFormat: 'absolute',
botAvatarBackgroundColor: '#304E7A',
...adjustStyleOptions
Expand Down
14 changes: 14 additions & 0 deletions packages/api/src/StyleOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,20 @@ type StyleOptions = {
*/
speechRecognitionContinuous?: boolean | undefined;

/**
* Whether part groups are open by default.
*
* @default true
*/
partGroupDefaultOpen?: boolean | undefined;

/**
* Whether references (citation link definitions) are open by default.
*
* @default true
*/
referenceListDefaultOpen?: boolean | undefined;

/**
* Defines how activities are being grouped by (in the order of appearance in the array). Default to `['sender', 'status', 'part']` or `sender,status` in CSS.
*
Expand Down
3 changes: 3 additions & 0 deletions packages/api/src/defaultStyleOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,9 @@ const DEFAULT_OPTIONS: Required<StyleOptions> = {
// Speech recognition
speechRecognitionContinuous: false,

partGroupDefaultOpen: true,
referenceListDefaultOpen: true,

groupActivitiesBy: ['sender', 'status', 'part'],

// Send box attachment bar
Expand Down
12 changes: 8 additions & 4 deletions packages/component/src/LinkDefinition/LinkDefinitions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import random from 'math-random';
import React, {
Children,
useCallback,
useMemo,
useRef,
useState,
type ComponentType,
Expand All @@ -16,7 +17,7 @@ import { ComponentIcon } from '../Icon';

import styles from './LinkDefinitions.module.css';

const { useLocalizer } = hooks;
const { useLocalizer, useStyleOptions } = hooks;
const { count: childrenCount, map: childrenMap } = Children;

type Props<TAccessoryProps> = Readonly<{
Expand Down Expand Up @@ -54,9 +55,12 @@ const LinkDefinitions = <TAccessoryProps extends {}>({
const localizeWithPlural = useLocalizer({ plural: true });
const summaryRef = useRef<HTMLElement>(null);
const classNames = useStyles(styles);
const [{ referenceListDefaultOpen }] = useStyleOptions();

const headerText = localizeWithPlural(REFERENCE_LIST_HEADER_IDS, childrenCount(children));

const defaultOpenString = useMemo(() => (referenceListDefaultOpen ? 'true' : 'false'), [referenceListDefaultOpen]);

const handleToggle = useCallback<ReactEventHandler<HTMLDetailsElement>>(event => {
const summary = summaryRef.current;
const details = event.target;
Expand All @@ -73,12 +77,12 @@ const LinkDefinitions = <TAccessoryProps extends {}>({
// eslint-disable-next-line react/forbid-dom-props
id={id}
onToggle={handleToggle}
open={true}
open={referenceListDefaultOpen}
>
<summary
aria-controls={id}
aria-expanded="true"
aria-pressed="true"
aria-expanded={defaultOpenString}
aria-pressed={defaultOpenString}
className={classNames['link-definitions__header']}
ref={summaryRef}
role="button"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ function PartGroupingActivity(props: PartGroupingActivityProps) {

const classNames = useStyles(styles);
const getKeyByActivity = useGetKeyByActivity();
const [isGroupOpen, setIsGroupOpen] = useState(true);
const [{ partGroupDefaultOpen }] = useStyleOptions();
const [isGroupOpen, setIsGroupOpen] = useState(partGroupDefaultOpen);

const messages = useMemo(
() => activities.map(activity => getOrgSchemaMessage(activity.entities)).filter(message => !!message),
Expand Down
3 changes: 2 additions & 1 deletion packages/fluent-theme/src/private/FluentThemeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,11 @@ function FluentThemeProvider(props: FluentThemeProviderProps) {
const fluentStyleOptions = useMemo(
() =>
Object.freeze({
...(variant === 'copilot' && { partGroupDefaultOpen: false, referenceListDefaultOpen: false }),
feedbackActionsPlacement: 'activity-actions',
stylesRoot
}),
[stylesRoot]
[stylesRoot, variant]
);

return (
Expand Down
Loading