Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apify-docs-theme/src/theme/MDXComponents/index.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -29,5 +30,6 @@ const MDXComponents = {
admonition: Admonition,
mermaid: Mermaid,
RunnableCodeBlock,
LLMButtons,
};
export default MDXComponents;
20 changes: 19 additions & 1 deletion docusaurus.config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
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');
const { externalLinkProcessor } = require('./tools/utils/externalLink');
const { removeLlmButtons } = require('./tools/utils/removeLlmButtons');

/** @type {Partial<import('@docusaurus/types').DocusaurusConfig>} */
module.exports = {
Expand Down Expand Up @@ -196,6 +197,21 @@ module.exports = {
md = md.replace('--&gt;', '-->');
}

// Find the first Heading h1 and add LLMButtons after it
// eslint-disable-next-line max-len
const headingRegex = /(<Heading[^>]*as=\{"h1"\}[^>]*className=\{"openapi__heading"\}[^>]*children=\{[^}]*\}[^>]*>\s*<\/Heading>)/;
md = md.replace(headingRegex, '$1\n\n<LLMButtons />\n');

return md;
},
createInfoPageMD: (pageData) => {
let md = createInfoPageMD(pageData);

// Find the first Heading h1 and add LLMButtons after it
// eslint-disable-next-line max-len
const headingRegex = /(<Heading[^>]*as=\{"h1"\}[^>]*className=\{"openapi__heading"\}[^>]*children=\{[^}]*\}[^>]*>\s*<\/Heading>)/;
md = md.replace(headingRegex, '$1\n\n<LLMButtons />\n');

return md;
},
},
Expand Down Expand Up @@ -293,6 +309,8 @@ module.exports = {
categoryName: 'Platform documentation',
},
],
// Add custom remark processing to remove LLM button text
remarkPlugins: [removeLlmButtons],
},
},
],
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion sources/legal/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ slug: /
hide_table_of_contents: true
---

# Apify Legal
<!-- vale off -->

## Company details (Impressum)
Expand Down
2 changes: 0 additions & 2 deletions sources/platform/actors/running/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
5 changes: 2 additions & 3 deletions sources/platform/index.mdx
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 0 additions & 2 deletions sources/platform/proxy/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.**

---
Expand Down
2 changes: 0 additions & 2 deletions sources/platform/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.**

---
Expand Down
2 changes: 0 additions & 2 deletions sources/platform/storage/dataset.md
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
2 changes: 0 additions & 2 deletions sources/platform/storage/key_value_store.md
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
71 changes: 71 additions & 0 deletions src/components/LLMButtons/CopyForLLM/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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 [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);

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 (
<button
className={styles.llmButton}
title="Copy for LLM"
onClick={handleCopy}
disabled={isLoading}
>
<span
className={`${styles.llmButtonIcon} ${styles.llmButtonIconBackgroundCopy}`}
aria-label="Copy for LLM"
/>
<ButtonText isLoading={isLoading} isCopied={isCopied} />
</button>
);
}
37 changes: 37 additions & 0 deletions src/components/LLMButtons/ViewAsMarkdown/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';

import styles from '../styles.module.css';

export default function ViewAsMarkdown() {
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`;
window.open(markdownUrl, '_blank');
} catch (error) {
console.error('Error opening markdown file:', error);
}
};

return (
<button
className={styles.llmButton}
title="View as Markdown"
onClick={handleClick}
>
<span
className={`${styles.llmButtonIcon} ${styles.llmButtonIconBackgroundMarkdown}`}
aria-label="View as Markdown"
/>
View as Markdown
</button>
);
}
15 changes: 15 additions & 0 deletions src/components/LLMButtons/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={styles.llmButtonsContainer}>
<ViewAsMarkdown />
<div className={styles.llmButtonsSeparator}></div>
<CopyForLLM />
</div>
);
}
57 changes: 57 additions & 0 deletions src/components/LLMButtons/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
.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: var(--ifm-hr-background-color);
}

.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;
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'] .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');
}
61 changes: 61 additions & 0 deletions src/theme/DocItem/Content/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useDoc } from '@docusaurus/plugin-content-docs/client';
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';
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();
const location = useLocation();

// 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 allowedPaths = [
'/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 = allowedPaths.some((path) => location.pathname.startsWith(path));

return (
<div className={clsx(ThemeClassNames.docs.docMarkdown, 'markdown')}>
{syntheticTitle && <Heading as="h1">{syntheticTitle}</Heading>}
{shouldShowLLMButtons && <LLMButtons />}
<MDXContent>{children}</MDXContent>
</div>
);
}
Loading
Loading