Skip to content

Commit 7671e32

Browse files
authored
Fix: citation links matching edge cases (#5614)
* Fix: citation matching in edge cases * Changelog
1 parent 713c1ff commit 7671e32

File tree

8 files changed

+370
-7
lines changed

8 files changed

+370
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/
319319
by [@lexi-taylor](https://github.com/lexi-taylor)
320320
- Fixed `npm start` may fail subsequently as builds are not fully flushed to `/dist/`, in PR [#5599](https://github.com/microsoft/BotFramework-WebChat/pull/5599), by [@compulim](https://github.com/compulim)
321321
- Fixed published package types containing internal package references, in PR [#5610](https://github.com/microsoft/BotFramework-WebChat/pull/5610), by [@OEvgeny](https://github.com/OEvgeny)
322+
- Fixed citation links are not properly matched against markdown links, in PR [#5614](https://github.com/microsoft/BotFramework-WebChat/pull/5614), by [@OEvgeny](https://github.com/OEvgeny)
322323

323324
### Removed
324325

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<title>Citation URL (fluent)</title>
5+
<script>
6+
location = './url?variant=fluent';
7+
</script>
8+
</head>
9+
<body></body>
10+
</html>
64.2 KB
Loading

__tests__/html2/citation/url.html

Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
4+
<head>
5+
<title>Citation URL</title>
6+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
7+
<script type="importmap">
8+
{
9+
"imports": {
10+
"@fluentui/react-provider": "https://esm.sh/@fluentui/react-provider?deps=react@18&exports=FluentProvider",
11+
"@fluentui/tokens": "https://esm.sh/@fluentui/tokens?deps=react@18&exports=createDarkTheme,webLightTheme",
12+
"@testduet/wait-for": "https://unpkg.com/@testduet/wait-for@main/dist/wait-for.mjs",
13+
"botframework-webchat": "/__dist__/packages/bundle/static/botframework-webchat.js",
14+
"botframework-webchat/decorator": "/__dist__/packages/bundle/static/botframework-webchat/decorator.js",
15+
"botframework-webchat/internal": "/__dist__/packages/bundle/static/botframework-webchat/internal.js",
16+
"botframework-webchat-fluent-theme": "/__dist__/packages/fluent-theme/static/botframework-webchat-fluent-theme.js",
17+
"react": "https://esm.sh/react@18",
18+
"react-dom": "https://esm.sh/react-dom@18",
19+
"react-dom/": "https://esm.sh/react-dom@18/"
20+
}
21+
}
22+
</script>
23+
<script crossorigin="anonymous" src="/test-harness.js"></script>
24+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
25+
<style type="text/css">
26+
#webchat {
27+
width: 640px;
28+
}
29+
30+
.fui-FluentProvider {
31+
height: 100%;
32+
}
33+
34+
.theme.variant-copilot {
35+
--webchat__color--surface: var(--colorGrey98);
36+
}
37+
</style>
38+
</head>
39+
40+
<body>
41+
<main id="webchat"></main>
42+
<script type="module">
43+
import '/test-harness.mjs';
44+
import '/test-page-object.mjs';
45+
46+
import { createDarkTheme, webLightTheme } from '@fluentui/tokens';
47+
import { FluentProvider } from '@fluentui/react-provider';
48+
import { waitFor } from '@testduet/wait-for';
49+
import { createDirectLine, createStoreWithOptions, ReactWebChat } from 'botframework-webchat';
50+
import { FluentThemeProvider } from 'botframework-webchat-fluent-theme';
51+
import { createElement } from 'react';
52+
import { createRoot } from 'react-dom/client';
53+
54+
const {
55+
testHelpers: { createDirectLineEmulator }
56+
} = window;
57+
58+
// TODO: This is for `createDirectLineEmulator` only, should find ways to eliminate this line.
59+
window.WebChat = { createStoreWithOptions };
60+
61+
// run = fn => fn();
62+
run(async function () {
63+
const { directLine, store } = createDirectLineEmulator();
64+
65+
const searchParams = new URLSearchParams(location.search);
66+
const variant = searchParams.get('variant');
67+
const theme = searchParams.get('fluent-theme');
68+
69+
await host.windowSize(480, 760, document.getElementById('webchat'));
70+
await host.sendDevToolsCommand('Emulation.setEmulatedMedia', {
71+
features: [
72+
{ name: 'prefers-reduced-motion', value: 'reduce' },
73+
...(theme === 'dark' || theme === 'light'
74+
? [{ name: 'prefers-color-scheme', value: theme }]
75+
: [])
76+
]
77+
});
78+
79+
const root = createRoot(document.getElementById('webchat'));
80+
81+
let fluentTheme;
82+
let codeBlockTheme;
83+
84+
if (theme === 'dark' || window.matchMedia('(prefers-color-scheme: dark)').matches && theme !== 'light') {
85+
fluentTheme = {
86+
...createDarkTheme({
87+
10: '#12174c',
88+
20: '#1a1f5b',
89+
30: '#21276a',
90+
40: '#293079',
91+
50: '#303788',
92+
60: '#384097',
93+
70: '#4049a7',
94+
80: '#151e80',
95+
90: '#4f59c5',
96+
100: '#5661d4',
97+
110: '#5e69e3',
98+
120: '#7982e8',
99+
130: '#949bec',
100+
140: '#afb5f1',
101+
150: '#c9cdf6',
102+
160: '#e4e6fa'
103+
}),
104+
colorNeutralBackground1Disabled: '#101010',
105+
colorNeutralBackground1Hover: '#101010',
106+
colorNeutralForeground5: '#424242',
107+
colorGrey98: '#1b1b1b'
108+
};
109+
codeBlockTheme = 'github-dark-default';
110+
} else {
111+
fluentTheme = {
112+
...webLightTheme,
113+
colorNeutralForeground1: '#1b1b1b',
114+
colorGrey98: '#fafafa'
115+
};
116+
codeBlockTheme = 'github-light-default';
117+
}
118+
119+
if (variant) {
120+
window.checkAccessibility = async () => { }
121+
}
122+
123+
const webChatProps = { directLine, store, styleOptions: { codeBlockTheme } };
124+
125+
root.render(
126+
variant === 'copilot' || variant === 'fluent' ?
127+
createElement(
128+
FluentProvider,
129+
{ className: 'fui-FluentProvider', theme: fluentTheme },
130+
createElement(
131+
FluentThemeProvider,
132+
{ variant: variant },
133+
createElement(ReactWebChat, webChatProps)
134+
)
135+
) :
136+
createElement(ReactWebChat, webChatProps)
137+
);
138+
139+
await pageConditions.uiConnected();
140+
141+
await directLine.emulateIncomingActivity(
142+
{
143+
"type": "message",
144+
"id": "00000000-0000-0000-0000-000000000001",
145+
"timestamp": "2025-10-09T00:00:00.0000000+00:00",
146+
"channelId": "test",
147+
"from": {
148+
"id": "bot-anon",
149+
"name": "test_agent",
150+
"role": "bot"
151+
},
152+
"conversation": { "id": "conv-anon" },
153+
"recipient": { "id": "user-anon", "role": "user" },
154+
"textFormat": "markdown",
155+
"locale": "en-US",
156+
"text": "Verify that citation **[9]** renders correctly.\n\n- **2025 Product Area Review [Template]** [9]\n\n[9]: https://example.sharepoint.com/sites/Docs/2025%20Product%20Area%20Review%20[Template].pptx \"2025 Product Area Review [Template].pptx\"",
157+
"entities": [
158+
{
159+
"type": "https://schema.org/Message",
160+
"@type": "Message",
161+
"@context": "https://schema.org",
162+
"citation": [
163+
{
164+
"appearance": {
165+
"text": "",
166+
"abstract": "2025 Product Area Review [Template].pptx",
167+
"@type": "DigitalDocument",
168+
"name": "2025 Product Area Review [Template].pptx",
169+
"url": "https://example.sharepoint.com/sites/Docs/2025%20Product%20Area%20Review%20[Template].pptx"
170+
},
171+
"position": 9,
172+
"@type": "Claim",
173+
"@id": "SPO_XYZ_claim_id_only_for_test_9"
174+
}
175+
]
176+
},
177+
{
178+
"type": "https://schema.org/Claim",
179+
"@type": "Claim",
180+
"@context": "https://schema.org",
181+
"@id": "SPO_XYZ_claim_id_only_for_test_9",
182+
"text": "",
183+
"name": "2025 Product Area Review [Template].pptx"
184+
}
185+
]
186+
}
187+
);
188+
189+
await directLine.emulateIncomingActivity({
190+
"type": "message",
191+
"id": "00000000-0000-0000-0000-000000000102",
192+
"timestamp": "2025-10-09T00:00:00.0000000+00:00",
193+
"channelId": "test",
194+
"from": { "id": "bot-anon", "name": "test_agent", "role": "bot" },
195+
"conversation": { "id": "conv-anon" },
196+
"recipient": { "id": "user-anon", "role": "user" },
197+
"textFormat": "markdown",
198+
"locale": "en-US",
199+
"text": "Verify array-style query params and nested redirect. **Search results (tags)**. [2]\n\n[2]: https://files.example.com/search?tags%5B%5D=a&tags%5B%5D=a%2Fb&redirect=https%3A%2F%2Fexample.com%2Fx%3Fy%3Dz \"search with tags[] and redirect\"",
200+
"entities": [
201+
{
202+
"type": "https://schema.org/Message",
203+
"@type": "Message",
204+
"@context": "https://schema.org",
205+
"citation": [
206+
{
207+
"appearance": {
208+
"text": "",
209+
"abstract": "search with tags[] and redirect",
210+
"@type": "DigitalDocument",
211+
"name": "search with tags[] and redirect",
212+
"url": "https://files.example.com/search?tags%5B%5D=a&tags%5B%5D=a%2Fb&redirect=https%3A%2F%2Fexample.com%2Fx%3Fy%3Dz"
213+
},
214+
"position": 2,
215+
"@type": "Claim",
216+
"@id": "CLAIM_TEST_102"
217+
}
218+
]
219+
},
220+
{ "type": "https://schema.org/Claim", "@type": "Claim", "@context": "https://schema.org", "@id": "CLAIM_TEST_102", "text": "", "name": "search with tags[] and redirect" }
221+
]
222+
});
223+
224+
await directLine.emulateIncomingActivity({
225+
"type": "message",
226+
"id": "00000000-0000-0000-0000-000000000103",
227+
"timestamp": "2025-10-09T00:00:00.0000000+00:00",
228+
"channelId": "test",
229+
"from": { "id": "bot-anon", "name": "test_agent", "role": "bot" },
230+
"conversation": { "id": "conv-anon" },
231+
"recipient": { "id": "user-anon", "role": "user" },
232+
"textFormat": "markdown",
233+
"locale": "en-US",
234+
"text": "Verify UTF-8 encoding in path. **Отчёт - план 📄**. [3]\n\n[3]: https://docs.example.com/reports/%D0%9E%D1%82%D1%87%D1%91%D1%82%20%E2%80%94%20%D0%BF%D0%BB%D0%B0%D0%BD%20%F0%9F%93%84.pdf \"Отчёт - план 📄.pdf\"",
235+
"entities": [
236+
{
237+
"type": "https://schema.org/Message",
238+
"@type": "Message",
239+
"@context": "https://schema.org",
240+
"citation": [
241+
{
242+
"appearance": {
243+
"text": "",
244+
"abstract": "Отчёт - план 📄.pdf",
245+
"@type": "DigitalDocument",
246+
"name": "Отчёт - план 📄.pdf",
247+
"url": "https://docs.example.com/reports/%D0%9E%D1%82%D1%87%D1%91%D1%82%20%E2%80%94%20%D0%BF%D0%BB%D0%B0%D0%BD%20%F0%9F%93%84.pdf"
248+
},
249+
"position": 3,
250+
"@type": "Claim",
251+
"@id": "CLAIM_TEST_103"
252+
}
253+
]
254+
},
255+
{ "type": "https://schema.org/Claim", "@type": "Claim", "@context": "https://schema.org", "@id": "CLAIM_TEST_103", "text": "", "name": "Отчёт - план 📄.pdf" }
256+
]
257+
});
258+
259+
await directLine.emulateIncomingActivity({
260+
"type": "message",
261+
"id": "00000000-0000-0000-0000-000000000105",
262+
"timestamp": "2025-10-09T00:00:00.0000000+00:00",
263+
"channelId": "test",
264+
"from": { "id": "bot-anon", "name": "test_agent", "role": "bot" },
265+
"conversation": { "id": "conv-anon" },
266+
"recipient": { "id": "user-anon", "role": "user" },
267+
"textFormat": "markdown",
268+
"locale": "en-US",
269+
"text": "Verify mailto with plus-addressing and encoded query. **Email owner**. [5]\n\n[5]: mailto:support+roadmap@example.com?subject=Roadmap%20Review%20%5BTemplate%5D&body=Please%20review%20slide%203.%0AThanks%21 \"mailto with + and encoded subject/body\"",
270+
"entities": [
271+
{
272+
"type": "https://schema.org/Message",
273+
"@type": "Message",
274+
"@context": "https://schema.org",
275+
"citation": [
276+
{
277+
"appearance": {
278+
"text": "",
279+
"abstract": "mailto with + and encoded subject/body",
280+
"@type": "DigitalDocument",
281+
"name": "mailto with + and encoded subject/body",
282+
"url": "mailto:support+roadmap@example.com?subject=Roadmap%20Review%20%5BTemplate%5D&body=Please%20review%20slide%203.%0AThanks%21"
283+
},
284+
"position": 5,
285+
"@type": "Claim",
286+
"@id": "CLAIM_TEST_105"
287+
}
288+
]
289+
},
290+
{ "type": "https://schema.org/Claim", "@type": "Claim", "@context": "https://schema.org", "@id": "CLAIM_TEST_105", "text": "", "name": "mailto with + and encoded subject/body" }
291+
]
292+
});
293+
294+
await directLine.emulateIncomingActivity({
295+
"type": "message",
296+
"id": "00000000-0000-0000-0000-000000000106",
297+
"timestamp": "2025-10-09T00:00:00.0000000+00:00",
298+
"channelId": "test",
299+
"from": { "id": "bot-anon", "name": "test_agent", "role": "bot" },
300+
"conversation": { "id": "conv-anon" },
301+
"recipient": { "id": "user-anon", "role": "user" },
302+
"textFormat": "markdown",
303+
"locale": "en-US",
304+
"text": "Verify `%23` for literal hash in filename. **Q1#1 Plan**. [6]\n\n[6]: https://example.sharepoint.com/sites/Docs/Q1%231%20Plan.pptx \"Q1#1 Plan.pptx\"",
305+
"entities": [
306+
{
307+
"type": "https://schema.org/Message",
308+
"@type": "Message",
309+
"@context": "https://schema.org",
310+
"citation": [
311+
{
312+
"appearance": {
313+
"text": "",
314+
"abstract": "Q1#1 Plan.pptx",
315+
"@type": "DigitalDocument",
316+
"name": "Q1#1 Plan.pptx",
317+
"url": "https://example.sharepoint.com/sites/Docs/Q1%231%20Plan.pptx"
318+
},
319+
"position": 6,
320+
"@type": "Claim",
321+
"@id": "CLAIM_TEST_106"
322+
}
323+
]
324+
},
325+
{ "type": "https://schema.org/Claim", "@type": "Claim", "@context": "https://schema.org", "@id": "CLAIM_TEST_106", "text": "", "name": "Q1#1 Plan.pptx" }
326+
]
327+
});
328+
329+
expect(Array.from(document.querySelectorAll('.webchat__render-markdown a')).map(a => a.href)).toEqual([
330+
'https://example.sharepoint.com/sites/Docs/2025%20Product%20Area%20Review%20%5BTemplate%5D.pptx',
331+
'https://example.sharepoint.com/sites/Docs/2025%20Product%20Area%20Review%20%5BTemplate%5D.pptx',
332+
'https://files.example.com/search?tags%5B%5D=a&tags%5B%5D=a%2Fb&redirect=https%3A%2F%2Fexample.com%2Fx%3Fy%3Dz',
333+
'https://docs.example.com/reports/%D0%9E%D1%82%D1%87%D1%91%D1%82%20%E2%80%94%20%D0%BF%D0%BB%D0%B0%D0%BD%20%F0%9F%93%84.pdf',
334+
'mailto:support+roadmap@example.com?subject=Roadmap%20Review%20%5BTemplate%5D&body=Please%20review%20slide%203.%0AThanks%21',
335+
'https://example.sharepoint.com/sites/Docs/Q1%231%20Plan.pptx'
336+
]);
337+
338+
await host.snapshot('local');
339+
});
340+
</script>
341+
</body>
342+
343+
</html>
76.7 KB
Loading

package-lock.json

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)