botframework-webchat-component 4.19.0 → 4.19.1-main.20260529.6bcfcee
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/_dtsroll-chunks/CwOuXmoL-botframework-webchat-styles.react.d.ts +68 -0
- package/dist/botframework-webchat-component.component.d.mts +1 -1
- package/dist/botframework-webchat-component.component.d.ts +1 -1
- package/dist/botframework-webchat-component.component.js +1 -1
- package/dist/botframework-webchat-component.component.mjs +1 -1
- package/dist/botframework-webchat-component.d.mts +1 -3
- package/dist/botframework-webchat-component.d.ts +1 -3
- package/dist/botframework-webchat-component.decorator.js +1 -1
- package/dist/botframework-webchat-component.decorator.js.map +1 -1
- package/dist/botframework-webchat-component.decorator.mjs +1 -1
- package/dist/botframework-webchat-component.decorator.mjs.map +1 -1
- package/dist/botframework-webchat-component.hook.js +1 -1
- package/dist/botframework-webchat-component.hook.mjs +1 -1
- package/dist/botframework-webchat-component.internal.d.mts +2 -6
- package/dist/botframework-webchat-component.internal.d.ts +2 -6
- package/dist/botframework-webchat-component.internal.js +1 -1
- package/dist/botframework-webchat-component.internal.js.map +1 -1
- package/dist/botframework-webchat-component.internal.mjs +1 -1
- package/dist/botframework-webchat-component.js +1 -1
- package/dist/botframework-webchat-component.js.map +1 -1
- package/dist/botframework-webchat-component.mjs +1 -1
- package/dist/botframework-webchat-component.mjs.map +1 -1
- package/dist/chunk-4EA5WZBJ.mjs +80 -0
- package/dist/chunk-4EA5WZBJ.mjs.map +1 -0
- package/dist/{chunk-H5YR7OLF.js → chunk-B2XHGOQH.js} +2 -2
- package/dist/{chunk-H5YR7OLF.js.map → chunk-B2XHGOQH.js.map} +1 -1
- package/dist/chunk-C2RHHZZQ.mjs +2 -0
- package/dist/chunk-C2RHHZZQ.mjs.map +1 -0
- package/dist/{chunk-TYPS3H4I.mjs → chunk-FIPA3BLZ.mjs} +2 -2
- package/dist/{chunk-TYPS3H4I.mjs.map → chunk-FIPA3BLZ.mjs.map} +1 -1
- package/dist/{chunk-LVVCSDZ4.mjs → chunk-HWDSXHRJ.mjs} +2 -2
- package/dist/chunk-JCX7GSY7.js +2 -0
- package/dist/{chunk-2R7BJ63Z.js.map → chunk-JCX7GSY7.js.map} +1 -1
- package/dist/{chunk-U6OWCHTQ.js → chunk-K4QNZHM5.js} +2 -2
- package/dist/chunk-K4QNZHM5.js.map +1 -0
- package/dist/{chunk-MOJMHOVH.js → chunk-OJOV52AD.js} +2 -2
- package/dist/{chunk-MOJMHOVH.js.map → chunk-OJOV52AD.js.map} +1 -1
- package/dist/{chunk-GTOP3WPD.mjs → chunk-WPWJFSZC.mjs} +2 -2
- package/dist/chunk-WPWJFSZC.mjs.map +1 -0
- package/dist/chunk-ZUKHLDZN.js +80 -0
- package/dist/chunk-ZUKHLDZN.js.map +1 -0
- package/dist/{component-BtSxgJS5.d.mts → component-BeCWAilk.d.mts} +34 -43
- package/dist/{component-Fyy8iCRE.d.ts → component-BtDQyqSP.d.ts} +34 -43
- package/dist/metafile-cjs.json +1 -1
- package/dist/metafile-esm.json +1 -1
- package/package.json +12 -14
- package/src/ActivityStatus/SendStatus/private/SendFailedRetry.tsx +31 -0
- package/src/BasicToast.js +4 -5
- package/src/BasicToaster.js +2 -3
- package/src/Composer.tsx +0 -20
- package/src/Utils/InlineMarkdown.tsx +121 -0
- package/src/Utils/LocalizedString.tsx +123 -124
- package/src/Utils/addTargetBlankToHyperlinks.spec.ts +52 -0
- package/src/Utils/addTargetBlankToHyperlinks.ts +14 -0
- package/src/boot/internal.ts +7 -2
- package/src/hooks/internal/WebChatUIContext.ts +0 -2
- package/src/hooks/useRenderMarkdownAsHTML.ts +5 -3
- package/src/hooks/useStreamingMarkdownWithDefinitions.ts +2 -2
- package/src/private/renderMarkdownInline.ts +19 -0
- package/src/providers/CustomElements/customElements/CodeBlock.ts +2 -2
- package/dist/_dtsroll-chunks/Cha1SOtx-botframework-webchat-styles.react.d.ts +0 -38
- package/dist/chunk-2R7BJ63Z.js +0 -2
- package/dist/chunk-A4NDFSZM.mjs +0 -77
- package/dist/chunk-A4NDFSZM.mjs.map +0 -1
- package/dist/chunk-CNTMOACS.mjs +0 -2
- package/dist/chunk-CNTMOACS.mjs.map +0 -1
- package/dist/chunk-GTOP3WPD.mjs.map +0 -1
- package/dist/chunk-U6OWCHTQ.js.map +0 -1
- package/dist/chunk-VDF6GQAL.js +0 -77
- package/dist/chunk-VDF6GQAL.js.map +0 -1
- package/src/ActivityStatus/SendStatus/private/SendFailedRetry.js +0 -28
- package/src/Utils/InlineMarkdown.js +0 -154
- package/src/Utils/addTargetBlankToHyperlinksMarkdown.js +0 -28
- package/src/Utils/addTargetBlankToHyperlinksMarkdown.spec.js +0 -45
- package/src/Utils/betterLinks.ts +0 -157
- package/src/Utils/parseDocumentFragmentFromString.ts +0 -9
- package/src/Utils/serializeDocumentFragmentIntoString.ts +0 -3
- package/src/Utils/updateMarkdownAttrs.js +0 -10
- package/src/Utils/updateMarkdownAttrs.spec.js +0 -71
- package/src/Utils/walkMarkdownTokens.js +0 -15
- package/src/Utils/walkMarkdownTokens.spec.js +0 -18
- package/src/hooks/internal/useInternalMarkdownIt.js +0 -7
- package/src/hooks/internal/useInternalRenderMarkdownInline.js +0 -9
- /package/dist/{chunk-LVVCSDZ4.mjs.map → chunk-HWDSXHRJ.mjs.map} +0 -0
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { hooks } from 'botframework-webchat-api';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
|
-
import React, { useCallback } from 'react';
|
|
4
|
-
|
|
5
|
-
import InlineMarkdown from '../../../Utils/InlineMarkdown';
|
|
6
|
-
|
|
7
|
-
const { useLocalizer } = hooks;
|
|
8
|
-
|
|
9
|
-
const MARKDOWN_REFERENCES = ['RETRY'];
|
|
10
|
-
|
|
11
|
-
const SendFailedRetry = ({ onRetryClick }) => {
|
|
12
|
-
const handleReference = useCallback(({ data }) => data === 'RETRY' && onRetryClick(), [onRetryClick]);
|
|
13
|
-
const localize = useLocalizer();
|
|
14
|
-
|
|
15
|
-
const sendFailedText = localize('ACTIVITY_STATUS_SEND_FAILED_RETRY');
|
|
16
|
-
|
|
17
|
-
return (
|
|
18
|
-
<InlineMarkdown onReference={handleReference} references={MARKDOWN_REFERENCES}>
|
|
19
|
-
{sendFailedText}
|
|
20
|
-
</InlineMarkdown>
|
|
21
|
-
);
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
SendFailedRetry.propTypes = {
|
|
25
|
-
onRetryClick: PropTypes.func.isRequired
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export default SendFailedRetry;
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/* eslint react/no-danger: "off" */
|
|
2
|
-
|
|
3
|
-
import { hooks } from 'botframework-webchat-api';
|
|
4
|
-
import { isForbiddenPropertyName } from 'botframework-webchat-core';
|
|
5
|
-
import PropTypes from 'prop-types';
|
|
6
|
-
import React, { useCallback, useMemo } from 'react';
|
|
7
|
-
import updateIn from 'simple-update-in';
|
|
8
|
-
|
|
9
|
-
import createCustomEvent from './createCustomEvent';
|
|
10
|
-
import randomId from './randomId';
|
|
11
|
-
import useInternalMarkdownIt from '../hooks/internal/useInternalMarkdownIt';
|
|
12
|
-
import { useStyleToEmotionObject } from '../hooks/internal/styleToEmotionObject';
|
|
13
|
-
import walkMarkdownTokens from './walkMarkdownTokens';
|
|
14
|
-
|
|
15
|
-
const { useStyleOptions } = hooks;
|
|
16
|
-
|
|
17
|
-
function replaceAnchorWithButton(markdownTokens) {
|
|
18
|
-
return walkMarkdownTokens(markdownTokens, markdownToken => {
|
|
19
|
-
markdownToken = { ...markdownToken };
|
|
20
|
-
|
|
21
|
-
switch (markdownToken.type) {
|
|
22
|
-
case 'link_open':
|
|
23
|
-
markdownToken.tag = 'button';
|
|
24
|
-
markdownToken.attrs = [
|
|
25
|
-
...updateIn(
|
|
26
|
-
markdownToken.attrs,
|
|
27
|
-
[([name, value]) => name === 'href' && value.startsWith('#')],
|
|
28
|
-
([, value]) => ['data-markdown-href', value.substr(1)]
|
|
29
|
-
),
|
|
30
|
-
['type', 'button']
|
|
31
|
-
];
|
|
32
|
-
break;
|
|
33
|
-
|
|
34
|
-
case 'link_close':
|
|
35
|
-
markdownToken.tag = 'button';
|
|
36
|
-
break;
|
|
37
|
-
|
|
38
|
-
default:
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return markdownToken;
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const InlineMarkdown = ({ children, onReference, references }) => {
|
|
47
|
-
if (typeof children !== 'string') {
|
|
48
|
-
console.warn('botframework-webchat: "children" prop passed to <InlineMarkdown> must be of type string.');
|
|
49
|
-
|
|
50
|
-
// Shortcut for disabling invalid props.
|
|
51
|
-
// eslint-disable-next-line react-hooks/immutability
|
|
52
|
-
children = '';
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const [markdownIt] = useInternalMarkdownIt();
|
|
56
|
-
const [{ accent }] = useStyleOptions();
|
|
57
|
-
const styleToClassName = useStyleToEmotionObject();
|
|
58
|
-
|
|
59
|
-
// We inlined the style here because this style is:
|
|
60
|
-
// 1. Internal to Web Chat
|
|
61
|
-
// 2. Not customizable from developers (other than setting `styleOptions.accent`)
|
|
62
|
-
const className = useMemo(
|
|
63
|
-
() =>
|
|
64
|
-
styleToClassName({
|
|
65
|
-
'& button[data-markdown-href]': {
|
|
66
|
-
appearance: 'none',
|
|
67
|
-
backgroundColor: 'transparent',
|
|
68
|
-
border: 0,
|
|
69
|
-
color: accent,
|
|
70
|
-
cursor: 'pointer',
|
|
71
|
-
fontFamily: 'inherit',
|
|
72
|
-
fontSize: 'inherit',
|
|
73
|
-
padding: 0
|
|
74
|
-
},
|
|
75
|
-
'@media screen and (forced-colors: active)': {
|
|
76
|
-
'& button[data-markdown-href]': {
|
|
77
|
-
color: 'LinkText',
|
|
78
|
-
textDecoration: 'underline'
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}) + '',
|
|
82
|
-
[accent, styleToClassName]
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
// Markdown-It only support references in uppercase.
|
|
86
|
-
// Re-shaping input.
|
|
87
|
-
// eslint-disable-next-line react-hooks/immutability
|
|
88
|
-
references = references.map(reference => reference.toUpperCase());
|
|
89
|
-
|
|
90
|
-
const { hrefToRef, refToHref } = references.reduce(
|
|
91
|
-
({ hrefToRef, refToHref }, ref) => {
|
|
92
|
-
const href = randomId();
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
hrefToRef: { ...hrefToRef, [href]: ref },
|
|
96
|
-
refToHref: { ...refToHref, [ref]: href }
|
|
97
|
-
};
|
|
98
|
-
},
|
|
99
|
-
{ hrefToRef: {}, refToHref: {} }
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
const html = useMemo(() => {
|
|
103
|
-
const tree = markdownIt.parseInline(children, {
|
|
104
|
-
references: references.reduce(
|
|
105
|
-
(references, key) =>
|
|
106
|
-
// Mitigated through denylisting.
|
|
107
|
-
// eslint-disable-next-line security/detect-object-injection
|
|
108
|
-
isForbiddenPropertyName(key) ? references : { ...references, [key]: { href: `#${refToHref[key]}` } },
|
|
109
|
-
{}
|
|
110
|
-
)
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// Turn "<a href="#retry">Retry</a>" into "<button data-ref="retry" type="button">Retry</button>"
|
|
114
|
-
const updatedTree = replaceAnchorWithButton(tree);
|
|
115
|
-
|
|
116
|
-
return { __html: markdownIt.renderer.render(updatedTree) };
|
|
117
|
-
}, [children, refToHref, markdownIt, references]);
|
|
118
|
-
|
|
119
|
-
const handleClick = useCallback(
|
|
120
|
-
event => {
|
|
121
|
-
event.stopPropagation();
|
|
122
|
-
|
|
123
|
-
const href = event.target.getAttribute('data-markdown-href');
|
|
124
|
-
|
|
125
|
-
href &&
|
|
126
|
-
onReference &&
|
|
127
|
-
onReference(
|
|
128
|
-
createCustomEvent(
|
|
129
|
-
'reference',
|
|
130
|
-
// Mitigated through denylisting.
|
|
131
|
-
// eslint-disable-next-line security/detect-object-injection
|
|
132
|
-
isForbiddenPropertyName(href) ? {} : { data: hrefToRef[href] }
|
|
133
|
-
)
|
|
134
|
-
);
|
|
135
|
-
},
|
|
136
|
-
[hrefToRef, onReference]
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
return <span className={className} dangerouslySetInnerHTML={html} onClick={handleClick} />;
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
InlineMarkdown.defaultProps = {
|
|
143
|
-
children: '',
|
|
144
|
-
onReference: undefined,
|
|
145
|
-
references: []
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
InlineMarkdown.propTypes = {
|
|
149
|
-
children: PropTypes.string,
|
|
150
|
-
onReference: PropTypes.func,
|
|
151
|
-
references: PropTypes.arrayOf(PropTypes.string)
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
export default InlineMarkdown;
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import updateMarkdownAttrs from './updateMarkdownAttrs';
|
|
2
|
-
import walkMarkdownTokens from './walkMarkdownTokens';
|
|
3
|
-
|
|
4
|
-
export default function addTargetBlankToHyperlinksMarkdown(tokens) {
|
|
5
|
-
return walkMarkdownTokens(tokens, token => {
|
|
6
|
-
switch (token.type) {
|
|
7
|
-
case 'link_open':
|
|
8
|
-
token = updateMarkdownAttrs(token, attrs =>
|
|
9
|
-
// Adds only for external links, e.g. https://, data:
|
|
10
|
-
// Don't add for internal links, e.g. #ref-1, ?q=doc
|
|
11
|
-
/^\w/u.test(attrs.href)
|
|
12
|
-
? {
|
|
13
|
-
...attrs,
|
|
14
|
-
rel: 'noopener noreferrer',
|
|
15
|
-
target: '_blank'
|
|
16
|
-
}
|
|
17
|
-
: attrs
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
break;
|
|
21
|
-
|
|
22
|
-
default:
|
|
23
|
-
break;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return token;
|
|
27
|
-
});
|
|
28
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import MarkdownIt from 'markdown-it';
|
|
2
|
-
|
|
3
|
-
import addTargetBlankToHyperlinksMarkdown from './addTargetBlankToHyperlinksMarkdown';
|
|
4
|
-
|
|
5
|
-
test('add to external links', () => {
|
|
6
|
-
const markdownIt = new MarkdownIt();
|
|
7
|
-
const markdown = 'Hello, [Microsoft](https://microsoft.com/)!';
|
|
8
|
-
const tree = markdownIt.parseInline(markdown);
|
|
9
|
-
const updatedTree = addTargetBlankToHyperlinksMarkdown(tree);
|
|
10
|
-
const html = markdownIt.renderer.render(updatedTree);
|
|
11
|
-
|
|
12
|
-
expect(html).toMatchInlineSnapshot(
|
|
13
|
-
`"Hello, <a href="https://microsoft.com/" rel="noopener noreferrer" target="_blank">Microsoft</a>!"`
|
|
14
|
-
);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
test("don't add for hashes", () => {
|
|
18
|
-
const markdownIt = new MarkdownIt();
|
|
19
|
-
const markdown = 'Hello, [Microsoft](#microsoft)!';
|
|
20
|
-
const tree = markdownIt.parseInline(markdown);
|
|
21
|
-
const updatedTree = addTargetBlankToHyperlinksMarkdown(tree);
|
|
22
|
-
const html = markdownIt.renderer.render(updatedTree);
|
|
23
|
-
|
|
24
|
-
expect(html).toMatchInlineSnapshot(`"Hello, <a href="#microsoft">Microsoft</a>!"`);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test("don't add for searches", () => {
|
|
28
|
-
const markdownIt = new MarkdownIt();
|
|
29
|
-
const markdown = 'Hello, [Microsoft](?q=microsoft)!';
|
|
30
|
-
const tree = markdownIt.parseInline(markdown);
|
|
31
|
-
const updatedTree = addTargetBlankToHyperlinksMarkdown(tree);
|
|
32
|
-
const html = markdownIt.renderer.render(updatedTree);
|
|
33
|
-
|
|
34
|
-
expect(html).toMatchInlineSnapshot(`"Hello, <a href="?q=microsoft">Microsoft</a>!"`);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test("don't add for cross references", () => {
|
|
38
|
-
const markdownIt = new MarkdownIt();
|
|
39
|
-
const markdown = 'Hello, [Microsoft]!';
|
|
40
|
-
const tree = markdownIt.parseInline(markdown, { references: { MICROSOFT: { href: '#microsoft' } } });
|
|
41
|
-
const updatedTree = addTargetBlankToHyperlinksMarkdown(tree);
|
|
42
|
-
const html = markdownIt.renderer.render(updatedTree);
|
|
43
|
-
|
|
44
|
-
expect(html).toMatchInlineSnapshot(`"Hello, <a href="#microsoft">Microsoft</a>!"`);
|
|
45
|
-
});
|
package/src/Utils/betterLinks.ts
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
/* eslint-disable security/detect-object-injection */
|
|
2
|
-
import MarkdownIt from 'markdown-it';
|
|
3
|
-
|
|
4
|
-
function iterator(md, ruleName, tokenType, iterator) {
|
|
5
|
-
function scan(state) {
|
|
6
|
-
const { env = {} } = state;
|
|
7
|
-
for (let blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) {
|
|
8
|
-
if (state.tokens[blkIdx].type !== 'inline') {
|
|
9
|
-
continue;
|
|
10
|
-
}
|
|
11
|
-
const inlineTokens = state.tokens[blkIdx].children;
|
|
12
|
-
for (let i = inlineTokens.length - 1; i >= 0; i--) {
|
|
13
|
-
if (inlineTokens[i].type !== tokenType) {
|
|
14
|
-
continue;
|
|
15
|
-
}
|
|
16
|
-
iterator(inlineTokens, i, env);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
md.core.ruler.push(ruleName, scan);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Put a transparent pixel instead of the "open in new window" icon, so developers can easily modify the icon in CSS.
|
|
24
|
-
const TRANSPARENT_GIF = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
|
25
|
-
|
|
26
|
-
type AttributeSetter = false | string | ((value?: string) => string);
|
|
27
|
-
|
|
28
|
-
export type LinkOptions = {
|
|
29
|
-
/** Value of "aria-label" attribute of the link. If set to `false`, remove existing attribute. */
|
|
30
|
-
ariaLabel?: AttributeSetter;
|
|
31
|
-
|
|
32
|
-
/** Turns this link into a <button> with "value" attribute instead of "href". */
|
|
33
|
-
asButton?: boolean;
|
|
34
|
-
|
|
35
|
-
/** Value of "class" attribute of the link. If set to `false`, remove existing attribute. */
|
|
36
|
-
className?: AttributeSetter;
|
|
37
|
-
|
|
38
|
-
/** Alternate text of the image icon appended to the link. */
|
|
39
|
-
iconAlt?: string;
|
|
40
|
-
|
|
41
|
-
/** Class name of the image icon appended to the link. */
|
|
42
|
-
iconClassName?: string;
|
|
43
|
-
|
|
44
|
-
/** Value of "rel" attribute of the link. If set to `false`, remove existing attribute. */
|
|
45
|
-
rel?: AttributeSetter;
|
|
46
|
-
|
|
47
|
-
/** Value of "target" attribute of the link. If set to `false`, remove existing attribute. */
|
|
48
|
-
target?: AttributeSetter;
|
|
49
|
-
|
|
50
|
-
/** Value of "title" attribute of the link. If set to `false`, remove existing attribute. */
|
|
51
|
-
title?: AttributeSetter;
|
|
52
|
-
|
|
53
|
-
/** Wraps the link with zero-width space. */
|
|
54
|
-
wrapZeroWidthSpace?: boolean;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export type BetterLinkEnv = {
|
|
58
|
-
decorateLink?: (href: string, textContent: string, linkOptions: LinkOptions) => LinkOptions | undefined;
|
|
59
|
-
linkOptions?: LinkOptions;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
// This is used for parsing Markdown for external links.
|
|
63
|
-
const internalMarkdownIt = new MarkdownIt();
|
|
64
|
-
|
|
65
|
-
const ZERO_WIDTH_SPACE_TOKEN = {
|
|
66
|
-
content: '\u200b',
|
|
67
|
-
type: 'text'
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
function setTokenAttribute(attrs: Array<[string, string]>, name: string, value?: AttributeSetter) {
|
|
71
|
-
const index = attrs.findIndex(entry => entry[0] === name);
|
|
72
|
-
|
|
73
|
-
if (value === false) {
|
|
74
|
-
~index && attrs.splice(index, 1);
|
|
75
|
-
} else if (typeof value === 'string') {
|
|
76
|
-
if (~index) {
|
|
77
|
-
attrs[+index][1] = value;
|
|
78
|
-
} else {
|
|
79
|
-
attrs.push([name, value]);
|
|
80
|
-
}
|
|
81
|
-
} else if (typeof value === 'function') {
|
|
82
|
-
if (~index) {
|
|
83
|
-
attrs[+index][1] = value(attrs[+index][1]);
|
|
84
|
-
} else {
|
|
85
|
-
attrs.push([name, value()]);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const betterLinks = (markdown: typeof MarkdownIt): typeof MarkdownIt =>
|
|
91
|
-
markdown.use(iterator, 'url_new_win', 'link_open', (tokens, index, env) => {
|
|
92
|
-
const { decorateLink, linkOptions }: BetterLinkEnv = env;
|
|
93
|
-
|
|
94
|
-
const indexOfLinkCloseToken = tokens.indexOf(tokens.slice(index + 1).find(({ type }) => type === 'link_close'));
|
|
95
|
-
// eslint-disable-next-line no-magic-numbers
|
|
96
|
-
const updatedTokens = tokens.splice(index, ~indexOfLinkCloseToken ? indexOfLinkCloseToken - index + 1 : 2);
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
const [linkOpenToken] = updatedTokens;
|
|
100
|
-
const linkCloseToken = updatedTokens[updatedTokens.length - 1];
|
|
101
|
-
|
|
102
|
-
const [, href] = linkOpenToken.attrs.find(([name]) => name === 'href');
|
|
103
|
-
const nodesInLink = updatedTokens.slice(1, updatedTokens.length - 1);
|
|
104
|
-
|
|
105
|
-
const textContent = nodesInLink
|
|
106
|
-
.filter(({ type }) => type === 'text')
|
|
107
|
-
.map(({ content }) => content)
|
|
108
|
-
.join(' ');
|
|
109
|
-
|
|
110
|
-
const decoration = decorateLink?.(href, textContent, linkOptions);
|
|
111
|
-
|
|
112
|
-
if (!decoration) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const { ariaLabel, asButton, className, iconAlt, iconClassName, rel, target, title, wrapZeroWidthSpace } =
|
|
117
|
-
decoration;
|
|
118
|
-
|
|
119
|
-
setTokenAttribute(linkOpenToken.attrs, 'aria-label', ariaLabel);
|
|
120
|
-
setTokenAttribute(linkOpenToken.attrs, 'class', className);
|
|
121
|
-
setTokenAttribute(linkOpenToken.attrs, 'title', title);
|
|
122
|
-
|
|
123
|
-
if (iconClassName) {
|
|
124
|
-
const iconTokens = internalMarkdownIt.parseInline(``)[0].children;
|
|
125
|
-
|
|
126
|
-
setTokenAttribute(iconTokens[0].attrs, 'class', iconClassName);
|
|
127
|
-
setTokenAttribute(iconTokens[0].attrs, 'title', iconAlt);
|
|
128
|
-
|
|
129
|
-
// Add an icon before </a>.
|
|
130
|
-
// eslint-disable-next-line no-magic-numbers
|
|
131
|
-
updatedTokens.splice(-1, 0, ...iconTokens);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (asButton) {
|
|
135
|
-
setTokenAttribute(linkOpenToken.attrs, 'href', false);
|
|
136
|
-
|
|
137
|
-
linkOpenToken.tag = 'button';
|
|
138
|
-
|
|
139
|
-
setTokenAttribute(linkOpenToken.attrs, 'type', 'button');
|
|
140
|
-
setTokenAttribute(linkOpenToken.attrs, 'value', href);
|
|
141
|
-
|
|
142
|
-
linkCloseToken.tag = 'button';
|
|
143
|
-
} else {
|
|
144
|
-
setTokenAttribute(linkOpenToken.attrs, 'rel', rel);
|
|
145
|
-
setTokenAttribute(linkOpenToken.attrs, 'target', target);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (wrapZeroWidthSpace) {
|
|
149
|
-
updatedTokens.splice(0, 0, ZERO_WIDTH_SPACE_TOKEN);
|
|
150
|
-
updatedTokens.splice(Infinity, 0, ZERO_WIDTH_SPACE_TOKEN);
|
|
151
|
-
}
|
|
152
|
-
} finally {
|
|
153
|
-
tokens.splice(index, 0, ...updatedTokens);
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
export default betterLinks;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export default function parseDocumentFragmentFromString(html: string): DocumentFragment {
|
|
2
|
-
const parser = new DOMParser();
|
|
3
|
-
const parsedDocument = parser.parseFromString(html, 'text/html');
|
|
4
|
-
const fragment = parsedDocument.createDocumentFragment();
|
|
5
|
-
|
|
6
|
-
fragment.append(...parsedDocument.body.childNodes);
|
|
7
|
-
|
|
8
|
-
return fragment;
|
|
9
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import updateIn from 'simple-update-in';
|
|
2
|
-
|
|
3
|
-
export default function updateMarkdownItAttrs(token, updater) {
|
|
4
|
-
return updateIn(token, ['attrs'], attrs => {
|
|
5
|
-
const map = Object.fromEntries(attrs);
|
|
6
|
-
const nextMap = updater(map);
|
|
7
|
-
|
|
8
|
-
return Object.entries(nextMap);
|
|
9
|
-
});
|
|
10
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import updateMarkdownAttrs from './updateMarkdownAttrs';
|
|
2
|
-
|
|
3
|
-
test('add "rel" and "target" attributes', () => {
|
|
4
|
-
const token = {
|
|
5
|
-
attrs: [['href', 'https://example.org/']]
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
const actual = updateMarkdownAttrs(token, attrs => ({ ...attrs, rel: 'noopener noreferrer', target: '_blank' }));
|
|
9
|
-
|
|
10
|
-
expect(actual).toMatchInlineSnapshot(`
|
|
11
|
-
{
|
|
12
|
-
"attrs": [
|
|
13
|
-
[
|
|
14
|
-
"href",
|
|
15
|
-
"https://example.org/",
|
|
16
|
-
],
|
|
17
|
-
[
|
|
18
|
-
"rel",
|
|
19
|
-
"noopener noreferrer",
|
|
20
|
-
],
|
|
21
|
-
[
|
|
22
|
-
"target",
|
|
23
|
-
"_blank",
|
|
24
|
-
],
|
|
25
|
-
],
|
|
26
|
-
}
|
|
27
|
-
`);
|
|
28
|
-
|
|
29
|
-
// The token passed in should kept unchanged
|
|
30
|
-
expect(token).toMatchInlineSnapshot(`
|
|
31
|
-
{
|
|
32
|
-
"attrs": [
|
|
33
|
-
[
|
|
34
|
-
"href",
|
|
35
|
-
"https://example.org/",
|
|
36
|
-
],
|
|
37
|
-
],
|
|
38
|
-
}
|
|
39
|
-
`);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('replace "href" attribute', () => {
|
|
43
|
-
const token = {
|
|
44
|
-
attrs: [['href', 'https://example.org/']]
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const actual = updateMarkdownAttrs(token, () => ({ href: 'https://microsoft.com/' }));
|
|
48
|
-
|
|
49
|
-
expect(actual).toMatchInlineSnapshot(`
|
|
50
|
-
{
|
|
51
|
-
"attrs": [
|
|
52
|
-
[
|
|
53
|
-
"href",
|
|
54
|
-
"https://microsoft.com/",
|
|
55
|
-
],
|
|
56
|
-
],
|
|
57
|
-
}
|
|
58
|
-
`);
|
|
59
|
-
|
|
60
|
-
// The token passed in should kept unchanged
|
|
61
|
-
expect(token).toMatchInlineSnapshot(`
|
|
62
|
-
{
|
|
63
|
-
"attrs": [
|
|
64
|
-
[
|
|
65
|
-
"href",
|
|
66
|
-
"https://example.org/",
|
|
67
|
-
],
|
|
68
|
-
],
|
|
69
|
-
}
|
|
70
|
-
`);
|
|
71
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export default function walkMarkdownTokens(tokens, walker) {
|
|
2
|
-
return tokens.map(token => {
|
|
3
|
-
if (token) {
|
|
4
|
-
const nextToken = walker(token);
|
|
5
|
-
|
|
6
|
-
if (nextToken.children) {
|
|
7
|
-
nextToken.children = walkMarkdownTokens(nextToken.children, walker);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
return nextToken;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return token;
|
|
14
|
-
});
|
|
15
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import MarkdownIt from 'markdown-it';
|
|
2
|
-
import updateIn from 'simple-update-in';
|
|
3
|
-
|
|
4
|
-
import walkMarkdownTokens from './walkMarkdownTokens';
|
|
5
|
-
|
|
6
|
-
test('walk every node and add class="markdown"', () => {
|
|
7
|
-
const markdownIt = new MarkdownIt();
|
|
8
|
-
const tree = markdownIt.parse('Hello, [World](#world)!');
|
|
9
|
-
const patchedTree = walkMarkdownTokens(tree, token =>
|
|
10
|
-
updateIn(token, ['attrs'], attrs => [...(attrs || []), ['class', 'markdown']])
|
|
11
|
-
);
|
|
12
|
-
const actual = markdownIt.renderer.render(patchedTree);
|
|
13
|
-
|
|
14
|
-
expect(actual).toMatchInlineSnapshot(`
|
|
15
|
-
"<p class="markdown">Hello, <a href="#world" class="markdown">World</a class="markdown">!</p class="markdown">
|
|
16
|
-
"
|
|
17
|
-
`);
|
|
18
|
-
});
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import useWebChatUIContext from './useWebChatUIContext';
|
|
2
|
-
|
|
3
|
-
function useInternalRenderMarkdownInline() {
|
|
4
|
-
const { internalRenderMarkdownInline } = useWebChatUIContext();
|
|
5
|
-
|
|
6
|
-
return internalRenderMarkdownInline;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export default useInternalRenderMarkdownInline;
|
|
File without changes
|