From 3de2afba5a05d91318325cdc8100c75a69693def Mon Sep 17 00:00:00 2001 From: patrikbraborec Date: Fri, 15 Aug 2025 15:56:09 +0200 Subject: [PATCH 01/11] docs: Add copy for LLMs and view as markdown buttons --- sources/legal/index.mdx | 1 - sources/platform/actors/running/index.md | 2 - sources/platform/index.mdx | 5 +- sources/platform/proxy/index.md | 2 - sources/platform/security.md | 2 - sources/platform/storage/dataset.md | 2 - sources/platform/storage/key_value_store.md | 2 - .../LLMButtons/CopyForLLM/index.tsx | 72 +++++++++++++++++++ .../LLMButtons/ViewAsMarkdown/index.tsx | 39 ++++++++++ src/components/LLMButtons/index.tsx | 15 ++++ src/components/LLMButtons/styles.module.css | 38 ++++++++++ src/theme/DocItem/Content/index.js | 33 +++++++++ src/theme/DocItem/Layout/index.js | 2 +- static/img/copy.svg | 4 ++ static/img/markdown.svg | 5 ++ 15 files changed, 209 insertions(+), 15 deletions(-) create mode 100644 src/components/LLMButtons/CopyForLLM/index.tsx create mode 100644 src/components/LLMButtons/ViewAsMarkdown/index.tsx create mode 100644 src/components/LLMButtons/index.tsx create mode 100644 src/components/LLMButtons/styles.module.css create mode 100644 src/theme/DocItem/Content/index.js create mode 100644 static/img/copy.svg create mode 100644 static/img/markdown.svg diff --git a/sources/legal/index.mdx b/sources/legal/index.mdx index 1a8788870f..78ffdf6be3 100644 --- a/sources/legal/index.mdx +++ b/sources/legal/index.mdx @@ -7,7 +7,6 @@ slug: / hide_table_of_contents: true --- -# Apify Legal ## Company details (Impressum) diff --git a/sources/platform/actors/running/index.md b/sources/platform/actors/running/index.md index 44bae2c156..56541d822d 100644 --- a/sources/platform/actors/running/index.md +++ b/sources/platform/actors/running/index.md @@ -5,8 +5,6 @@ sidebar_position: 7.1 slug: /actors/running --- -# Running Actors - **In this section, you learn how to run Apify Actors using Apify Console or programmatically. You will learn about their configuration, versioning, data retention, usage, and pricing.** import Tabs from '@theme/Tabs'; diff --git a/sources/platform/index.mdx b/sources/platform/index.mdx index 87d6993c11..50204a168f 100644 --- a/sources/platform/index.mdx +++ b/sources/platform/index.mdx @@ -1,16 +1,15 @@ --- -title: Home +title: Apify platform description: Apify is your one-stop shop for web scraping, data extraction, and RPA. Automate anything you can do manually in a browser. slug: / hide_table_of_contents: true sidebar_position: 0 +sidebar_label: Home --- import Card from "@site/src/components/Card"; import CardGrid from "@site/src/components/CardGrid"; import homepageContent from "./homepage_content.json"; -# Apify platform - > **Apify** is a cloud platform that helps you build reliable web scrapers, fast, and automate anything you can do manually in a web browser. > > **Actors** are serverless cloud programs running on the Apify platform that can easily crawl websites with millions of pages, but also perform arbitrary computing jobs such as sending emails or data transformations. They can be started manually, using our API or scheduler, and they can be easily integrated with other apps. diff --git a/sources/platform/proxy/index.md b/sources/platform/proxy/index.md index 787659a405..7397d12728 100644 --- a/sources/platform/proxy/index.md +++ b/sources/platform/proxy/index.md @@ -11,8 +11,6 @@ import TabItem from '@theme/TabItem'; import Card from "@site/src/components/Card"; import CardGrid from "@site/src/components/CardGrid"; -# Proxy - **Learn to anonymously access websites in scraping/automation jobs. Improve data outputs and efficiency of bots, and access websites from various geographies.** --- diff --git a/sources/platform/security.md b/sources/platform/security.md index 8cc96b013f..ec13e53011 100644 --- a/sources/platform/security.md +++ b/sources/platform/security.md @@ -6,8 +6,6 @@ category: platform slug: /security --- -# Security - **Learn more about Apify's security practices and data protection measures that are used to protect your Actors, their data, and the Apify platform in general.** --- diff --git a/sources/platform/storage/dataset.md b/sources/platform/storage/dataset.md index 01831adaeb..4178fdfec7 100644 --- a/sources/platform/storage/dataset.md +++ b/sources/platform/storage/dataset.md @@ -6,8 +6,6 @@ toc_max_heading_level: 4 slug: /storage/dataset --- -# Dataset - **Store and export web scraping, crawling or data processing job results. Learn how to access and manage datasets in Apify Console or via API.** import Tabs from '@theme/Tabs'; diff --git a/sources/platform/storage/key_value_store.md b/sources/platform/storage/key_value_store.md index 95941d9a79..ffe40d484c 100644 --- a/sources/platform/storage/key_value_store.md +++ b/sources/platform/storage/key_value_store.md @@ -6,8 +6,6 @@ sidebar_position: 9.3 slug: /storage/key-value-store --- -# Key-value store - **Store anything from Actor or task run results, JSON documents, or images. Learn how to access and manage key-value stores from Apify Console or via API.** import Tabs from '@theme/Tabs'; diff --git a/src/components/LLMButtons/CopyForLLM/index.tsx b/src/components/LLMButtons/CopyForLLM/index.tsx new file mode 100644 index 0000000000..9eb8521ea1 --- /dev/null +++ b/src/components/LLMButtons/CopyForLLM/index.tsx @@ -0,0 +1,72 @@ +import useBaseUrl from '@docusaurus/useBaseUrl'; +import React, { useState } from 'react'; + +import styles from '../styles.module.css'; + +// Custom component for button text +function ButtonText({ isLoading, isCopied }: { isLoading: boolean; isCopied: boolean }) { + if (isLoading) { + return <>Copying...; + } + if (isCopied) { + return <>Copied!; + } + return <>Copy for LLM; +} + +export default function CopyForLLM() { + const copyIcon = useBaseUrl('/img/copy.svg'); + const [isLoading, setIsLoading] = useState(false); + const [isCopied, setIsCopied] = useState(false); + + const handleCopy = async () => { + try { + setIsLoading(true); + + const currentUrl = window.location.href; + const markdownUrl = `${currentUrl}.md`; + + // Fetch the markdown content + const response = await fetch(markdownUrl); + + if (!response.ok) { + throw new Error(`Failed to fetch markdown: ${response.status}`); + } + + const markdownContent = await response.text(); + + // Copy to clipboard + await navigator.clipboard.writeText(markdownContent); + + // Show success feedback + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + } catch (error) { + console.error('Failed to copy markdown content:', error); + } finally { + setIsLoading(false); + } + }; + + return ( + + ); +} diff --git a/src/components/LLMButtons/ViewAsMarkdown/index.tsx b/src/components/LLMButtons/ViewAsMarkdown/index.tsx new file mode 100644 index 0000000000..047f57a214 --- /dev/null +++ b/src/components/LLMButtons/ViewAsMarkdown/index.tsx @@ -0,0 +1,39 @@ +import useBaseUrl from '@docusaurus/useBaseUrl'; +import React from 'react'; + +import styles from '../styles.module.css'; + +export default function ViewAsMarkdown() { + const markdownIcon = useBaseUrl('/img/markdown.svg'); + + const handleClick = () => { + try { + const currentUrl = window.location.href; + const markdownUrl = `${currentUrl}.md`; + window.open(markdownUrl, '_blank'); + } catch (error) { + console.error('Error opening markdown file:', error); + } + }; + + return ( + + ); +} diff --git a/src/components/LLMButtons/index.tsx b/src/components/LLMButtons/index.tsx new file mode 100644 index 0000000000..fd42724448 --- /dev/null +++ b/src/components/LLMButtons/index.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import CopyForLLM from './CopyForLLM'; +import styles from './styles.module.css'; +import ViewAsMarkdown from './ViewAsMarkdown'; + +export default function LLMButtons() { + return ( +
+ +
+ +
+ ); +} diff --git a/src/components/LLMButtons/styles.module.css b/src/components/LLMButtons/styles.module.css new file mode 100644 index 0000000000..2e5644a9ea --- /dev/null +++ b/src/components/LLMButtons/styles.module.css @@ -0,0 +1,38 @@ +.llmButtonsContainer { + display: flex; + align-items: center; + gap: 12px; + margin-top: -8px; + margin-bottom: calc(var(--ifm-h1-vertical-rhythm-bottom) * var(--ifm-leading));; +} + +.llmButtonsSeparator { + width: 1px; + height: 16px; + background-color: #e0e0e0; +} + +.llmButton { + display: flex; + align-items: center; + background-color: transparent; + border: none; + height: 16px; + cursor: pointer; + padding: 0; + gap: 4px; +} + +.llmButtonIcon { + width: 16px; + height: 16px; + margin: 0 !important; + cursor: pointer; +} + +/* Dark theme adjustments */ +[data-theme='dark'] .llmButton { + background: #2a2a2a; + border-color: #404040; + color: #e0e0e0; +} diff --git a/src/theme/DocItem/Content/index.js b/src/theme/DocItem/Content/index.js new file mode 100644 index 0000000000..18b3c65551 --- /dev/null +++ b/src/theme/DocItem/Content/index.js @@ -0,0 +1,33 @@ + import { useDoc } from '@docusaurus/plugin-content-docs/client'; +import { ThemeClassNames } from '@docusaurus/theme-common'; +import Heading from '@theme/Heading'; +import MDXContent from '@theme/MDXContent'; +import clsx from 'clsx'; +import React from 'react'; + +import LLMButtons from '../../../components/LLMButtons'; + +function useSyntheticTitle() { + const { metadata, frontMatter, contentTitle } = useDoc(); + const shouldRender = !frontMatter.hide_title && typeof contentTitle === 'undefined'; + + if (!shouldRender) { + return null; + } + + return metadata.title; +} + +export default function DocItemContent({ children }) { + const syntheticTitle = useSyntheticTitle(); + + return ( +
+
+ {syntheticTitle} + + {children} +
+
+ ); +} diff --git a/src/theme/DocItem/Layout/index.js b/src/theme/DocItem/Layout/index.js index 1c69bfc949..25e007163c 100644 --- a/src/theme/DocItem/Layout/index.js +++ b/src/theme/DocItem/Layout/index.js @@ -5,7 +5,6 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import Giscus from '@giscus/react'; import ContentVisibility from '@theme/ContentVisibility'; import DocBreadcrumbs from '@theme/DocBreadcrumbs'; -import DocItemContent from '@theme/DocItem/Content'; import DocItemFooter from '@theme/DocItem/Footer'; import DocItemPaginator from '@theme/DocItem/Paginator'; import DocItemTOCDesktop from '@theme/DocItem/TOC/Desktop'; @@ -15,6 +14,7 @@ import DocVersionBanner from '@theme/DocVersionBanner'; import clsx from 'clsx'; import React, { useCallback } from 'react'; +import DocItemContent from '../Content'; import styles from './styles.module.css'; /** diff --git a/static/img/copy.svg b/static/img/copy.svg new file mode 100644 index 0000000000..1da744647f --- /dev/null +++ b/static/img/copy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/static/img/markdown.svg b/static/img/markdown.svg new file mode 100644 index 0000000000..b5599a0f78 --- /dev/null +++ b/static/img/markdown.svg @@ -0,0 +1,5 @@ + + + + + From 34ddf14a625014e538c7fe811ea9c65b7dcebf49 Mon Sep 17 00:00:00 2001 From: patrikbraborec Date: Mon, 18 Aug 2025 15:57:25 +0200 Subject: [PATCH 02/11] Add darkmode support, add analytics --- .../LLMButtons/CopyForLLM/index.tsx | 19 ++++++------ .../LLMButtons/ViewAsMarkdown/index.tsx | 20 ++++++------- src/components/LLMButtons/index.tsx | 4 +-- src/components/LLMButtons/styles.module.css | 29 +++++++++++++++---- src/theme/DocItem/Content/index.js | 7 +++-- static/img/copy-dark-theme.svg | 4 +++ static/img/markdown-dark-theme.svg | 5 ++++ 7 files changed, 58 insertions(+), 30 deletions(-) create mode 100644 static/img/copy-dark-theme.svg create mode 100644 static/img/markdown-dark-theme.svg diff --git a/src/components/LLMButtons/CopyForLLM/index.tsx b/src/components/LLMButtons/CopyForLLM/index.tsx index 9eb8521ea1..94dbc578c2 100644 --- a/src/components/LLMButtons/CopyForLLM/index.tsx +++ b/src/components/LLMButtons/CopyForLLM/index.tsx @@ -1,4 +1,3 @@ -import useBaseUrl from '@docusaurus/useBaseUrl'; import React, { useState } from 'react'; import styles from '../styles.module.css'; @@ -15,11 +14,18 @@ function ButtonText({ isLoading, isCopied }: { isLoading: boolean; isCopied: boo } export default function CopyForLLM() { - const copyIcon = useBaseUrl('/img/copy.svg'); const [isLoading, setIsLoading] = useState(false); const [isCopied, setIsCopied] = useState(false); const handleCopy = async () => { + if ((window as any).analytics) { + (window as any).analytics.track('Clicked', { + app: 'docs', + button_text: 'Copy for LLM', + element: 'llm-buttons.copyForLLM', + }); + } + try { setIsLoading(true); @@ -56,14 +62,7 @@ export default function CopyForLLM() { disabled={isLoading} > diff --git a/src/components/LLMButtons/ViewAsMarkdown/index.tsx b/src/components/LLMButtons/ViewAsMarkdown/index.tsx index 047f57a214..e8c64421b9 100644 --- a/src/components/LLMButtons/ViewAsMarkdown/index.tsx +++ b/src/components/LLMButtons/ViewAsMarkdown/index.tsx @@ -1,12 +1,17 @@ -import useBaseUrl from '@docusaurus/useBaseUrl'; import React from 'react'; import styles from '../styles.module.css'; export default function ViewAsMarkdown() { - const markdownIcon = useBaseUrl('/img/markdown.svg'); - const handleClick = () => { + if ((window as any).analytics) { + (window as any).analytics.track('Clicked', { + app: 'docs', + button_text: 'View as Markdown', + element: 'llm-buttons.viewAsMarkdown', + }); + } + try { const currentUrl = window.location.href; const markdownUrl = `${currentUrl}.md`; @@ -23,14 +28,7 @@ export default function ViewAsMarkdown() { onClick={handleClick} > View as Markdown diff --git a/src/components/LLMButtons/index.tsx b/src/components/LLMButtons/index.tsx index fd42724448..6ab85fa376 100644 --- a/src/components/LLMButtons/index.tsx +++ b/src/components/LLMButtons/index.tsx @@ -7,9 +7,9 @@ import ViewAsMarkdown from './ViewAsMarkdown'; export default function LLMButtons() { return (
- -
+
+
); } diff --git a/src/components/LLMButtons/styles.module.css b/src/components/LLMButtons/styles.module.css index 2e5644a9ea..d517b5c9c9 100644 --- a/src/components/LLMButtons/styles.module.css +++ b/src/components/LLMButtons/styles.module.css @@ -3,13 +3,13 @@ align-items: center; gap: 12px; margin-top: -8px; - margin-bottom: calc(var(--ifm-h1-vertical-rhythm-bottom) * var(--ifm-leading));; + margin-bottom: calc(var(--ifm-h1-vertical-rhythm-bottom) * var(--ifm-leading)); } .llmButtonsSeparator { width: 1px; height: 16px; - background-color: #e0e0e0; + background-color: var(--ifm-hr-background-color); } .llmButton { @@ -28,11 +28,30 @@ height: 16px; margin: 0 !important; cursor: pointer; + background-size: contain; + background-repeat: no-repeat; + background-position: center; + display: inline-block; +} + +.llmButtonIconBackgroundMarkdown { + background-image: url('/img/markdown.svg'); + +} + +.llmButtonIconBackgroundCopy { + background-image: url('/img/copy.svg'); } /* Dark theme adjustments */ -[data-theme='dark'] .llmButton { - background: #2a2a2a; - border-color: #404040; +[data-theme='dark'] .llmButtonIcon { color: #e0e0e0; } + +[data-theme='dark'] .llmButtonIconBackgroundMarkdown { + background-image: url('/img/markdown-dark-theme.svg'); +} + +[data-theme='dark'] .llmButtonIconBackgroundCopy { + background-image: url('/img/copy-dark-theme.svg'); +} \ No newline at end of file diff --git a/src/theme/DocItem/Content/index.js b/src/theme/DocItem/Content/index.js index 18b3c65551..6b37f872f3 100644 --- a/src/theme/DocItem/Content/index.js +++ b/src/theme/DocItem/Content/index.js @@ -1,5 +1,6 @@ - import { useDoc } from '@docusaurus/plugin-content-docs/client'; +import { useDoc } from '@docusaurus/plugin-content-docs/client'; import { ThemeClassNames } from '@docusaurus/theme-common'; +import { useLocation } from '@docusaurus/router'; import Heading from '@theme/Heading'; import MDXContent from '@theme/MDXContent'; import clsx from 'clsx'; @@ -20,12 +21,14 @@ function useSyntheticTitle() { export default function DocItemContent({ children }) { const syntheticTitle = useSyntheticTitle(); + const location = useLocation(); + const shouldShowLLMButtons = !location.pathname.startsWith('/legal'); return (
{syntheticTitle} - + {shouldShowLLMButtons && } {children}
diff --git a/static/img/copy-dark-theme.svg b/static/img/copy-dark-theme.svg new file mode 100644 index 0000000000..1b1e48450c --- /dev/null +++ b/static/img/copy-dark-theme.svg @@ -0,0 +1,4 @@ + + + + diff --git a/static/img/markdown-dark-theme.svg b/static/img/markdown-dark-theme.svg new file mode 100644 index 0000000000..90f88d48a6 --- /dev/null +++ b/static/img/markdown-dark-theme.svg @@ -0,0 +1,5 @@ + + + + + From 1c6d56d9bc14b7f8b9a53e355658b6f187197a45 Mon Sep 17 00:00:00 2001 From: patrikbraborec Date: Mon, 18 Aug 2025 16:00:35 +0200 Subject: [PATCH 03/11] Fix lint errors --- src/components/LLMButtons/ViewAsMarkdown/index.tsx | 2 +- src/theme/DocItem/Content/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/LLMButtons/ViewAsMarkdown/index.tsx b/src/components/LLMButtons/ViewAsMarkdown/index.tsx index e8c64421b9..0f6e751fda 100644 --- a/src/components/LLMButtons/ViewAsMarkdown/index.tsx +++ b/src/components/LLMButtons/ViewAsMarkdown/index.tsx @@ -11,7 +11,7 @@ export default function ViewAsMarkdown() { element: 'llm-buttons.viewAsMarkdown', }); } - + try { const currentUrl = window.location.href; const markdownUrl = `${currentUrl}.md`; diff --git a/src/theme/DocItem/Content/index.js b/src/theme/DocItem/Content/index.js index 6b37f872f3..48c4af8d3d 100644 --- a/src/theme/DocItem/Content/index.js +++ b/src/theme/DocItem/Content/index.js @@ -1,6 +1,6 @@ import { useDoc } from '@docusaurus/plugin-content-docs/client'; -import { ThemeClassNames } from '@docusaurus/theme-common'; import { useLocation } from '@docusaurus/router'; +import { ThemeClassNames } from '@docusaurus/theme-common'; import Heading from '@theme/Heading'; import MDXContent from '@theme/MDXContent'; import clsx from 'clsx'; From dc28c790778a673a55990bc4a2fd6b1f37e91a19 Mon Sep 17 00:00:00 2001 From: patrikbraborec Date: Tue, 19 Aug 2025 16:37:07 +0200 Subject: [PATCH 04/11] Fix issues with API docs --- docusaurus.config.js | 34 ++++++++++++++++++++++++++- src/theme/DocItem/Content/index.js | 37 ++++++++++++++++++++++++++---- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/docusaurus.config.js b/docusaurus.config.js index b5d81a2b4b..840b1c6b2c 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -1,7 +1,7 @@ const { join, resolve } = require('node:path'); const clsx = require('clsx'); -const { createApiPageMD } = require('docusaurus-plugin-openapi-docs/lib/markdown'); +const { createApiPageMD, createInfoPageMD } = require('docusaurus-plugin-openapi-docs/lib/markdown'); const { config } = require('./apify-docs-theme'); const { collectSlugs } = require('./tools/utils/collectSlugs'); @@ -196,6 +196,38 @@ module.exports = { md = md.replace('-->', '-->'); } + // Add LLMButtons import and component + if (!md.includes('import LLMButtons')) { + // Find the first import statement and add LLMButtons import after it + const firstImportMatch = md.match(/^import\s+.*?from\s+["'][^"']*["'];?\s*$/m); + if (firstImportMatch) { + const importEnd = md.indexOf(firstImportMatch[0]) + firstImportMatch[0].length; + const llmButtonsImport = '\nimport LLMButtons from "@site/src/components/LLMButtons";'; + md = md.slice(0, importEnd) + llmButtonsImport + md.slice(importEnd); + } + } + + // Find the first Heading h1 and add LLMButtons after it + // eslint-disable-next-line max-len + const headingRegex = /(]*as=\{"h1"\}[^>]*className=\{"openapi__heading"\}[^>]*children=\{[^}]*\}[^>]*>\s*<\/Heading>)/; + md = md.replace(headingRegex, '$1\n\n\n'); + + return md; + }, + createInfoPageMD: (pageData) => { + let md = createInfoPageMD(pageData); + + // Add LLMButtons import and component + if (!md.includes('import LLMButtons')) { + // eslint-disable-next-line max-len + md = md.replace('import Heading from "@theme/Heading";', 'import Heading from "@theme/Heading";\nimport LLMButtons from "@site/src/components/LLMButtons";'); + } + + // Find the first Heading h1 and add LLMButtons after it + // eslint-disable-next-line max-len + const headingRegex = /(]*as=\{"h1"\}[^>]*className=\{"openapi__heading"\}[^>]*children=\{[^}]*\}[^>]*>\s*<\/Heading>)/; + md = md.replace(headingRegex, '$1\n\n\n'); + return md; }, }, diff --git a/src/theme/DocItem/Content/index.js b/src/theme/DocItem/Content/index.js index 48c4af8d3d..43c3951559 100644 --- a/src/theme/DocItem/Content/index.js +++ b/src/theme/DocItem/Content/index.js @@ -22,15 +22,42 @@ function useSyntheticTitle() { export default function DocItemContent({ children }) { const syntheticTitle = useSyntheticTitle(); const location = useLocation(); - const shouldShowLLMButtons = !location.pathname.startsWith('/legal'); + + // Define the allowed API v2 paths that should show LLMButtons (tag/info pages) + // The logic is handled here, and also in docusaurus.config.js (see docusaurus-plugin-openapi-docs) + const allowedApiV2Paths = [ + '/api/v2/getting-started', + '/api/v2/actors', + '/api/v2/actors-actor-versions', + '/api/v2/actors-actor-builds', + '/api/v2/actors-actor-runs', + '/api/v2/actors-webhook-collection', + '/api/v2/actor-builds', + '/api/v2/actor-runs', + '/api/v2/actor-tasks', + '/api/v2/storage-datasets', + '/api/v2/storage-key-value-stores', + '/api/v2/storage-request-queues', + '/api/v2/storage-request-queues-requests', + '/api/v2/storage-request-queues-requests-locks', + '/api/v2/webhooks-webhooks', + '/api/v2/webhooks-webhook-dispatches', + '/api/v2/schedules', + '/api/v2/store', + '/api/v2/logs', + '/api/v2/users', + '/platform', + ]; + + const shouldShowLLMButtons = allowedApiV2Paths.some((path) => location.pathname.startsWith(path)); return (
- {syntheticTitle} - {shouldShowLLMButtons && } - {children} -
+ {syntheticTitle} + {shouldShowLLMButtons && } + {children} +
); } From df020c753ef8ce644fef2da455fa881b391365ab Mon Sep 17 00:00:00 2001 From: Patrik Braborec Date: Thu, 21 Aug 2025 10:03:42 +0200 Subject: [PATCH 05/11] Update src/theme/DocItem/Content/index.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: František Nesveda --- src/theme/DocItem/Content/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/theme/DocItem/Content/index.js b/src/theme/DocItem/Content/index.js index 43c3951559..94117c4b29 100644 --- a/src/theme/DocItem/Content/index.js +++ b/src/theme/DocItem/Content/index.js @@ -23,9 +23,9 @@ export default function DocItemContent({ children }) { const syntheticTitle = useSyntheticTitle(); const location = useLocation(); - // Define the allowed API v2 paths that should show LLMButtons (tag/info pages) + // Define the allowed paths that should show LLMButtons (tag/info pages) // The logic is handled here, and also in docusaurus.config.js (see docusaurus-plugin-openapi-docs) - const allowedApiV2Paths = [ + const allowedPaths = [ '/api/v2/getting-started', '/api/v2/actors', '/api/v2/actors-actor-versions', @@ -49,7 +49,7 @@ export default function DocItemContent({ children }) { '/platform', ]; - const shouldShowLLMButtons = allowedApiV2Paths.some((path) => location.pathname.startsWith(path)); + const shouldShowLLMButtons = allowedPaths.some((path) => location.pathname.startsWith(path)); return (
From 466a3bf41edf40578c2a8b942052d65c5214894c Mon Sep 17 00:00:00 2001 From: Patrik Braborec Date: Thu, 21 Aug 2025 10:04:20 +0200 Subject: [PATCH 06/11] Update src/theme/DocItem/Content/index.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: František Nesveda --- src/theme/DocItem/Content/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/theme/DocItem/Content/index.js b/src/theme/DocItem/Content/index.js index 94117c4b29..0259db45a8 100644 --- a/src/theme/DocItem/Content/index.js +++ b/src/theme/DocItem/Content/index.js @@ -54,7 +54,7 @@ export default function DocItemContent({ children }) { return (
- {syntheticTitle} + {syntheticTitle && {syntheticTitle}} {shouldShowLLMButtons && } {children}
From 84fcb58fcccfcd841d514b89c4ffc6cdd1f2cbbc Mon Sep 17 00:00:00 2001 From: patrikbraborec Date: Thu, 21 Aug 2025 10:09:40 +0200 Subject: [PATCH 07/11] Add llm buttons only if we render html --- src/theme/DocItem/Content/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/theme/DocItem/Content/index.js b/src/theme/DocItem/Content/index.js index 0259db45a8..ee2cca84cf 100644 --- a/src/theme/DocItem/Content/index.js +++ b/src/theme/DocItem/Content/index.js @@ -49,7 +49,7 @@ export default function DocItemContent({ children }) { '/platform', ]; - const shouldShowLLMButtons = allowedPaths.some((path) => location.pathname.startsWith(path)); + const shouldShowLLMButtons = allowedPaths.some((path) => location.pathname.startsWith(path) && !location.pathname.endsWith('.md')); return (
From f162354f5cf37b554e367d7fc83d55c276118089 Mon Sep 17 00:00:00 2001 From: patrikbraborec Date: Thu, 21 Aug 2025 10:18:40 +0200 Subject: [PATCH 08/11] Fix lint errors Fix issues with content template --- .../src/theme/MDXComponents/index.js | 2 ++ docusaurus.config.js | 17 ----------------- package-lock.json | 2 +- src/theme/DocItem/Content/index.js | 10 ++++------ 4 files changed, 7 insertions(+), 24 deletions(-) diff --git a/apify-docs-theme/src/theme/MDXComponents/index.js b/apify-docs-theme/src/theme/MDXComponents/index.js index c70b80412e..dd24cd8770 100644 --- a/apify-docs-theme/src/theme/MDXComponents/index.js +++ b/apify-docs-theme/src/theme/MDXComponents/index.js @@ -1,3 +1,4 @@ +import LLMButtons from '@site/src/components/LLMButtons'; import Admonition from '@theme/Admonition'; import MDXA from '@theme/MDXComponents/A'; import MDXCode from '@theme/MDXComponents/Code'; @@ -29,5 +30,6 @@ const MDXComponents = { admonition: Admonition, mermaid: Mermaid, RunnableCodeBlock, + LLMButtons, }; export default MDXComponents; diff --git a/docusaurus.config.js b/docusaurus.config.js index 840b1c6b2c..bfb01db1de 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -196,17 +196,6 @@ module.exports = { md = md.replace('-->', '-->'); } - // Add LLMButtons import and component - if (!md.includes('import LLMButtons')) { - // Find the first import statement and add LLMButtons import after it - const firstImportMatch = md.match(/^import\s+.*?from\s+["'][^"']*["'];?\s*$/m); - if (firstImportMatch) { - const importEnd = md.indexOf(firstImportMatch[0]) + firstImportMatch[0].length; - const llmButtonsImport = '\nimport LLMButtons from "@site/src/components/LLMButtons";'; - md = md.slice(0, importEnd) + llmButtonsImport + md.slice(importEnd); - } - } - // Find the first Heading h1 and add LLMButtons after it // eslint-disable-next-line max-len const headingRegex = /(]*as=\{"h1"\}[^>]*className=\{"openapi__heading"\}[^>]*children=\{[^}]*\}[^>]*>\s*<\/Heading>)/; @@ -217,12 +206,6 @@ module.exports = { createInfoPageMD: (pageData) => { let md = createInfoPageMD(pageData); - // Add LLMButtons import and component - if (!md.includes('import LLMButtons')) { - // eslint-disable-next-line max-len - md = md.replace('import Heading from "@theme/Heading";', 'import Heading from "@theme/Heading";\nimport LLMButtons from "@site/src/components/LLMButtons";'); - } - // Find the first Heading h1 and add LLMButtons after it // eslint-disable-next-line max-len const headingRegex = /(]*as=\{"h1"\}[^>]*className=\{"openapi__heading"\}[^>]*children=\{[^}]*\}[^>]*>\s*<\/Heading>)/; diff --git a/package-lock.json b/package-lock.json index 24313c3fbc..f5ef6ded04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -74,7 +74,7 @@ }, "apify-docs-theme": { "name": "@apify/docs-theme", - "version": "1.0.197", + "version": "1.0.198", "license": "ISC", "dependencies": { "@apify/docs-search-modal": "^1.2.2", diff --git a/src/theme/DocItem/Content/index.js b/src/theme/DocItem/Content/index.js index ee2cca84cf..14855855b6 100644 --- a/src/theme/DocItem/Content/index.js +++ b/src/theme/DocItem/Content/index.js @@ -49,15 +49,13 @@ export default function DocItemContent({ children }) { '/platform', ]; - const shouldShowLLMButtons = allowedPaths.some((path) => location.pathname.startsWith(path) && !location.pathname.endsWith('.md')); + const shouldShowLLMButtons = allowedPaths.some((path) => location.pathname.startsWith(path)); return (
-
- {syntheticTitle && {syntheticTitle}} - {shouldShowLLMButtons && } - {children} -
+ {syntheticTitle && {syntheticTitle}} + {shouldShowLLMButtons && } + {children}
); } From 0e597aafc850572426b01ccbdeaaf4b2dc53f6be Mon Sep 17 00:00:00 2001 From: patrikbraborec Date: Thu, 21 Aug 2025 14:06:54 +0200 Subject: [PATCH 09/11] Remove LLM buttons in md --- docusaurus.config.js | 3 +++ tools/utils/removeLlmButtons.js | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 tools/utils/removeLlmButtons.js diff --git a/docusaurus.config.js b/docusaurus.config.js index bfb01db1de..1cf94cc3a5 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -6,6 +6,7 @@ const { createApiPageMD, createInfoPageMD } = require('docusaurus-plugin-openapi const { config } = require('./apify-docs-theme'); const { collectSlugs } = require('./tools/utils/collectSlugs'); const { externalLinkProcessor } = require('./tools/utils/externalLink'); +const { removeLlmButtons } = require('./tools/utils/removeLlmButtons'); /** @type {Partial} */ module.exports = { @@ -308,6 +309,8 @@ module.exports = { categoryName: 'Platform documentation', }, ], + // Add custom remark processing to remove LLM button text + remarkPlugins: [removeLlmButtons], }, }, ], diff --git a/tools/utils/removeLlmButtons.js b/tools/utils/removeLlmButtons.js new file mode 100644 index 0000000000..e06f352080 --- /dev/null +++ b/tools/utils/removeLlmButtons.js @@ -0,0 +1,27 @@ +const { visit } = require('unist-util-visit'); + +/** + * Remark plugin to remove LLM button text and related elements from markdown content. + * This is used by the @signalwire/docusaurus-plugin-llms-txt plugin to clean up + * the generated markdown files. + */ +function removeLlmButtons() { + return (tree) => { + // Remove text nodes that contain LLM button text + visit(tree, 'text', (node, index, parent) => { + if (node.value && ( + node.value.includes('View as Markdown') || + node.value.includes('Copy for LLM') || + node.value.includes('View as MarkdownCopy for LLM') + )) { + // Remove the text node + parent.children.splice(index, 1); + return index; // Adjust index after removal + } + }); + }; +} + +module.exports = { + removeLlmButtons, +}; From d1895a40d05816bf8b79c8e03c4684851f5ada84 Mon Sep 17 00:00:00 2001 From: patrikbraborec Date: Thu, 21 Aug 2025 14:19:44 +0200 Subject: [PATCH 10/11] Remove extra whitespaces --- package.json | 2 +- scripts/cleanupMarkdownFiles.mjs | 90 ++++++++++++++++++++++++++++++++ tools/utils/removeLlmButtons.js | 21 +++++++- 3 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 scripts/cleanupMarkdownFiles.mjs diff --git a/package.json b/package.json index a4aa7ff682..20cd340355 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "lint:code": "eslint .", "lint:code:fix": "eslint . --fix", "postinstall": "patch-package", - "postbuild": "node ./scripts/joinLlmsFiles.mjs" + "postbuild": "node ./scripts/joinLlmsFiles.mjs && node ./scripts/cleanupMarkdownFiles.mjs" }, "devDependencies": { "@apify/eslint-config": "^1.0.0", diff --git a/scripts/cleanupMarkdownFiles.mjs b/scripts/cleanupMarkdownFiles.mjs new file mode 100644 index 0000000000..0fcd9d38ba --- /dev/null +++ b/scripts/cleanupMarkdownFiles.mjs @@ -0,0 +1,90 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const BUILD_DIR = path.resolve(__dirname, '../build'); + +/** + * String-based cleanup function for removing LLM button text from markdown content. + */ +function removeLlmButtonsFromString(markdownContent) { + if (!markdownContent) return markdownContent; + + let cleaned = markdownContent; + + // Remove LLM button text patterns + cleaned = cleaned.replace(/View as MarkdownCopy for LLM/g, ''); + cleaned = cleaned.replace(/View as Markdown/g, ''); + cleaned = cleaned.replace(/Copy for LLM/g, ''); + + // Remove lines that only contain LLM button text + cleaned = cleaned.replace(/^[^\n]*View as Markdown[^\n]*$/gm, ''); + cleaned = cleaned.replace(/^[^\n]*Copy for LLM[^\n]*$/gm, ''); + + // Clean up excessive whitespace and empty lines + cleaned = cleaned.replace(/\n{3,}/g, '\n\n'); // Replace 3+ newlines with 2 + cleaned = cleaned.replace(/^\s+$/gm, ''); // Remove lines that are only whitespace + cleaned = cleaned.replace(/\n\s*\n\s*\n/g, '\n\n'); // Clean up multiple empty lines + + return cleaned.trim(); +} + +/** + * Recursively find and process all .md files in the build directory. + */ +async function processMarkdownFiles(dir) { + try { + const entries = await fs.readdir(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + // Recursively process subdirectories + await processMarkdownFiles(fullPath); + } else if (entry.name.endsWith('.md')) { + // Process markdown files + console.log(`Processing: ${fullPath}`); + + try { + const content = await fs.readFile(fullPath, 'utf8'); + const cleanedContent = removeLlmButtonsFromString(content); + + if (content !== cleanedContent) { + await fs.writeFile(fullPath, cleanedContent, 'utf8'); + console.log(` ✓ Cleaned: ${fullPath}`); + } + } catch (error) { + console.error(` ✗ Error processing ${fullPath}:`, error.message); + } + } + } + } catch (error) { + console.error(`Error reading directory ${dir}:`, error.message); + } +} + +/** + * Main function to clean up markdown files. + */ +async function cleanupMarkdownFiles() { + console.log('Starting markdown cleanup...'); + + if (!await fs.stat(BUILD_DIR).catch(() => false)) { + console.error(`Build directory not found: ${BUILD_DIR}`); + process.exit(1); + } + + await processMarkdownFiles(BUILD_DIR); + console.log('Markdown cleanup completed!'); +} + +// Run the cleanup if this script is executed directly +if (import.meta.url === `file://${process.argv[1]}`) { + cleanupMarkdownFiles().catch(console.error); +} + +export { cleanupMarkdownFiles, removeLlmButtonsFromString }; diff --git a/tools/utils/removeLlmButtons.js b/tools/utils/removeLlmButtons.js index e06f352080..0c4647bebd 100644 --- a/tools/utils/removeLlmButtons.js +++ b/tools/utils/removeLlmButtons.js @@ -12,13 +12,32 @@ function removeLlmButtons() { if (node.value && ( node.value.includes('View as Markdown') || node.value.includes('Copy for LLM') || - node.value.includes('View as MarkdownCopy for LLM') + node.value.includes('View as MarkdownCopy for LLM') || + node.value.trim() === 'View as Markdown' || + node.value.trim() === 'Copy for LLM' )) { // Remove the text node parent.children.splice(index, 1); return index; // Adjust index after removal } }); + + // Clean up empty paragraphs that resulted from text removal + visit(tree, 'paragraph', (node, index, parent) => { + // Check if paragraph is empty or only contains whitespace + const hasContent = node.children && node.children.some(child => { + if (child.type === 'text') { + return child.value && child.value.trim().length > 0; + } + return true; // Keep non-text nodes + }); + + if (!hasContent) { + // Remove empty paragraph + parent.children.splice(index, 1); + return index; + } + }); }; } From 1946455cde565fb23b03b03defc49953f4d9395f Mon Sep 17 00:00:00 2001 From: patrikbraborec Date: Thu, 21 Aug 2025 14:22:41 +0200 Subject: [PATCH 11/11] Fix lint errors --- package.json | 2 +- scripts/cleanupMarkdownFiles.mjs | 90 -------------------------------- tools/utils/removeLlmButtons.js | 14 ++--- 3 files changed, 9 insertions(+), 97 deletions(-) delete mode 100644 scripts/cleanupMarkdownFiles.mjs diff --git a/package.json b/package.json index 20cd340355..a4aa7ff682 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "lint:code": "eslint .", "lint:code:fix": "eslint . --fix", "postinstall": "patch-package", - "postbuild": "node ./scripts/joinLlmsFiles.mjs && node ./scripts/cleanupMarkdownFiles.mjs" + "postbuild": "node ./scripts/joinLlmsFiles.mjs" }, "devDependencies": { "@apify/eslint-config": "^1.0.0", diff --git a/scripts/cleanupMarkdownFiles.mjs b/scripts/cleanupMarkdownFiles.mjs deleted file mode 100644 index 0fcd9d38ba..0000000000 --- a/scripts/cleanupMarkdownFiles.mjs +++ /dev/null @@ -1,90 +0,0 @@ -import fs from 'node:fs/promises'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const BUILD_DIR = path.resolve(__dirname, '../build'); - -/** - * String-based cleanup function for removing LLM button text from markdown content. - */ -function removeLlmButtonsFromString(markdownContent) { - if (!markdownContent) return markdownContent; - - let cleaned = markdownContent; - - // Remove LLM button text patterns - cleaned = cleaned.replace(/View as MarkdownCopy for LLM/g, ''); - cleaned = cleaned.replace(/View as Markdown/g, ''); - cleaned = cleaned.replace(/Copy for LLM/g, ''); - - // Remove lines that only contain LLM button text - cleaned = cleaned.replace(/^[^\n]*View as Markdown[^\n]*$/gm, ''); - cleaned = cleaned.replace(/^[^\n]*Copy for LLM[^\n]*$/gm, ''); - - // Clean up excessive whitespace and empty lines - cleaned = cleaned.replace(/\n{3,}/g, '\n\n'); // Replace 3+ newlines with 2 - cleaned = cleaned.replace(/^\s+$/gm, ''); // Remove lines that are only whitespace - cleaned = cleaned.replace(/\n\s*\n\s*\n/g, '\n\n'); // Clean up multiple empty lines - - return cleaned.trim(); -} - -/** - * Recursively find and process all .md files in the build directory. - */ -async function processMarkdownFiles(dir) { - try { - const entries = await fs.readdir(dir, { withFileTypes: true }); - - for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - - if (entry.isDirectory()) { - // Recursively process subdirectories - await processMarkdownFiles(fullPath); - } else if (entry.name.endsWith('.md')) { - // Process markdown files - console.log(`Processing: ${fullPath}`); - - try { - const content = await fs.readFile(fullPath, 'utf8'); - const cleanedContent = removeLlmButtonsFromString(content); - - if (content !== cleanedContent) { - await fs.writeFile(fullPath, cleanedContent, 'utf8'); - console.log(` ✓ Cleaned: ${fullPath}`); - } - } catch (error) { - console.error(` ✗ Error processing ${fullPath}:`, error.message); - } - } - } - } catch (error) { - console.error(`Error reading directory ${dir}:`, error.message); - } -} - -/** - * Main function to clean up markdown files. - */ -async function cleanupMarkdownFiles() { - console.log('Starting markdown cleanup...'); - - if (!await fs.stat(BUILD_DIR).catch(() => false)) { - console.error(`Build directory not found: ${BUILD_DIR}`); - process.exit(1); - } - - await processMarkdownFiles(BUILD_DIR); - console.log('Markdown cleanup completed!'); -} - -// Run the cleanup if this script is executed directly -if (import.meta.url === `file://${process.argv[1]}`) { - cleanupMarkdownFiles().catch(console.error); -} - -export { cleanupMarkdownFiles, removeLlmButtonsFromString }; diff --git a/tools/utils/removeLlmButtons.js b/tools/utils/removeLlmButtons.js index 0c4647bebd..9a5bf8d598 100644 --- a/tools/utils/removeLlmButtons.js +++ b/tools/utils/removeLlmButtons.js @@ -10,22 +10,23 @@ function removeLlmButtons() { // Remove text nodes that contain LLM button text visit(tree, 'text', (node, index, parent) => { if (node.value && ( - node.value.includes('View as Markdown') || - node.value.includes('Copy for LLM') || - node.value.includes('View as MarkdownCopy for LLM') || - node.value.trim() === 'View as Markdown' || - node.value.trim() === 'Copy for LLM' + node.value.includes('View as Markdown') + || node.value.includes('Copy for LLM') + || node.value.includes('View as MarkdownCopy for LLM') + || node.value.trim() === 'View as Markdown' + || node.value.trim() === 'Copy for LLM' )) { // Remove the text node parent.children.splice(index, 1); return index; // Adjust index after removal } + return undefined; // Explicit return for consistency }); // Clean up empty paragraphs that resulted from text removal visit(tree, 'paragraph', (node, index, parent) => { // Check if paragraph is empty or only contains whitespace - const hasContent = node.children && node.children.some(child => { + const hasContent = node.children && node.children.some((child) => { if (child.type === 'text') { return child.value && child.value.trim().length > 0; } @@ -37,6 +38,7 @@ function removeLlmButtons() { parent.children.splice(index, 1); return index; } + return undefined; // Explicit return for consistency }); }; }