Skip to content

Commit 729921f

Browse files
authored
Move "Edit on Git" and "Export PDF" actions into page actions dropdown (#3574)
1 parent 1839ea2 commit 729921f

File tree

19 files changed

+181
-157
lines changed

19 files changed

+181
-157
lines changed

packages/gitbook/src/components/AIActions/AIActions.tsx renamed to packages/gitbook/src/components/PageActions/PageActions.tsx

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,32 @@
22

33
import { useAIChatState } from '@/components/AI';
44
import type { Assistant } from '@/components/AI';
5-
import { ChatGPTIcon } from '@/components/AIActions/assets/ChatGPTIcon';
6-
import { ClaudeIcon } from '@/components/AIActions/assets/ClaudeIcon';
7-
import { MarkdownIcon } from '@/components/AIActions/assets/MarkdownIcon';
5+
import { ChatGPTIcon } from '@/components/PageActions/assets/ChatGPTIcon';
6+
import { ClaudeIcon } from '@/components/PageActions/assets/ClaudeIcon';
7+
import { MarkdownIcon } from '@/components/PageActions/assets/MarkdownIcon';
88
import { Button } from '@/components/primitives/Button';
99
import { DropdownMenuItem, useDropdownMenuClose } from '@/components/primitives/DropdownMenu';
1010
import { tString, useLanguage } from '@/intl/client';
1111
import type { TranslationLanguage } from '@/intl/translations';
12+
import type { GitSyncState } from '@gitbook/api';
1213
import { Icon, type IconName, IconStyle } from '@gitbook/icons';
1314
import assertNever from 'assert-never';
1415
import QuickLRU from 'quick-lru';
1516
import type React from 'react';
1617
import { create } from 'zustand';
1718

18-
type AIActionType = 'button' | 'dropdown-menu-item';
19+
type PageActionType = 'button' | 'dropdown-menu-item';
1920

2021
/**
2122
* Opens our AI Docs Assistant.
2223
*/
23-
export function OpenAIAssistant(props: { assistant: Assistant; type: AIActionType }) {
24+
export function OpenAIAssistant(props: { assistant: Assistant; type: PageActionType }) {
2425
const { assistant, type } = props;
2526
const chat = useAIChatState();
2627
const language = useLanguage();
2728

2829
return (
29-
<AIActionWrapper
30+
<PageActionWrapper
3031
type={type}
3132
icon={assistant.icon}
3233
label={assistant.label}
@@ -90,7 +91,7 @@ const markdownCache = new QuickLRU<string, string>({ maxSize: 10 });
9091
*/
9192
export function CopyMarkdown(props: {
9293
markdownPageUrl: string;
93-
type: AIActionType;
94+
type: PageActionType;
9495
isDefaultAction?: boolean;
9596
}) {
9697
const { markdownPageUrl, type, isDefaultAction } = props;
@@ -130,7 +131,7 @@ export function CopyMarkdown(props: {
130131
};
131132

132133
return (
133-
<AIActionWrapper
134+
<PageActionWrapper
134135
type={type}
135136
icon={copied ? 'check' : 'copy'}
136137
label={copied ? tString(language, 'code_copied') : tString(language, 'copy_page')}
@@ -145,12 +146,12 @@ export function CopyMarkdown(props: {
145146
/**
146147
* Redirects to the markdown version of the page.
147148
*/
148-
export function ViewAsMarkdown(props: { markdownPageUrl: string; type: AIActionType }) {
149+
export function ViewAsMarkdown(props: { markdownPageUrl: string; type: PageActionType }) {
149150
const { markdownPageUrl, type } = props;
150151
const language = useLanguage();
151152

152153
return (
153-
<AIActionWrapper
154+
<PageActionWrapper
154155
type={type}
155156
icon={<MarkdownIcon className="size-4 fill-current" />}
156157
label={tString(language, 'view_page_markdown')}
@@ -166,15 +167,15 @@ export function ViewAsMarkdown(props: { markdownPageUrl: string; type: AIActionT
166167
export function OpenInLLM(props: {
167168
provider: 'chatgpt' | 'claude';
168169
url: string;
169-
type: AIActionType;
170+
type: PageActionType;
170171
}) {
171172
const { provider, url, type } = props;
172173
const language = useLanguage();
173174

174175
const providerLabel = provider === 'chatgpt' ? 'ChatGPT' : 'Claude';
175176

176177
return (
177-
<AIActionWrapper
178+
<PageActionWrapper
178179
type={type}
179180
icon={
180181
provider === 'chatgpt' ? (
@@ -191,11 +192,48 @@ export function OpenInLLM(props: {
191192
);
192193
}
193194

195+
export function GitEditLink(props: {
196+
type: PageActionType;
197+
provider: GitSyncState['installationProvider'];
198+
url: string;
199+
}) {
200+
const { type, provider, url } = props;
201+
const language = useLanguage();
202+
203+
const providerName =
204+
provider === 'github' ? 'GitHub' : provider === 'gitlab' ? 'GitLab' : 'Git';
205+
206+
return (
207+
<PageActionWrapper
208+
type={type}
209+
icon={provider === 'gitlab' ? 'gitlab' : 'github'}
210+
label={tString(language, 'edit_on_git', providerName)}
211+
shortLabel={tString(language, 'edit')}
212+
href={url}
213+
/>
214+
);
215+
}
216+
217+
export function ViewAsPDF(props: { url: string; type: PageActionType }) {
218+
const { url, type } = props;
219+
const language = useLanguage();
220+
221+
return (
222+
<PageActionWrapper
223+
type={type}
224+
icon="file-pdf"
225+
label={tString(language, 'pdf_download')}
226+
href={url}
227+
target="_self"
228+
/>
229+
);
230+
}
231+
194232
/**
195233
* Wraps an action in a button (for the default action) or dropdown menu item.
196234
*/
197-
function AIActionWrapper(props: {
198-
type: AIActionType;
235+
function PageActionWrapper(props: {
236+
type: PageActionType;
199237
icon: IconName | React.ReactNode;
200238
label: string;
201239
/**
@@ -205,10 +243,22 @@ function AIActionWrapper(props: {
205243
onClick?: (e: React.MouseEvent) => void;
206244
description?: string;
207245
href?: string;
246+
target?: React.HTMLAttributeAnchorTarget;
208247
disabled?: boolean;
209248
loading?: boolean;
210249
}) {
211-
const { type, icon, label, shortLabel, onClick, href, description, disabled, loading } = props;
250+
const {
251+
type,
252+
icon,
253+
label,
254+
shortLabel,
255+
onClick,
256+
href,
257+
target = '_blank',
258+
description,
259+
disabled,
260+
loading,
261+
} = props;
212262

213263
if (type === 'button') {
214264
return (
@@ -222,7 +272,7 @@ function AIActionWrapper(props: {
222272
className="bg-tint-base text-sm"
223273
onClick={onClick}
224274
href={href}
225-
target={href ? '_blank' : undefined}
275+
target={href ? target : undefined}
226276
disabled={disabled || loading}
227277
>
228278
{shortLabel}
@@ -234,7 +284,7 @@ function AIActionWrapper(props: {
234284
<DropdownMenuItem
235285
className="flex items-stretch gap-2.5 p-2"
236286
href={href}
237-
target="_blank"
287+
target={target}
238288
onClick={onClick}
239289
disabled={disabled || loading}
240290
>
@@ -255,9 +305,11 @@ function AIActionWrapper(props: {
255305
</div>
256306

257307
<div className="flex flex-1 flex-col gap-0.5">
258-
<span className="flex items-center gap-2 text-tint-strong">
308+
<span className="flex items-center gap-1 text-tint-strong">
259309
<span className="truncate font-medium text-sm">{label}</span>
260-
{href ? <Icon icon="arrow-up-right" className="size-3 shrink-0" /> : null}
310+
{href && target === '_blank' ? (
311+
<Icon icon="arrow-up-right" className="size-3 shrink-0 text-tint-subtle" />
312+
) : null}
261313
</span>
262314
{description && <span className="text-tint text-xs">{description}</span>}
263315
</div>

packages/gitbook/src/components/AIActions/AIActionsDropdown.tsx renamed to packages/gitbook/src/components/PageActions/PageActionsDropdown.tsx

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,50 @@
22

33
import {
44
CopyMarkdown,
5+
GitEditLink,
56
OpenAIAssistant,
67
OpenInLLM,
78
ViewAsMarkdown,
8-
} from '@/components/AIActions/AIActions';
9+
ViewAsPDF,
10+
} from '@/components/PageActions/PageActions';
911
import { Button, ButtonGroup } from '@/components/primitives/Button';
1012
import { DropdownMenu, DropdownMenuSeparator } from '@/components/primitives/DropdownMenu';
1113
import { tString, useLanguage } from '@/intl/client';
12-
import type { SiteCustomizationSettings } from '@gitbook/api';
14+
import type { GitSyncState, SiteCustomizationSettings } from '@gitbook/api';
1315
import { Icon } from '@gitbook/icons';
1416
import { useRef } from 'react';
1517
import { useAI } from '../AI';
1618

17-
interface AIActionsDropdownProps {
19+
interface PageActionsDropdownProps {
1820
markdownPageUrl: string;
1921
className?: string;
2022
actions: SiteCustomizationSettings['pageActions'];
23+
editOnGit?: {
24+
provider: GitSyncState['installationProvider'];
25+
url: string;
26+
};
27+
pdfUrl?: string;
2128
}
2229

2330
/**
2431
* Dropdown menu for the AI Actions (Ask Docs Assistant, Copy page, View as Markdown, Open in LLM).
2532
*/
26-
export function AIActionsDropdown(props: AIActionsDropdownProps) {
33+
export function PageActionsDropdown(props: PageActionsDropdownProps) {
2734
const ref = useRef<HTMLDivElement>(null);
2835
const assistants = useAI().assistants.filter(
2936
(assistant) => assistant.ui === true && assistant.pageAction
3037
);
3138
const language = useLanguage();
3239

33-
return assistants.length > 0 || props.actions.markdown || props.actions.externalAI ? (
40+
const defaultActions = [assistants.length > 0, props.actions.markdown, props.editOnGit].filter(
41+
Boolean
42+
);
43+
const dropdownActions = [props.actions.externalAI, props.pdfUrl].filter(Boolean);
44+
45+
return [...defaultActions, ...dropdownActions].length > 0 ? (
3446
<ButtonGroup ref={ref} className={props.className}>
35-
<DefaultAction {...props} />
36-
{props.actions.markdown || props.actions.externalAI ? (
47+
{defaultActions.length > 0 ? <DefaultAction {...props} /> : null}
48+
{dropdownActions.length > 0 ? (
3749
<DropdownMenu
3850
align="end"
3951
className="!min-w-60 max-w-max"
@@ -45,15 +57,18 @@ export function AIActionsDropdown(props: AIActionsDropdownProps) {
4557
className="size-3 transition-transform group-data-[state=open]/button:rotate-180"
4658
/>
4759
}
48-
label={tString(language, 'more')}
49-
iconOnly
60+
label={tString(
61+
language,
62+
defaultActions.some(Boolean) ? 'more' : 'actions'
63+
)}
64+
iconOnly={defaultActions.some(Boolean)}
5065
size="xsmall"
5166
variant="secondary"
5267
className="bg-tint-base text-sm"
5368
/>
5469
}
5570
>
56-
<AIActionsDropdownMenuContent {...props} />
71+
<PageActionsDropdownMenuContent {...props} />
5772
</DropdownMenu>
5873
) : null}
5974
</ButtonGroup>
@@ -63,7 +78,7 @@ export function AIActionsDropdown(props: AIActionsDropdownProps) {
6378
/**
6479
* The content of the dropdown menu.
6580
*/
66-
function AIActionsDropdownMenuContent(props: AIActionsDropdownProps) {
81+
function PageActionsDropdownMenuContent(props: PageActionsDropdownProps) {
6782
const { markdownPageUrl, actions } = props;
6883
const assistants = useAI().assistants.filter(
6984
(assistant) => assistant.ui === true && assistant.pageAction
@@ -98,14 +113,30 @@ function AIActionsDropdownMenuContent(props: AIActionsDropdownProps) {
98113
<OpenInLLM provider="claude" url={markdownPageUrl} type="dropdown-menu-item" />
99114
</>
100115
) : null}
116+
117+
{props.editOnGit || props.pdfUrl ? (
118+
<>
119+
<DropdownMenuSeparator className="first:hidden" />
120+
{props.editOnGit ? (
121+
<GitEditLink
122+
type="dropdown-menu-item"
123+
provider={props.editOnGit.provider}
124+
url={props.editOnGit.url}
125+
/>
126+
) : null}
127+
{props.pdfUrl ? (
128+
<ViewAsPDF url={props.pdfUrl} type="dropdown-menu-item" />
129+
) : null}
130+
</>
131+
) : null}
101132
</>
102133
);
103134
}
104135

105136
/**
106137
* A default action shown as a quick-access button beside the dropdown menu
107138
*/
108-
function DefaultAction(props: AIActionsDropdownProps) {
139+
function DefaultAction(props: PageActionsDropdownProps) {
109140
const { markdownPageUrl, actions } = props;
110141
const assistants = useAI().assistants.filter(
111142
(assistant) => assistant.ui === true && assistant.pageAction
@@ -116,21 +147,23 @@ function DefaultAction(props: AIActionsDropdownProps) {
116147
return <OpenAIAssistant assistant={assistant} type="button" />;
117148
}
118149

119-
if (actions.markdown) {
150+
if (props.editOnGit) {
120151
return (
121-
<CopyMarkdown
122-
isDefaultAction={!assistant}
123-
markdownPageUrl={markdownPageUrl}
152+
<GitEditLink
124153
type="button"
154+
provider={props.editOnGit.provider}
155+
url={props.editOnGit.url}
125156
/>
126157
);
127158
}
128159

129-
if (actions.externalAI) {
160+
if (actions.markdown) {
130161
return (
131-
<>
132-
<OpenInLLM provider="chatgpt" url={markdownPageUrl} type="button" />
133-
</>
162+
<CopyMarkdown
163+
isDefaultAction={!assistant}
164+
markdownPageUrl={markdownPageUrl}
165+
type="button"
166+
/>
134167
);
135168
}
136169
}

0 commit comments

Comments
 (0)