@apify/ui-library 0.71.1-featcolortokens-178953.56 → 0.71.1-featcolortokens-178953.63

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.
Files changed (134) hide show
  1. package/dist/src/design_system/colors/generated/{dark.d.ts → css_variables.dark.d.ts} +1 -1
  2. package/dist/src/design_system/colors/generated/css_variables.dark.d.ts.map +1 -0
  3. package/dist/src/design_system/colors/generated/{dark.js → css_variables.dark.js} +1 -1
  4. package/dist/src/design_system/colors/generated/css_variables.dark.js.map +1 -0
  5. package/dist/src/design_system/colors/generated/{light.d.ts → css_variables.light.d.ts} +1 -1
  6. package/dist/src/design_system/colors/generated/css_variables.light.d.ts.map +1 -0
  7. package/{src/design_system/colors/generated/light.ts → dist/src/design_system/colors/generated/css_variables.light.js} +1 -1
  8. package/dist/src/design_system/colors/generated/css_variables.light.js.map +1 -0
  9. package/dist/src/design_system/colors/generated/{palette.dark.d.ts → css_variables_palette.dark.d.ts} +1 -1
  10. package/dist/src/design_system/colors/generated/css_variables_palette.dark.d.ts.map +1 -0
  11. package/dist/src/design_system/colors/generated/{palette.dark.js → css_variables_palette.dark.js} +1 -1
  12. package/dist/src/design_system/colors/generated/css_variables_palette.dark.js.map +1 -0
  13. package/dist/src/design_system/colors/generated/{palette.light.d.ts → css_variables_palette.light.d.ts} +1 -1
  14. package/dist/src/design_system/colors/generated/css_variables_palette.light.d.ts.map +1 -0
  15. package/{src/design_system/colors/generated/palette.light.ts → dist/src/design_system/colors/generated/css_variables_palette.light.js} +1 -1
  16. package/dist/src/design_system/colors/generated/css_variables_palette.light.js.map +1 -0
  17. package/dist/src/design_system/colors/index.d.ts +4 -4
  18. package/dist/src/design_system/colors/index.d.ts.map +1 -1
  19. package/dist/src/design_system/colors/index.js +4 -4
  20. package/dist/src/design_system/colors/index.js.map +1 -1
  21. package/dist/tsconfig.build.tsbuildinfo +1 -0
  22. package/package.json +7 -5
  23. package/.stylelintrc +0 -12
  24. package/CHANGELOG.md +0 -3334
  25. package/CODEOWNERS +0 -7
  26. package/dist/src/design_system/colors/generated/dark.d.ts.map +0 -1
  27. package/dist/src/design_system/colors/generated/dark.js.map +0 -1
  28. package/dist/src/design_system/colors/generated/light.d.ts.map +0 -1
  29. package/dist/src/design_system/colors/generated/light.js +0 -147
  30. package/dist/src/design_system/colors/generated/light.js.map +0 -1
  31. package/dist/src/design_system/colors/generated/palette.dark.d.ts.map +0 -1
  32. package/dist/src/design_system/colors/generated/palette.dark.js.map +0 -1
  33. package/dist/src/design_system/colors/generated/palette.light.d.ts.map +0 -1
  34. package/dist/src/design_system/colors/generated/palette.light.js +0 -74
  35. package/dist/src/design_system/colors/generated/palette.light.js.map +0 -1
  36. package/dist/src/design_system/colors_theme.d.ts +0 -213
  37. package/dist/src/design_system/colors_theme.d.ts.map +0 -1
  38. package/dist/src/design_system/colors_theme.js +0 -213
  39. package/dist/src/design_system/colors_theme.js.map +0 -1
  40. package/dist/src/design_system/properties_theme.d.ts +0 -175
  41. package/dist/src/design_system/properties_theme.d.ts.map +0 -1
  42. package/dist/src/design_system/properties_theme.js +0 -315
  43. package/dist/src/design_system/properties_theme.js.map +0 -1
  44. package/eslint.config.mjs +0 -44
  45. package/src/codemods/generate_typograpy_tokens_files.mjs +0 -137
  46. package/src/components/action_link.tsx +0 -60
  47. package/src/components/actor_template_card.tsx +0 -116
  48. package/src/components/badge.tsx +0 -148
  49. package/src/components/banner.tsx +0 -94
  50. package/src/components/blog_article.tsx +0 -85
  51. package/src/components/box.tsx +0 -127
  52. package/src/components/button.tsx +0 -305
  53. package/src/components/chip.tsx +0 -128
  54. package/src/components/code/action_button.tsx +0 -96
  55. package/src/components/code/code_block/code_block.styled.tsx +0 -180
  56. package/src/components/code/code_block/code_block.tsx +0 -224
  57. package/src/components/code/code_block/code_block_with_tabs.tsx +0 -257
  58. package/src/components/code/code_block/utils.tsx +0 -67
  59. package/src/components/code/index.ts +0 -5
  60. package/src/components/code/inline_code/inline_code.tsx +0 -62
  61. package/src/components/code/one_line_code/one_line_code.tsx +0 -228
  62. package/src/components/code/prism_highlighter.tsx +0 -180
  63. package/src/components/color_wheel_gradient.tsx +0 -31
  64. package/src/components/floating/index.ts +0 -3
  65. package/src/components/floating/menu.tsx +0 -189
  66. package/src/components/floating/menu_common.tsx +0 -31
  67. package/src/components/floating/menu_components.tsx +0 -99
  68. package/src/components/image.tsx +0 -24
  69. package/src/components/index.ts +0 -22
  70. package/src/components/link.tsx +0 -114
  71. package/src/components/message.tsx +0 -153
  72. package/src/components/rating.tsx +0 -106
  73. package/src/components/readme_renderer/index.ts +0 -3
  74. package/src/components/readme_renderer/pythonize_value.ts +0 -76
  75. package/src/components/readme_renderer/table_of_contents.tsx +0 -272
  76. package/src/components/readme_renderer/utils.tsx +0 -46
  77. package/src/components/simple_markdown/index.ts +0 -2
  78. package/src/components/simple_markdown/simple_markdown.tsx +0 -214
  79. package/src/components/simple_markdown/simple_markdown_components.tsx +0 -293
  80. package/src/components/tabs/index.ts +0 -2
  81. package/src/components/tabs/tab.tsx +0 -217
  82. package/src/components/tabs/tabs.tsx +0 -169
  83. package/src/components/tag.tsx +0 -196
  84. package/src/components/text/heading_content.tsx +0 -56
  85. package/src/components/text/heading_marketing.tsx +0 -55
  86. package/src/components/text/heading_shared.tsx +0 -55
  87. package/src/components/text/index.ts +0 -19
  88. package/src/components/text/text_base.tsx +0 -52
  89. package/src/components/text/text_content.tsx +0 -104
  90. package/src/components/text/text_marketing.tsx +0 -152
  91. package/src/components/text/text_shared.tsx +0 -95
  92. package/src/components/tile/horizontal_tile.tsx +0 -77
  93. package/src/components/tile/index.ts +0 -2
  94. package/src/components/tile/shared.ts +0 -27
  95. package/src/components/tile/vertical_tile.tsx +0 -59
  96. package/src/components/to_consolidate/card.tsx +0 -141
  97. package/src/components/to_consolidate/index.ts +0 -4
  98. package/src/components/to_consolidate/markdown.tsx +0 -609
  99. package/src/components/to_consolidate/pagination.tsx +0 -136
  100. package/src/components/to_consolidate/tab_number_chip.tsx +0 -31
  101. package/src/design_system/colors/build_color_tokens.js +0 -175
  102. package/src/design_system/colors/figma_color_tokens.dark.json +0 -886
  103. package/src/design_system/colors/figma_color_tokens.light.json +0 -886
  104. package/src/design_system/colors/generated/colors_theme.dark.ts +0 -110
  105. package/src/design_system/colors/generated/colors_theme.light.ts +0 -110
  106. package/src/design_system/colors/generated/dark.ts +0 -147
  107. package/src/design_system/colors/generated/palette.dark.ts +0 -74
  108. package/src/design_system/colors/generated/properties_theme.ts +0 -179
  109. package/src/design_system/colors/index.ts +0 -7
  110. package/src/design_system/colors_theme.ts +0 -213
  111. package/src/design_system/properties_theme.ts +0 -453
  112. package/src/design_system/supernova_typography_tokens.json +0 -657
  113. package/src/design_system/theme.ts +0 -25
  114. package/src/design_system/tokens/index.ts +0 -5
  115. package/src/design_system/tokens/layouts.ts +0 -29
  116. package/src/design_system/tokens/radiuses.ts +0 -22
  117. package/src/design_system/tokens/shadows.ts +0 -22
  118. package/src/design_system/tokens/spaces.ts +0 -15
  119. package/src/design_system/tokens/transitions.ts +0 -19
  120. package/src/design_system/typography_theme.ts +0 -197
  121. package/src/index.ts +0 -8
  122. package/src/type_utils.ts +0 -7
  123. package/src/ui_dependency_provider.tsx +0 -58
  124. package/src/utils/copy_to_clipboard.ts +0 -24
  125. package/src/utils/image_color.ts +0 -42
  126. package/src/utils/index.ts +0 -4
  127. package/src/utils/resize_observer.ts +0 -18
  128. package/src/utils/sanitization.ts +0 -14
  129. package/tsconfig.build.json +0 -17
  130. package/tsconfig.json +0 -10
  131. /package/{src/design_system/colors/generated → style/colors}/dark.scss +0 -0
  132. /package/{src/design_system/colors/generated → style/colors}/light.scss +0 -0
  133. /package/{src/design_system/colors/generated → style/colors}/palette.dark.scss +0 -0
  134. /package/{src/design_system/colors/generated → style/colors}/palette.light.scss +0 -0
