botframework-webchat 4.16.1-main.20240405.136b5a4 → 4.17.0-main.20240408.d0aa541
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/webchat-es5.js +1 -1
- package/dist/webchat-minimal.js +1 -1
- package/dist/webchat.js +1 -1
- package/lib/addVersion.js +1 -1
- package/lib/createFullStyleSet.d.ts +66 -13
- package/lib/createFullStyleSet.d.ts.map +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/markdown/markdownItPlugins/betterLink.d.ts +2 -0
- package/lib/markdown/markdownItPlugins/betterLink.d.ts.map +1 -1
- package/lib/markdown/markdownItPlugins/betterLink.js +66 -50
- package/lib/markdown/renderMarkdown.d.ts.map +1 -1
- package/lib/markdown/renderMarkdown.js +12 -5
- package/package.json +7 -7
- package/src/__tests__/renderMarkdown.spec.js +4 -4
- package/src/markdown/markdownItPlugins/betterLink.ts +54 -33
- package/src/markdown/renderMarkdown.ts +7 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import iterator from 'markdown-it-for-inline';
|
|
2
1
|
import MarkdownIt from 'markdown-it';
|
|
2
|
+
import iterator from 'markdown-it-for-inline';
|
|
3
3
|
|
|
4
4
|
// Put a transparent pixel instead of the "open in new window" icon, so developers can easily modify the icon in CSS.
|
|
5
5
|
const TRANSPARENT_GIF = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
|
@@ -30,11 +30,19 @@ type Decoration = {
|
|
|
30
30
|
|
|
31
31
|
/** Value of "title" attribute of the link. If set to `false`, remove existing attribute. */
|
|
32
32
|
title?: AttributeSetter;
|
|
33
|
+
|
|
34
|
+
/** Wraps the link with zero-width space. */
|
|
35
|
+
wrapZeroWidthSpace?: boolean;
|
|
33
36
|
};
|
|
34
37
|
|
|
35
38
|
// This is used for parsing Markdown for external links.
|
|
36
39
|
const internalMarkdownIt = new MarkdownIt();
|
|
37
40
|
|
|
41
|
+
const ZERO_WIDTH_SPACE_TOKEN = {
|
|
42
|
+
content: '\u200b',
|
|
43
|
+
type: 'text'
|
|
44
|
+
};
|
|
45
|
+
|
|
38
46
|
function setTokenAttribute(attrs: Array<[string, string]>, name: string, value?: AttributeSetter) {
|
|
39
47
|
const index = attrs.findIndex(entry => entry[0] === name);
|
|
40
48
|
|
|
@@ -61,52 +69,65 @@ const betterLink = (
|
|
|
61
69
|
): typeof MarkdownIt =>
|
|
62
70
|
markdown.use(iterator, 'url_new_win', 'link_open', (tokens, index) => {
|
|
63
71
|
const indexOfLinkCloseToken = tokens.indexOf(tokens.slice(index + 1).find(({ type }) => type === 'link_close'));
|
|
64
|
-
|
|
72
|
+
// eslint-disable-next-line no-magic-numbers
|
|
73
|
+
const updatedTokens = tokens.splice(index, ~indexOfLinkCloseToken ? indexOfLinkCloseToken - index + 1 : 2);
|
|
65
74
|
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
try {
|
|
76
|
+
const [linkOpenToken] = updatedTokens;
|
|
77
|
+
const linkCloseToken = updatedTokens[updatedTokens.length - 1];
|
|
68
78
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
.map(({ content }) => content)
|
|
72
|
-
.join(' ');
|
|
79
|
+
const [, href] = linkOpenToken.attrs.find(([name]) => name === 'href');
|
|
80
|
+
const nodesInLink = updatedTokens.slice(1, updatedTokens.length - 1);
|
|
73
81
|
|
|
74
|
-
|
|
82
|
+
const textContent = nodesInLink
|
|
83
|
+
.filter(({ type }) => type === 'text')
|
|
84
|
+
.map(({ content }) => content)
|
|
85
|
+
.join(' ');
|
|
75
86
|
|
|
76
|
-
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
87
|
+
const decoration = decorate(href, textContent);
|
|
79
88
|
|
|
80
|
-
|
|
89
|
+
if (!decoration) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
81
92
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
setTokenAttribute(token.attrs, 'title', title);
|
|
93
|
+
const { ariaLabel, asButton, className, iconAlt, iconClassName, rel, target, title, wrapZeroWidthSpace } =
|
|
94
|
+
decoration;
|
|
85
95
|
|
|
86
|
-
|
|
87
|
-
|
|
96
|
+
setTokenAttribute(linkOpenToken.attrs, 'aria-label', ariaLabel);
|
|
97
|
+
setTokenAttribute(linkOpenToken.attrs, 'class', className);
|
|
98
|
+
setTokenAttribute(linkOpenToken.attrs, 'title', title);
|
|
88
99
|
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
if (iconClassName) {
|
|
101
|
+
const iconTokens = internalMarkdownIt.parseInline(``)[0].children;
|
|
91
102
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
103
|
+
setTokenAttribute(iconTokens[0].attrs, 'class', iconClassName);
|
|
104
|
+
setTokenAttribute(iconTokens[0].attrs, 'title', iconAlt);
|
|
105
|
+
|
|
106
|
+
// Add an icon before </a>.
|
|
107
|
+
// eslint-disable-next-line no-magic-numbers
|
|
108
|
+
updatedTokens.splice(-1, 0, ...iconTokens);
|
|
109
|
+
}
|
|
95
110
|
|
|
96
|
-
|
|
97
|
-
|
|
111
|
+
if (asButton) {
|
|
112
|
+
setTokenAttribute(linkOpenToken.attrs, 'href', false);
|
|
98
113
|
|
|
99
|
-
|
|
114
|
+
linkOpenToken.tag = 'button';
|
|
100
115
|
|
|
101
|
-
|
|
102
|
-
|
|
116
|
+
setTokenAttribute(linkOpenToken.attrs, 'type', 'button');
|
|
117
|
+
setTokenAttribute(linkOpenToken.attrs, 'value', href);
|
|
103
118
|
|
|
104
|
-
|
|
105
|
-
|
|
119
|
+
linkCloseToken.tag = 'button';
|
|
120
|
+
} else {
|
|
121
|
+
setTokenAttribute(linkOpenToken.attrs, 'rel', rel);
|
|
122
|
+
setTokenAttribute(linkOpenToken.attrs, 'target', target);
|
|
106
123
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
124
|
+
|
|
125
|
+
if (wrapZeroWidthSpace) {
|
|
126
|
+
updatedTokens.splice(0, 0, ZERO_WIDTH_SPACE_TOKEN);
|
|
127
|
+
updatedTokens.splice(Infinity, 0, ZERO_WIDTH_SPACE_TOKEN);
|
|
128
|
+
}
|
|
129
|
+
} finally {
|
|
130
|
+
tokens.splice(index, 0, ...updatedTokens);
|
|
110
131
|
}
|
|
111
132
|
});
|
|
112
133
|
|
|
@@ -2,9 +2,9 @@ import { onErrorResumeNext } from 'botframework-webchat-core';
|
|
|
2
2
|
import MarkdownIt from 'markdown-it';
|
|
3
3
|
import sanitizeHTML from 'sanitize-html';
|
|
4
4
|
|
|
5
|
-
import { pre as respectCRLFPre } from './markdownItPlugins/respectCRLF';
|
|
6
5
|
import ariaLabel, { post as ariaLabelPost, pre as ariaLabelPre } from './markdownItPlugins/ariaLabel';
|
|
7
6
|
import betterLink from './markdownItPlugins/betterLink';
|
|
7
|
+
import { pre as respectCRLFPre } from './markdownItPlugins/respectCRLF';
|
|
8
8
|
import iterateLinkDefinitions from './private/iterateLinkDefinitions';
|
|
9
9
|
|
|
10
10
|
const SANITIZE_HTML_OPTIONS = Object.freeze({
|
|
@@ -88,7 +88,8 @@ export default function render(
|
|
|
88
88
|
.use(betterLink, (href: string, textContent: string): BetterLinkDecoration | undefined => {
|
|
89
89
|
const decoration: BetterLinkDecoration = {
|
|
90
90
|
rel: 'noopener noreferrer',
|
|
91
|
-
target: '_blank'
|
|
91
|
+
target: '_blank',
|
|
92
|
+
wrapZeroWidthSpace: true
|
|
92
93
|
};
|
|
93
94
|
|
|
94
95
|
const ariaLabelSegments: string[] = [textContent];
|
|
@@ -101,10 +102,12 @@ export default function render(
|
|
|
101
102
|
linkDefinition.title || onErrorResumeNext(() => new URL(linkDefinition.url).host) || linkDefinition.url
|
|
102
103
|
);
|
|
103
104
|
|
|
104
|
-
linkDefinition.identifier
|
|
105
|
+
// linkDefinition.identifier is uppercase, while linkDefinition.label is as-is.
|
|
106
|
+
linkDefinition.label === textContent && classes.add('webchat__render-markdown__pure-identifier');
|
|
105
107
|
}
|
|
106
108
|
|
|
107
|
-
|
|
109
|
+
// For links that would be sanitized out, let's turn them into a button so we could handle them later.
|
|
110
|
+
if (!SANITIZE_HTML_OPTIONS.allowedSchemes.map(scheme => `${scheme}:`).includes(protocol)) {
|
|
108
111
|
decoration.asButton = true;
|
|
109
112
|
|
|
110
113
|
classes.add('webchat__render-markdown__citation');
|