@apify/docs-theme 1.0.212 → 1.0.214

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apify/docs-theme",
3
- "version": "1.0.212",
3
+ "version": "1.0.214",
4
4
  "description": "",
5
5
  "main": "./src/index.js",
6
6
  "files": [
@@ -20,6 +20,8 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@apify/docs-search-modal": "^1.2.2",
23
+ "@apify/ui-library": "^1.97.2",
24
+ "@apify/ui-icons": "^1.19.0",
23
25
  "@docusaurus/theme-common": "^3.7.0",
24
26
  "@stackql/docusaurus-plugin-hubspot": "^1.1.0",
25
27
  "algoliasearch": "^5.19.0",
@@ -7,6 +7,8 @@ import MDXContent from '@theme/MDXContent';
7
7
  import clsx from 'clsx';
8
8
  import React from 'react';
9
9
 
10
+ import styles from './styles.module.css';
11
+
10
12
  function useSyntheticTitle() {
11
13
  const { metadata, frontMatter, contentTitle } = useDoc();
12
14
  const shouldRender = !frontMatter.hide_title && typeof contentTitle === 'undefined';
@@ -62,11 +64,15 @@ export default function DocItemContent({ children }) {
62
64
  const shouldShowLLMButtons = allowedPaths.some((path) => location.pathname.startsWith(path))
63
65
  && !disallowedPaths.some((path) => location.pathname.includes(path));
64
66
 
65
- return (
66
- <div className={clsx(ThemeClassNames.docs.docMarkdown, 'markdown')}>
67
- {syntheticTitle && <Heading as="h1">{syntheticTitle}</Heading>}
68
- {shouldShowLLMButtons && <LLMButtons />}
69
- <MDXContent>{children}</MDXContent>
70
- </div>
71
- );
67
+ return (
68
+ <div className={clsx(ThemeClassNames.docs.docMarkdown, 'markdown')}>
69
+ {(syntheticTitle || shouldShowLLMButtons) && (
70
+ <div className={styles.docItemContent}>
71
+ {syntheticTitle && <Heading as="h1">{syntheticTitle}</Heading>}
72
+ {shouldShowLLMButtons && <LLMButtons />}
73
+ </div>
74
+ )}
75
+ <MDXContent>{children}</MDXContent>
76
+ </div>
77
+ );
72
78
  }
@@ -0,0 +1,17 @@
1
+ .docItemContent {
2
+ display: flex;
3
+ align-items: center;
4
+ flex-wrap: wrap;
5
+ /* move the h1 margin to padding */
6
+ h1 {
7
+ margin-bottom: 0 !important;
8
+ }
9
+ padding-bottom: calc(
10
+ var(--ifm-h1-vertical-rhythm-bottom) * var(--ifm-leading)
11
+ );
12
+
13
+ @media (max-width: 767px) {
14
+ flex-direction: column;
15
+ align-items: flex-start;
16
+ }
17
+ }
@@ -1,15 +1,295 @@
1
- import React from 'react';
1
+ import clsx from 'clsx';
2
+ import React, { useCallback, useState } from 'react';
3
+
4
+ import {
5
+ AnthropicIcon,
6
+ ChatGptIcon,
7
+ CheckIcon,
8
+ ChevronDownIcon,
9
+ CopyIcon,
10
+ ExternalLinkIcon,
11
+ LoaderIcon,
12
+ MarkdownIcon,
13
+ PerplexityIcon,
14
+ } from '@apify/ui-icons';
15
+ import { Menu, Text, theme } from '@apify/ui-library';
2
16
 
3
- import CopyForLLM from './CopyForLLM';
4
17
  import styles from './styles.module.css';
5
- import ViewAsMarkdown from './ViewAsMarkdown';
6
18
 
7
- export default function LLMButtons() {
8
- return (
9
- <div className={styles.llmButtonsContainer}>
10
- <ViewAsMarkdown />
11
- <div className={styles.llmButtonsSeparator}></div>
12
- <CopyForLLM />
19
+ const DROPDOWN_OPTIONS = [
20
+ {
21
+ label: 'Copy for LLM',
22
+ description: 'Copy page as Markdown for LLMs',
23
+ showExternalIcon: false,
24
+ Icon: CopyIcon,
25
+ value: 'copyForLLM',
26
+ },
27
+ {
28
+ label: 'View as Markdown',
29
+ description: 'View this page as plain text',
30
+ showExternalIcon: true,
31
+ Icon: MarkdownIcon,
32
+ value: 'viewAsMarkdown',
33
+ },
34
+ {
35
+ label: 'Open in ChatGPT',
36
+ description: 'Ask questions about this page',
37
+ showExternalIcon: true,
38
+ Icon: ChatGptIcon,
39
+ value: 'openInChatGPT',
40
+ },
41
+ {
42
+ label: 'Open in Claude',
43
+ description: 'Ask questions about this page',
44
+ showExternalIcon: true,
45
+ Icon: AnthropicIcon,
46
+ value: 'openInClaude',
47
+ },
48
+ {
49
+ label: 'Open in Perplexity',
50
+ description: 'Ask questions about this page',
51
+ showExternalIcon: true,
52
+ Icon: PerplexityIcon,
53
+ value: 'openInPerplexity',
54
+ },
55
+ ];
56
+
57
+ const getPrompt = (currentUrl) => `Read from ${currentUrl} so I can ask questions about it.`;
58
+ const getMarkdownUrl = (currentUrl) => `${currentUrl}.md`;
59
+
60
+ const onOpenInChatGPTClick = () => {
61
+ if (window.analytics) {
62
+ window.analytics.track('Clicked', {
63
+ app: 'docs',
64
+ button_text: 'Open in ChatGPT',
65
+ element: 'llm-buttons.openInChatGPT',
66
+ });
67
+ }
68
+
69
+ const prompt = getPrompt(window.location.href);
70
+
71
+ try {
72
+ window.open(
73
+ `https://chatgpt.com/?hints=search&q=${encodeURIComponent(prompt)}`,
74
+ '_blank',
75
+ );
76
+ } catch (error) {
77
+ console.error('Error opening ChatGPT:', error);
78
+ }
79
+ };
80
+
81
+ const onOpenInClaudeClick = () => {
82
+ if (window.analytics) {
83
+ window.analytics.track('Clicked', {
84
+ app: 'docs',
85
+ button_text: 'Open in Claude',
86
+ element: 'llm-buttons.openInClaude',
87
+ });
88
+ }
89
+
90
+ const prompt = getPrompt(window.location.href);
91
+
92
+ try {
93
+ window.open(
94
+ `https://claude.ai/new?q=${encodeURIComponent(prompt)}`,
95
+ '_blank',
96
+ );
97
+ } catch (error) {
98
+ console.error('Error opening Claude:', error);
99
+ }
100
+ };
101
+
102
+ const onOpenInPerplexityClick = () => {
103
+ if (window.analytics) {
104
+ window.analytics.track('Clicked', {
105
+ app: 'docs',
106
+ button_text: 'Open in Perplexity',
107
+ element: 'llm-buttons.openInPerplexity',
108
+ });
109
+ }
110
+
111
+ const prompt = getPrompt(window.location.href);
112
+
113
+ try {
114
+ window.open(
115
+ `https://www.perplexity.ai/search/new?q=${encodeURIComponent(
116
+ prompt,
117
+ )}`,
118
+ '_blank',
119
+ );
120
+ } catch (error) {
121
+ console.error('Error opening Perplexity:', error);
122
+ }
123
+ };
124
+
125
+ const onCopyAsMarkdownClick = async ({ setCopyingStatus }) => {
126
+ if (window.analytics) {
127
+ window.analytics.track('Clicked', {
128
+ app: 'docs',
129
+ button_text: 'Copy for LLM',
130
+ element: 'llm-buttons.copyForLLM',
131
+ });
132
+ }
133
+
134
+ const markdownUrl = getMarkdownUrl(window.location.href);
135
+
136
+ try {
137
+ setCopyingStatus('loading');
138
+
139
+ // Fetch the markdown content
140
+ const response = await fetch(markdownUrl);
141
+
142
+ if (!response.ok) {
143
+ throw new Error(`Failed to fetch markdown: ${response.status}`);
144
+ }
145
+
146
+ const markdownContent = await response.text();
147
+
148
+ // Copy to clipboard
149
+ await navigator.clipboard.writeText(markdownContent);
150
+
151
+ // Show success feedback
152
+ setCopyingStatus('copied');
153
+ } catch (error) {
154
+ console.error('Failed to copy markdown content:', error);
155
+ } finally {
156
+ setTimeout(() => setCopyingStatus('idle'), 2000);
157
+ }
158
+ };
159
+
160
+ const onViewAsMarkdownClick = () => {
161
+ if (window.analytics) {
162
+ window.analytics.track('Clicked', {
163
+ app: 'docs',
164
+ button_text: 'View as Markdown',
165
+ element: 'llm-buttons.viewAsMarkdown',
166
+ });
167
+ }
168
+
169
+ const markdownUrl = getMarkdownUrl(window.location.href);
170
+
171
+ try {
172
+ window.open(markdownUrl, '_blank');
173
+ } catch (error) {
174
+ console.error('Error opening markdown file:', error);
175
+ }
176
+ };
177
+
178
+ function getButtonText({ status }) {
179
+ switch (status) {
180
+ case 'loading':
181
+ return 'Copying...';
182
+ case 'copied':
183
+ return 'Copied';
184
+ default:
185
+ return 'Copy for LLM';
186
+ }
187
+ }
188
+
189
+ const MenuBase = ({
190
+ children,
191
+ ref,
192
+ copyingStatus,
193
+ setCopyingStatus,
194
+ chevronIconRef,
195
+ ...props
196
+ }) => (
197
+ <div ref={ref} className={styles.llmButtonWrapper}>
198
+ <div className={styles.llmButton}>
199
+ <div
200
+ className={styles.copyUpIconWrapper}
201
+ onClick={() => onCopyAsMarkdownClick({ setCopyingStatus })}
202
+ >
203
+ {copyingStatus === 'loading' && <LoaderIcon size={16} />}
204
+ {copyingStatus === 'copied' && <CheckIcon size={16} />}
205
+ {copyingStatus === 'idle' && <CopyIcon size={16} />}
206
+ </div>
207
+ <Text
208
+ size="regular"
209
+ onClick={() => onCopyAsMarkdownClick({ setCopyingStatus })}
210
+ className={styles.llmButtonText}
211
+ >
212
+ {getButtonText({ status: copyingStatus })}
213
+ </Text>
214
+ <div {...props} className={styles.chevronIconWrapper}>
215
+ <ChevronDownIcon
216
+ size="16"
217
+ color={theme.color.neutral.icon}
218
+ className={styles.chevronIcon}
219
+ ref={chevronIconRef}
220
+ />
221
+ </div>
222
+ </div>
223
+ </div>
224
+ );
225
+
226
+ const Option = ({ Icon, label, description, showExternalIcon }) => (
227
+ <div className={styles.menuOption}>
228
+ <Icon size={16} className={styles.menuOptionIcon} />
229
+ <div className={styles.menuOptionText}>
230
+ <Text size="regular">{label}</Text>
231
+ <Text size="small" color={theme.color.neutral.textSubtle}>
232
+ {description}
233
+ </Text>
13
234
  </div>
235
+ {showExternalIcon && (
236
+ <ExternalLinkIcon
237
+ size={16}
238
+ className={styles.menuOptionExternalIcon}
239
+ />
240
+ )}
241
+ </div>
242
+ );
243
+
244
+ export default function LLMButtons({ isApiReferencePage = false }) {
245
+ const [copyingStatus, setCopyingStatus] = useState('idle');
246
+ const chevronIconRef = React.useRef(null);
247
+
248
+ const onMenuOptionClick = useCallback((value) => {
249
+ switch (value) {
250
+ case 'copyForLLM':
251
+ onCopyAsMarkdownClick({ setCopyingStatus });
252
+ break;
253
+ case 'viewAsMarkdown':
254
+ onViewAsMarkdownClick();
255
+ break;
256
+ case 'openInChatGPT':
257
+ onOpenInChatGPTClick();
258
+ break;
259
+ case 'openInClaude':
260
+ onOpenInClaudeClick();
261
+ break;
262
+ case 'openInPerplexity':
263
+ onOpenInPerplexityClick();
264
+ break;
265
+ default:
266
+ break;
267
+ }
268
+ }, []);
269
+
270
+ return (
271
+ <Menu
272
+ className={clsx(styles.llmMenu, {
273
+ [styles.llmMenuApiReferencePage]: isApiReferencePage,
274
+ })}
275
+ onMenuOpen={(isOpen) => chevronIconRef.current?.classList.toggle(
276
+ styles.chevronIconOpen,
277
+ isOpen,
278
+ )
279
+ }
280
+ components={{
281
+ MenuBase: (props) => (
282
+ <MenuBase
283
+ copyingStatus={copyingStatus}
284
+ setCopyingStatus={setCopyingStatus}
285
+ chevronIconRef={chevronIconRef}
286
+ {...props}
287
+ />
288
+ ),
289
+ }}
290
+ onSelect={onMenuOptionClick}
291
+ options={DROPDOWN_OPTIONS}
292
+ renderOption={Option}
293
+ />
14
294
  );
15
295
  }
@@ -1,57 +1,115 @@
1
- .llmButtonsContainer {
1
+ :global .menu-list[data-floating-ui-focusable] {
2
+ gap: 0.4rem;
3
+ padding: 0.6rem;
4
+ }
5
+
6
+ .llmMenu {
7
+ margin-top: 1.2rem;
8
+ width: fit-content;
9
+
10
+ @media (min-width: 768px) {
11
+ margin-left: auto;
12
+ }
13
+ }
14
+
15
+ .llmButtonWrapper {
2
16
  display: flex;
3
- align-items: center;
4
- gap: 12px;
5
- margin-top: -8px;
6
- margin-bottom: calc(var(--ifm-h1-vertical-rhythm-bottom) * var(--ifm-leading));
17
+ justify-content: flex-end;
18
+
19
+ @media (min-width: 768px) {
20
+ /* hack to make the dropdown menu align to the right */
21
+ width: 23.3rem;
22
+ }
7
23
  }
8
24
 
9
- .llmButtonsSeparator {
10
- width: 1px;
11
- height: 16px;
12
- background-color: var(--ifm-hr-background-color);
25
+ .llmMenuApiReferencePage {
26
+ margin-top: -0.8rem;
27
+ margin-bottom: calc(var(--ifm-h1-vertical-rhythm-bottom) * var(--ifm-leading));
28
+ margin-left: 0 !important;
29
+ .llmButtonWrapper {
30
+ width: fit-content !important;
31
+ }
13
32
  }
14
33
 
15
34
  .llmButton {
35
+ width: fit-content;
36
+ display: inline-flex;
37
+ align-items: center;
38
+ height: 3rem;
39
+
16
40
  display: flex;
17
41
  align-items: center;
18
- background-color: transparent;
19
- border: none;
20
- height: 16px;
42
+ border-radius: 8px;
43
+ border: 1px solid var(--color-Neutral_SeparatorSubtle);
44
+ background-color: var(--color-Neutral_BackgroundMuted);
45
+
21
46
  cursor: pointer;
22
- padding: 0;
23
- gap: 4px;
47
+ transition: background-color 0.2s ease-in-out;
48
+
49
+ &:hover {
50
+ background-color: var(--color-Neutral_BackgroundMuted);
51
+ }
24
52
  }
25
53
 
26
- .llmButtonIcon {
27
- width: 16px;
28
- height: 16px;
29
- margin: 0 !important;
30
- cursor: pointer;
31
- background-size: contain;
32
- background-repeat: no-repeat;
33
- background-position: center;
34
- display: inline-block;
54
+ .copyUpIconWrapper {
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ padding-left: 0.8rem;
59
+ padding-right: 0.4rem;
60
+ height: 100%;
61
+ }
62
+
63
+ .llmButtonText {
64
+ height: 100%;
65
+ display: flex;
66
+ align-items: center;
67
+ padding-right: 0.8rem;
68
+ min-width: 9.3rem;
69
+
70
+ /* prevents font size glitch when loading the page */
71
+ margin: 0px;
72
+ font-size: 1.4rem;
73
+ font-weight: 400;
74
+ font-family: Inter, sans-serif;
35
75
  }
36
76
 
37
- .llmButtonIconBackgroundMarkdown {
38
- background-image: url('https://docs.apify.com/img/markdown.svg');
77
+ .chevronIconWrapper {
78
+ border-left: 1px solid var(--color-Neutral_SeparatorSubtle);
79
+ display: flex;
80
+ align-items: center;
81
+ justify-content: center;
82
+ padding-inline: 0.8rem;
83
+ height: 100%;
84
+ }
39
85
 
86
+ .chevronIcon {
87
+ transition: transform 0.2s ease-in-out;
40
88
  }
41
89
 
42
- .llmButtonIconBackgroundCopy {
43
- background-image: url('https://docs.apify.com/img/copy.svg');
90
+ .chevronIconOpen {
91
+ transform: rotate(180deg);
44
92
  }
45
93
 
46
- /* Dark theme adjustments */
47
- [data-theme='dark'] .llmButtonIcon {
48
- color: #e0e0e0;
94
+ .menuOption {
95
+ padding-block: 0.4rem;
96
+ display: flex;
97
+ gap: 0.4rem;
49
98
  }
50
99
 
51
- [data-theme='dark'] .llmButtonIconBackgroundMarkdown {
52
- background-image: url('https://docs.apify.com/img/markdown-dark-theme.svg');
100
+ .menuOptionIcon {
101
+ flex-shrink: 0;
102
+ margin-top: 0.4rem;
103
+ }
104
+
105
+ .menuOptionText {
106
+ flex: 1;
107
+ display: flex;
108
+ flex-direction: column;
109
+ gap: 0.2rem;
53
110
  }
54
111
 
55
- [data-theme='dark'] .llmButtonIconBackgroundCopy {
56
- background-image: url('https://docs.apify.com/img/copy-dark-theme.svg');
112
+ .menuOptionExternalIcon {
113
+ flex-shrink: 0;
114
+ margin-top: 0.2rem;
57
115
  }
@@ -1,71 +0,0 @@
1
- import React, { useState } from 'react';
2
-
3
- import styles from '../styles.module.css';
4
-
5
- // Custom component for button text
6
- function ButtonText({ isLoading, isCopied }) {
7
- if (isLoading) {
8
- return 'Copying...';
9
- }
10
- if (isCopied) {
11
- return 'Copied!';
12
- }
13
- return 'Copy for LLM';
14
- }
15
-
16
- export default function CopyForLLM() {
17
- const [isLoading, setIsLoading] = useState(false);
18
- const [isCopied, setIsCopied] = useState(false);
19
-
20
- const handleCopy = async () => {
21
- if (window.analytics) {
22
- window.analytics.track('Clicked', {
23
- app: 'docs',
24
- button_text: 'Copy for LLM',
25
- element: 'llm-buttons.copyForLLM',
26
- });
27
- }
28
-
29
- try {
30
- setIsLoading(true);
31
-
32
- const currentUrl = window.location.href;
33
- const markdownUrl = `${currentUrl}.md`;
34
-
35
- // Fetch the markdown content
36
- const response = await fetch(markdownUrl);
37
-
38
- if (!response.ok) {
39
- throw new Error(`Failed to fetch markdown: ${response.status}`);
40
- }
41
-
42
- const markdownContent = await response.text();
43
-
44
- // Copy to clipboard
45
- await navigator.clipboard.writeText(markdownContent);
46
-
47
- // Show success feedback
48
- setIsCopied(true);
49
- setTimeout(() => setIsCopied(false), 2000);
50
- } catch (error) {
51
- console.error('Failed to copy markdown content:', error);
52
- } finally {
53
- setIsLoading(false);
54
- }
55
- };
56
-
57
- return (
58
- <button
59
- className={styles.llmButton}
60
- title="Copy for LLM"
61
- onClick={handleCopy}
62
- disabled={isLoading}
63
- >
64
- <span
65
- className={`${styles.llmButtonIcon} ${styles.llmButtonIconBackgroundCopy}`}
66
- aria-label="Copy for LLM"
67
- />
68
- <ButtonText isLoading={isLoading} isCopied={isCopied} />
69
- </button>
70
- );
71
- }
@@ -1,37 +0,0 @@
1
- import React from 'react';
2
-
3
- import styles from '../styles.module.css';
4
-
5
- export default function ViewAsMarkdown() {
6
- const handleClick = () => {
7
- if (window.analytics) {
8
- window.analytics.track('Clicked', {
9
- app: 'docs',
10
- button_text: 'View as Markdown',
11
- element: 'llm-buttons.viewAsMarkdown',
12
- });
13
- }
14
-
15
- try {
16
- const currentUrl = window.location.href;
17
- const markdownUrl = `${currentUrl}.md`;
18
- window.open(markdownUrl, '_blank');
19
- } catch (error) {
20
- console.error('Error opening markdown file:', error);
21
- }
22
- };
23
-
24
- return (
25
- <button
26
- className={styles.llmButton}
27
- title="View as Markdown"
28
- onClick={handleClick}
29
- >
30
- <span
31
- className={`${styles.llmButtonIcon} ${styles.llmButtonIconBackgroundMarkdown}`}
32
- aria-label="View as Markdown"
33
- />
34
- View as Markdown
35
- </button>
36
- );
37
- }