@@ -1,609 +0,0 @@
1
- import clsx from 'clsx';
2
- import _ from 'lodash';
3
- import qs from 'query-string';
4
- import React, { useMemo } from 'react';
5
- import type { Components } from 'react-markdown';
6
- import ReactMarkdown, { uriTransformer } from 'react-markdown';
7
- import type { AllowElement, Node } from 'react-markdown/lib/rehype-filter';
8
- import rehypeRaw from 'rehype-raw';
9
- import remarkGfm from 'remark-gfm';
10
- import styled from 'styled-components';
11
-
12
- import { LinkIcon } from '@apify/ui-icons';
13
-
14
- import type { UiThemeOption } from '../../design_system/theme.js';
15
- import { theme } from '../../design_system/theme.js';
16
- import { useCopyToClipboard } from '../../utils/index.js';
17
- import { CodeBlock, inlineCodeStyles, OneLineCode } from '../code/index.js';
18
- import { cleanMarkdown, slugifyHeadingChildren } from '../readme_renderer/utils.js';
19
-
20
- interface StyledReadmeProps {
21
- $scrollMarginTopPx: number;
22
- }
23
-
24
- const markdownClassNames = {
25
- COPY_BUTTON: 'Markdown-CopyButton',
26
- CODE_PRE: 'Markdown-CodePre',
27
- VIDEO_WRAPPER: 'Markdown-VideoWrapper',
28
- IFRAME_CONTAINER: 'Iframe-container',
29
- HEADING_COPY_ICON: 'ReadmeRenderer-headingCopyIcon',
30
- INLINE_CODE: 'Markdown-InlineCode',
31
- } as const;
32
-
33
- const StyledMarkdown = styled(ReactMarkdown) <StyledReadmeProps>`
34
- @font-face {
35
- font-family: "ellipsis-font";
36
- src: local("Courier");
37
- unicode-range: U+2026;
38
- size-adjust: 0%;
39
- }
40
-
41
- &>:first-child {
42
- margin-top: 0;
43
- }
44
-
45
- h1, h2, h3, h4, h5 {
46
- scroll-margin-top: ${({ $scrollMarginTopPx }) => `${$scrollMarginTopPx}px`};
47
- margin-top: ${theme.space.space32};
48
- margin-bottom: ${theme.space.space16};
49
- align-items: center;
50
-
51
- & > .${markdownClassNames.INLINE_CODE} {
52
- font-size: inherit !important;
53
- line-height: inherit !important;
54
- }
55
-
56
- /* Do no change the font style if the heading is defined as bold in the markdown and is wrapped in a <strong> tag */
57
- strong {
58
- font-size: inherit !important;
59
- line-height: inherit !important;
60
- font-weight: inherit !important;
61
- }
62
-
63
- a {
64
- text-decoration: none !important;
65
- }
66
-
67
- .${markdownClassNames.HEADING_COPY_ICON} {
68
- display: none;
69
- transform: translateX(${theme.space.space4});
70
- color: ${theme.color.neutral.textSubtle};
71
-
72
- svg {
73
- stroke: ${theme.color.primary.text};
74
- max-height: 1em !important;
75
- }
76
- }
77
-
78
- &:hover {
79
- .${markdownClassNames.HEADING_COPY_ICON} {
80
- display: inline-block;
81
- }
82
- }
83
- }
84
-
85
- p, li, strong, b, table {
86
- ${theme.typography.content.mobile.paragraph}
87
- @media (min-width: ${theme.layout.tablet}) {
88
- ${theme.typography.content.tablet.paragraph}
89
- }
90
- @media (min-width: ${theme.layout.desktop}) {
91
- ${theme.typography.content.desktop.paragraph}
92
- }
93
- a {
94
- overflow-wrap: break-word;
95
- text-decoration: none;
96
- &:hover {
97
- text-decoration: underline;
98
- }
99
- }
100
- }
101
-
102
- p {
103
- margin: ${theme.space.space16} 0;
104
- }
105
-
106
- h1 {
107
- ${theme.typography.content.mobile.heading1}
108
- @media (min-width: ${theme.layout.tablet}) {
109
- ${theme.typography.content.tablet.heading1}
110
- }
111
- @media (min-width: ${theme.layout.desktop}) {
112
- ${theme.typography.content.desktop.heading1}
113
- }
114
- }
115
-
116
- h2 {
117
- ${theme.typography.content.mobile.heading2}
118
- @media (min-width: ${theme.layout.tablet}) {
119
- ${theme.typography.content.tablet.heading2}
120
- }
121
- @media (min-width: ${theme.layout.desktop}) {
122
- ${theme.typography.content.desktop.heading2}
123
- }
124
- }
125
-
126
- h3 {
127
- ${theme.typography.content.mobile.heading3}
128
- @media (min-width: ${theme.layout.tablet}) {
129
- ${theme.typography.content.tablet.heading3}
130
- }
131
- @media (min-width: ${theme.layout.desktop}) {
132
- ${theme.typography.content.desktop.heading3}
133
- }
134
- }
135
-
136
- h4 {
137
- ${theme.typography.content.mobile.heading4}
138
- @media (min-width: ${theme.layout.tablet}) {
139
- ${theme.typography.content.tablet.heading4}
140
- }
141
- @media (min-width: ${theme.layout.desktop}) {
142
- ${theme.typography.content.desktop.heading4}
143
- }
144
- }
145
-
146
- h5 {
147
- ${theme.typography.content.mobile.heading5}
148
- @media (min-width: ${theme.layout.tablet}) {
149
- ${theme.typography.content.tablet.heading5}
150
- }
151
- @media (min-width: ${theme.layout.desktop}) {
152
- ${theme.typography.content.desktop.heading5}
153
- }
154
- }
155
-
156
- h6 {
157
- ${theme.typography.content.mobile.heading6}
158
- @media (min-width: ${theme.layout.tablet}) {
159
- ${theme.typography.content.tablet.heading6}
160
- }
161
- @media (min-width: ${theme.layout.desktop}) {
162
- ${theme.typography.content.desktop.heading6}
163
- }
164
- }
165
-
166
- img {
167
- max-width: 100%;
168
- }
169
-
170
- ul, ol {
171
- padding-left: ${theme.space.space32};
172
- }
173
-
174
- li {
175
- margin-top: ${theme.space.space4};
176
- }
177
-
178
- hr {
179
- color: ${theme.color.neutral.border};
180
- }
181
-
182
- strong {
183
- ${theme.typography.content.mobile.paragraphStrong}
184
- @media (min-width: ${theme.layout.tablet}) {
185
- ${theme.typography.content.tablet.paragraphStrong}
186
- }
187
- @media (min-width: ${theme.layout.desktop}) {
188
- ${theme.typography.content.desktop.paragraphStrong}
189
- }
190
- }
191
-
192
- blockquote {
193
- border-left: 2px solid ${theme.color.neutral.separatorSubtle};
194
- padding-left: ${theme.space.space16};
195
-
196
- color: ${theme.color.neutral.textMuted};
197
-
198
- p {
199
- margin-bottom: 0;
200
- }
201
- }
202
-
203
- table {
204
- display: block;
205
- overflow: auto;
206
- border-collapse: collapse;
207
-
208
- td, th {
209
- border: 1px solid ${theme.color.neutral.border};
210
- padding: ${theme.space.space16};
211
- text-align: left;
212
- }
213
-
214
- tr:nth-child(even):not([class]) {
215
- > th, > td {
216
- background-color: inherit;
217
- }
218
- }
219
- }
220
-
221
- th {
222
- ${theme.typography.content.mobile.paragraphStrong}
223
- @media (min-width: ${theme.layout.tablet}) {
224
- ${theme.typography.content.tablet.paragraphStrong}
225
- }
226
- @media (min-width: ${theme.layout.desktop}) {
227
- ${theme.typography.content.desktop.paragraphStrong}
228
- }
229
- }
230
-
231
- a:hover {
232
- text-decoration: underline;
233
- }
234
-
235
- .${markdownClassNames.VIDEO_WRAPPER} {
236
- width: 65%;
237
- display: block;
238
-
239
- .${markdownClassNames.IFRAME_CONTAINER} {
240
- position: relative;
241
- overflow: hidden;
242
- width: 100%;
243
- padding-top: 56.25%;
244
- display: block;
245
-
246
- iframe {
247
- position: absolute;
248
- top: 0;
249
- left: 0;
250
- bottom: 0;
251
- right: 0;
252
- width: 100%;
253
- height: 100%;
254
- border: 0px;
255
- }
256
- }
257
- }
258
-
259
- .${markdownClassNames.INLINE_CODE} {
260
- ${inlineCodeStyles}
261
- ${theme.typography.content.mobile.snippet}
262
- /* prevents long URLs from overflowing its container */
263
- hyphens: auto;
264
- overflow-wrap: break-word;
265
-
266
- @media (min-width: ${theme.layout.tablet}) {
267
- ${theme.typography.content.tablet.snippet}
268
- }
269
-
270
- @media (min-width: ${theme.layout.desktop}) {
271
- ${theme.typography.content.desktop.snippet}
272
- }
273
-
274
- b, strong{
275
- font-size: inherit !important;
276
- line-height: inherit !important;
277
- font-family: inherit !important;
278
- }
279
- }
280
- `;
281
-
282
- export interface HeadingRendererProps {
283
- node: {
284
- tagName: keyof JSX.IntrinsicElements;
285
- };
286
- children: React.ReactNode;
287
- }
288
-
289
- /**
290
- * Adds ids to headings
291
- */
292
- const DefaultHeadingRenderer = ({ node, children }: HeadingRendererProps) => {
293
- const Tag = node.tagName;
294
- const id = slugifyHeadingChildren(children);
295
- return <Tag id={id}>{children}</Tag>;
296
- };
297
-
298
- interface CodeRendererComponentProps {
299
- inline: boolean;
300
- className: string;
301
- children: React.ReactNode;
302
- }
303
-
304
- function CodeRenderer({
305
- inline,
306
- className,
307
- children,
308
- }: CodeRendererComponentProps) {
309
- if (inline) {
310
- return <code className={clsx(className, markdownClassNames.INLINE_CODE)}>{children}</code>;
311
- }
312
-
313
- const code = String(children).replace(/\n$/, '').trim();
314
- const match = /language-(\w+)/.exec(className || '');
315
- const language = match?.[1]?.toLowerCase();
316
- const isOneLineCode = code.split('\n').length <= 1;
317
-
318
- if (isOneLineCode) {
319
- return (
320
- <OneLineCode
321
- language={language}
322
- fullWidth
323
- >
324
- {code}
325
- </OneLineCode>
326
- );
327
- }
328
-
329
- return (
330
- <CodeBlock
331
- content={code}
332
- language={language}
333
- hideLineNumbers
334
- fullWidth
335
- hideBashHeader
336
- hideBashPromptPrefixes
337
- />
338
- );
339
- }
340
-
341
- const youtubeRegex = /^(?:https?:\/\/)?(?:www\.)?(?:m\.)?(?:youtube(?:-nocookie)?\.com|youtu\.be)\/(?:watch\?v=|embed\/|v\/)?([a-zA-Z0-9\-_]+)(?:\S*)?$/;
342
- const vimeoRegex = /^((?:https?:\/\/)?(?:player\.)?vimeo\.com(?:\/video)?\/(\d+))$/;
343
-
344
- const getVideoSrc = (link: string) => {
345
- const youtubeLink = link.match(youtubeRegex);
346
- const vimeoLink = link.match(vimeoRegex);
347
- let src;
348
- if (youtubeLink) {
349
- // add rel=0 to disable related videos from other channels at the end of the video
350
- // add enablejsapi=1 to enable tracking videos via API through Google Analytics
351
- src = qs.stringifyUrl({ url: `https://www.youtube.com/embed/${youtubeLink[1]}`, query: { rel: 0, enablejsapi: 1 } });
352
- }
353
- if (vimeoLink) src = `https://player.vimeo.com/video/${vimeoLink[2]}`;
354
-
355
- return src;
356
- };
357
-
358
- interface VideoProps {
359
- src: string;
360
- }
361
- const Video = ({ src }: VideoProps) => {
362
- return (
363
- <span className={markdownClassNames.VIDEO_WRAPPER}>
364
- <span className="Iframe-container">
365
- <iframe loading="lazy" allowFullScreen src={src} />
366
- </span>
367
- </span>
368
- );
369
- };
370
-
371
- export const APIFY_HOSTNAMES = [
372
- 'apify.com',
373
- 'docs.apify.com',
374
- 'help.apify.com',
375
- 'sdk.apify.com',
376
- 'blog.apify.com',
377
- 'kb.apify.com', // old knowledge base domain (redirects to help.apify.com)
378
- 'my.apify.com', // old console.domain (redirects to console.apify.com)
379
- 'console.apify.com',
380
- 'crawlee.dev',
381
- ];
382
-
383
- interface LinkRendererProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
384
- node: {
385
- properties: {
386
- enableEmbeddedVideo: boolean;
387
- };
388
- };
389
- href: string;
390
- }
391
-
392
- interface LinkRendererOptions {
393
- hostname?: string,
394
- Link?: React.ElementType;
395
- }
396
-
397
- // We want no-follow for external links
398
- // Also if link is a video from youtube or vimeo, we want to render it as iframe
399
- // Allowing to pass hostname to check if the link is an Apify link to the same hostname (is needed for SSR on the web)
400
- const DefaultLinkRenderer = ({ node, href, ...props }: LinkRendererProps, { hostname, Link }: LinkRendererOptions, isUserGeneratedContent?: boolean) => {
401
- const videoSrc = node.properties.enableEmbeddedVideo && getVideoSrc(href);
402
- if (videoSrc) return <Video src={videoSrc} />;
403
-
404
- // check for anchor links, hash link are not parsable by URL constructor
405
- const isHashLink = (href || '').startsWith('#');
406
- if (isHashLink) return <a href={href} {...props} />;
407
-
408
- let urlParsed: URL | undefined;
409
- try { // TODO: replace with URL.canParse() when we have node 19+ 🥲
410
- urlParsed = new URL(href);
411
- } catch {
412
- // Probably invalid url, go on as it doesn't make sense to track this error
413
- // This component is used on all Actor pages - readmes.
414
- // So it is mainly for the user-generated content - there can be many invalid links and we won't do anything about it anyway.
415
- return <span>{href}</span>;
416
- }
417
-
418
- if (!hostname && (typeof window !== 'undefined' && !window?.location?.hostname)) return <a href={href} {...props} />;
419
-
420
- const currentHostname = hostname || (typeof window !== 'undefined' && window?.location?.hostname.toLowerCase());
421
- const hasDifferentHostname = urlParsed && (urlParsed.hostname.toLowerCase() !== currentHostname);
422
- const isApifyLink = urlParsed
423
- && APIFY_HOSTNAMES.includes(urlParsed.hostname.toLowerCase())
424
- && urlParsed.protocol === 'https:'; // we want to disqualify links that have http: protocol. It's a mistake on users' side that we are penalized for.
425
-
426
- // Same host name, use the provided link for internal navigation
427
- if (!hasDifferentHostname && Link) {
428
- return <Link to={urlParsed} {...props} />;
429
- }
430
-
431
- let linkProps = {};
432
-
433
- // If false, we want to open the link in the same tab (linkProps won't have any props)
434
- if (hasDifferentHostname) {
435
- // If the link is an Apify link to the different hostname, we want to open in a new tab
436
- if (isApifyLink) {
437
- linkProps = { target: '_blank', rel: 'noopener' };
438
- } else {
439
- // If an external non-Apify link, we want to always open it in a new tab and add rel="noopener nofollow"
440
- // USG - User Generated Content (new rel attribute for nofollow links)
441
- // Google says:
442
- // It’s valid to use nofollow with the new attributes — such as rel=”nofollow ugc”
443
- // — if you wish to be backwards-compatible with services that don’t support the new attributes.
444
- linkProps = { target: '_blank', rel: clsx('noopener nofollow', isUserGeneratedContent && 'ugc') };
445
- }
446
- }
447
-
448
- return <a href={href} {...props} {...linkProps} />;
449
- };
450
-
451
- // node is just to omit from exported props
452
- interface ParagraphRendererProps extends React.HTMLAttributes<HTMLParagraphElement> {
453
- node?: {
454
- children: {
455
- tagName: keyof JSX.IntrinsicElements;
456
- type: string;
457
- value: string;
458
- properties: {
459
- enableEmbeddedVideo: boolean;
460
- };
461
- }[];
462
- };
463
- }
464
-
465
- // If the paragraph is just a link to youtube or vimeo, we want to render it as iframe
466
- const ParagraphRenderer = ({ node, ...props }: ParagraphRendererProps) => {
467
- // We enable embedded video only is surrounded by new lines (will be rendered as only child in paragraph)
468
- node?.children.forEach((child) => {
469
- // eslint-disable-next-line no-param-reassign
470
- if (child.tagName === 'a') child.properties.enableEmbeddedVideo = node.children.length === 1; // This means the link is the only child
471
- });
472
- const child = node?.children[0];
473
- const isText = child?.type === 'text';
474
-
475
- const videoSrc = isText && getVideoSrc(child.value);
476
- if (videoSrc) return <Video src={videoSrc} />;
477
-
478
- return <p {...props} />;
479
- };
480
-
481
- const HeadingRendererWithAnchor = ({ node, children }: HeadingRendererProps) => {
482
- const Tag = node.tagName;
483
- const id = slugifyHeadingChildren(children);
484
-
485
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
486
- const [_isCopied, handleClick] = useCopyToClipboard({
487
- text: id ?? '',
488
- // We want the whole URL to be copied, not just the ID
489
- transform: (text) => {
490
- const url = new URL(window.location.href);
491
- url.hash = `#${text}`;
492
- return url.toString();
493
- },
494
- });
495
-
496
- return (
497
- <Tag id={id}>
498
- {children}
499
- <a
500
- onClick={handleClick}
501
- href={`#${id}`}
502
- className={`${markdownClassNames.HEADING_COPY_ICON}`}
503
- >
504
- <LinkIcon size="16" />
505
- </a>
506
- </Tag>
507
- );
508
- };
509
-
510
- export interface MarkdownProps {
511
- markdown: string;
512
- transformLinkUri?: (href: string) => string;
513
- transformImageUri?: (src: string) => string;
514
- className?: string;
515
- theme?: UiThemeOption;
516
- scrollMarginTopPx?: number; // Offset from top of page to account for sticky header
517
- // Function where we can define which elements are allowed in the markdown. See // Function where we can define which elements are allowed in the markdown. See https://github.com/remarkjs/react-markdown#props for more info
518
- allowElement?: AllowElement
519
- isUserGeneratedContent?: boolean;
520
- currentPathHostname?: string;
521
- addHeadingAnchors?: boolean;
522
- Link?: React.ElementType;
523
- LinkRenderer?: (props: LinkRendererProps, options: LinkRendererOptions, isUserGeneratedContent?: boolean) => React.ReactElement;
524
- lazyLoadImages?: boolean;
525
- }
526
-
527
- const Markdown = ({
528
- markdown,
529
- transformLinkUri,
530
- transformImageUri,
531
- className,
532
- scrollMarginTopPx = 10,
533
- allowElement,
534
- currentPathHostname,
535
- addHeadingAnchors,
536
- isUserGeneratedContent,
537
- Link,
538
- LinkRenderer,
539
- lazyLoadImages,
540
- }: MarkdownProps) => {
541
- const headingRenderer = addHeadingAnchors ? HeadingRendererWithAnchor : DefaultHeadingRenderer;
542
- const cleanedMarkdown = useMemo(() => cleanMarkdown(markdown), [markdown]);
543
- return (
544
- <StyledMarkdown
545
- $scrollMarginTopPx={scrollMarginTopPx}
546
- className={className}
547
- rehypePlugins={[rehypeRaw]}
548
- remarkPlugins={[remarkGfm]}
549
- allowedElements={[
550
- 'a',
551
- 'b',
552
- 'blockquote',
553
- 'br',
554
- 'center',
555
- 'code',
556
- 'del',
557
- 'em',
558
- 'h1',
559
- 'h2',
560
- 'h3',
561
- 'h4',
562
- 'h5',
563
- 'hr',
564
- 'i',
565
- 'img',
566
- 'li',
567
- 'ol',
568
- 'p',
569
- 'pre',
570
- 'span',
571
- 'strong',
572
- 'table',
573
- 'tbody',
574
- 'td',
575
- 'tfoot',
576
- 'th',
577
- 'thead',
578
- 'tr',
579
- 'u',
580
- 'ul',
581
- ]}
582
- allowElement={allowElement}
583
- components={{
584
- h1: headingRenderer,
585
- h2: headingRenderer,
586
- h3: headingRenderer,
587
- h4: headingRenderer,
588
- h5: headingRenderer,
589
- a: (linkProps: LinkRendererProps) => (
590
- LinkRenderer
591
- ? LinkRenderer(linkProps, { hostname: currentPathHostname, Link }, isUserGeneratedContent)
592
- : DefaultLinkRenderer(linkProps, { hostname: currentPathHostname, Link }, isUserGeneratedContent)
593
- ),
594
- code: (codeProps: CodeRendererComponentProps) => CodeRenderer(codeProps),
595
- p: ParagraphRenderer,
596
- img: ({ node, ...imageProps }: React.ImgHTMLAttributes<HTMLImageElement> & { node?: Node }) => (
597
- <img {...imageProps} {...(lazyLoadImages ? { loading: 'lazy' } : {})} /> // node is injected by rehype-raw plugin and causing invalid prop
598
- ),
599
- } as unknown as Components}
600
- transformLinkUri={(href) => (transformLinkUri ? uriTransformer(transformLinkUri(href)) : uriTransformer(href))}
601
- transformImageUri={transformImageUri}
602
- >
603
- {cleanedMarkdown}
604
- </StyledMarkdown>
605
- );
606
- };
607
-
608
- const MemoizedMarkdown = React.memo(Markdown, (prevProps, nextProps) => _.isEqual(prevProps, nextProps));
609
- export { MemoizedMarkdown as Markdown };