1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
<script lang="ts">
import type { LinkableText, Action } from '@jet-app/app-store/api/models';
import { sanitizeHtml } from '@amp/web-app-components/src/utils/sanitize-html';
import LinkWrapper from '~/components/LinkWrapper.svelte';
export let item: LinkableText;
type Fragment = {
text: string;
action?: Action;
isTrailingPunctuation?: boolean;
};
const {
linkedSubstrings = {},
styledText: { rawText },
} = item;
// `LinkableText` items contain a `rawText` string, and an object of `linkedSubstrings`,
// where the key of the object is the substring to replace in the `rawText` and whose value
// is the `Action` that the link should trigger.
//
// That means we have to render replace the keys from `linkedSubstrings` in the `rawText`.
// To do this, we build a regex to match all the strings that are supposed to be linked,
// then build an array of objects representing the fully text, with the `Action` appended
// to the fragments that need to be linked.
const fragmentsToLink = Object.keys(linkedSubstrings);
let fragments: Fragment[];
if (fragmentsToLink.length === 0) {
fragments = [{ text: rawText }];
} else {
// Escapes regex-sensitive characters in the text, so characters like `.` or `+` don't act as regex operators
const cleanedFragmentsToLink = fragmentsToLink.map((fragment) =>
fragment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),
);
const pattern = new RegExp(
`(${cleanedFragmentsToLink.join('|')})`,
'g',
);
// After we split our text into an array representing the seqence of the raw text, with the
// linkable items as their own entries, we transform the array to contain include the linkable
// items actions, which we then use to determine if we want to render a `LinkWrapper` or plain-text.
fragments = rawText.split(pattern).map((fragment): Fragment => {
const action = linkedSubstrings[fragment];
if (action) {
return { action, text: fragment };
} else {
const isTrailingPunctuation = /^[.,;:!?)\]}"”»']+$/.test(
fragment.trim(),
);
return {
isTrailingPunctuation,
text: fragment,
};
}
});
}
</script>
{#each fragments as fragment}
{#if fragment.action}
<LinkWrapper
action={fragment.action}
includeExternalLinkArrowIcon={false}
>
{fragment.text}
</LinkWrapper>
{:else if fragment.isTrailingPunctuation}
<span class="trailing-punctuation">{fragment.text}</span>
{:else}
{@html sanitizeHtml(fragment.text)}
{/if}
{/each}
<style>
span :global(a:hover) {
text-decoration: underline;
}
.trailing-punctuation {
margin-inline-start: -0.45ch;
}
</style>
|