Skip to content

Commit 0c1d8f8

Browse files
committed
Update
1 parent f87db08 commit 0c1d8f8

File tree

5 files changed

+263
-247
lines changed

5 files changed

+263
-247
lines changed

packages/gitbook/src/components/AIActions/AIActionsDropdown.tsx

Lines changed: 46 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
'use client';
2+
23
import { useAIChatController, useAIChatState } from '@/components/AI/useAIChat';
34
import AIChatIcon from '@/components/AIChat/AIChatIcon';
45
import { Button } from '@/components/primitives/Button';
56
import { DropdownMenu, DropdownMenuItem } from '@/components/primitives/DropdownMenu';
67
import { tString, useLanguage } from '@/intl/client';
78
import { Icon, type IconName, IconStyle } from '@gitbook/icons';
8-
import { useEffect, useRef } from 'react';
9+
import { useRef } from 'react';
910

10-
type Action = {
11+
type AIAction = {
1112
icon?: IconName | React.ReactNode;
1213
label: string;
1314
description?: string;
@@ -18,7 +19,12 @@ type Action = {
1819
onClick?: () => void;
1920
};
2021

21-
export function AIActionsDropdown() {
22+
export function AIActionsDropdown(props: {
23+
markdown?: string;
24+
markdownUrl: string;
25+
}) {
26+
const { markdown, markdownUrl } = props;
27+
2228
const chatController = useAIChatController();
2329
const chat = useAIChatState();
2430
const language = useLanguage();
@@ -36,82 +42,45 @@ export function AIActionsDropdown() {
3642
});
3743
};
3844

39-
const handleCopyPage = async () => {
40-
const markdownUrl = `${window.location.href}.md`;
41-
42-
// Get the page content
43-
const markdown = await fetch(markdownUrl).then((res) => res.text());
44-
45-
// Copy the markdown to the clipboard
46-
navigator.clipboard.writeText(markdown);
47-
};
48-
49-
const handleViewAsMarkdown = () => {
50-
// Open the page in Markdown format
51-
const currentUrl = window.location.href;
52-
const markdownUrl = `${currentUrl}.md`;
53-
window.open(markdownUrl, '_blank');
54-
};
55-
56-
const actions: Action[] = [
45+
const actions: AIAction[] = [
5746
{
58-
icon: 'copy',
59-
label: 'Copy page',
60-
description: 'Copy the page content',
61-
onClick: handleCopyPage,
47+
icon: <AIChatIcon />,
48+
label: 'Ask Docs Assistant',
49+
description: 'Ask our Docs Assistant about this page',
50+
onClick: () => {},
6251
},
52+
...(markdown
53+
? [
54+
{
55+
icon: 'copy',
56+
label: 'Copy for LLMs',
57+
description: 'Copy page as Markdown',
58+
onClick: () => {
59+
if (!markdown) return;
60+
navigator.clipboard.writeText(markdown);
61+
},
62+
},
63+
]
64+
: []),
6365
{
64-
icon: 'markdown',
66+
icon: (
67+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 471 289.85" className="size-5">
68+
<title>markdown icon</title>
69+
<path d="M437,289.85H34a34,34,0,0,1-34-34V34A34,34,0,0,1,34,0H437a34,34,0,0,1,34,34V255.88A34,34,0,0,1,437,289.85ZM34,22.64A11.34,11.34,0,0,0,22.64,34V255.88A11.34,11.34,0,0,0,34,267.2H437a11.34,11.34,0,0,0,11.33-11.32V34A11.34,11.34,0,0,0,437,22.64Z" />
70+
<path d="M67.93,221.91v-154h45.29l45.29,56.61L203.8,67.93h45.29v154H203.8V133.6l-45.29,56.61L113.22,133.6v88.31Zm283.06,0-67.94-74.72h45.29V67.93h45.29v79.26h45.29Z" />
71+
</svg>
72+
),
6573
label: 'View as Markdown',
66-
description: 'Open a Markdown version of this page',
74+
description: 'View this page as plain text',
6775
isExternal: true,
68-
onClick: handleViewAsMarkdown,
76+
onClick: () => {
77+
window.open(markdownUrl, '_blank');
78+
},
6979
},
7080
];
7181

72-
// Get the header width with title and check if there is enough space to show the dropdown
73-
useEffect(() => {
74-
const getHeaderAvailableSpace = () => {
75-
const header = document.getElementById('page-header');
76-
const headerTitle = header?.getElementsByTagName('h1')[0];
77-
78-
return (
79-
(header?.getBoundingClientRect().width ?? 0) -
80-
(headerTitle?.getBoundingClientRect().width ?? 0)
81-
);
82-
};
83-
84-
const dropdownWidth = 202;
85-
86-
window.addEventListener('resize', () => {
87-
const headerAvailableSpace = getHeaderAvailableSpace();
88-
if (ref.current) {
89-
if (headerAvailableSpace <= dropdownWidth) {
90-
ref.current.classList.add('-mt-3');
91-
ref.current.classList.remove('mt-3');
92-
} else {
93-
ref.current.classList.remove('-mt-3');
94-
ref.current.classList.add('mt-3');
95-
}
96-
}
97-
});
98-
99-
window.addEventListener('load', () => {
100-
const headerAvailableSpace = getHeaderAvailableSpace();
101-
if (ref.current) {
102-
if (headerAvailableSpace <= dropdownWidth) {
103-
ref.current.classList.add('-mt-3');
104-
ref.current.classList.remove('mt-3');
105-
} else {
106-
ref.current.classList.remove('-mt-3');
107-
ref.current.classList.add('mt-3');
108-
}
109-
}
110-
});
111-
}, []);
112-
11382
return (
114-
<div ref={ref} className="hidden items-stretch justify-start md:flex">
83+
<div ref={ref} className="hidden h-fit items-stretch justify-start md:flex">
11584
<Button
11685
icon={<AIChatIcon className="size-3.5" />}
11786
size="small"
@@ -145,17 +114,19 @@ export function AIActionsDropdown() {
145114
{typeof action.icon === 'string' ? (
146115
<Icon
147116
icon={action.icon as IconName}
148-
iconStyle={IconStyle.Light}
149-
className="size-full"
117+
iconStyle={IconStyle.Regular}
118+
className="size-full fill-transparent stroke-current"
150119
/>
151120
) : (
152121
action.icon
153122
)}
154123
</div>
155124
) : null}
156-
<div className="flex flex-1 flex-col">
157-
<span className="flex items-center gap-1.5 text-tint-strong">
158-
<span className="truncate font-medium">{action.label}</span>
125+
<div className="flex flex-1 flex-col gap-0.5">
126+
<span className="flex items-center gap-2 text-tint-strong">
127+
<span className="truncate font-medium text-[0.9375rem]">
128+
{action.label}
129+
</span>
159130
{action.isExternal ? (
160131
<Icon icon="arrow-up-right" className="size-3" />
161132
) : null}

packages/gitbook/src/components/PageBody/PageHeader.tsx

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { AIActionsDropdown } from '@/components/AIActions/AIActionsDropdown';
22
import type { GitBookSiteContext } from '@/lib/context';
3+
import { getMarkdownForPage } from '@/lib/markdownPage';
34
import type { AncestorRevisionPage } from '@/lib/pages';
45
import { tcls } from '@/lib/tailwind';
56
import type { RevisionPageDocument } from '@gitbook/api';
@@ -16,21 +17,23 @@ export async function PageHeader(props: {
1617
const { context, page, ancestors } = props;
1718
const { revision, linker } = context;
1819

20+
const markdownResult = await getMarkdownForPage(context, page.path);
21+
1922
if (!page.layout.title && !page.layout.description) {
2023
return null;
2124
}
2225

2326
return (
2427
<header
25-
id="page-header"
2628
className={tcls(
2729
'max-w-3xl',
2830
'page-full-width:max-w-screen-2xl',
2931
'mx-auto',
3032
'mb-6',
3133
'space-y-3',
3234
'page-api-block:ml-0',
33-
'relative'
35+
'relative',
36+
'page-api-block:max-w-full'
3437
)}
3538
>
3639
{ancestors.length > 0 && (
@@ -79,27 +82,33 @@ export async function PageHeader(props: {
7982
</ol>
8083
</nav>
8184
)}
82-
{page.layout.title ? (
83-
<h1
84-
className={tcls(
85-
'text-4xl',
86-
'font-bold',
87-
'flex',
88-
'items-center',
89-
'gap-4',
90-
'w-fit'
91-
)}
92-
>
93-
<PageIcon page={page} style={['text-tint-subtle ', 'shrink-0']} />
94-
{page.title}
95-
</h1>
96-
) : null}
85+
<div className="flex items-start justify-between gap-4">
86+
{page.layout.title ? (
87+
<h1
88+
className={tcls(
89+
'text-4xl',
90+
'font-bold',
91+
'flex',
92+
'items-center',
93+
'gap-4',
94+
'w-fit',
95+
'text-pretty'
96+
)}
97+
>
98+
<PageIcon page={page} style={['text-tint-subtle ', 'shrink-0']} />
99+
{page.title}
100+
</h1>
101+
) : null}
102+
{page.layout.tableOfContents ? (
103+
<AIActionsDropdown
104+
markdown={markdownResult.data}
105+
markdownUrl={`${context.linker.toPathInSite(page.path)}.md`}
106+
/>
107+
) : null}
108+
</div>
97109
{page.description && page.layout.description ? (
98110
<p className={tcls('text-lg', 'text-tint')}>{page.description}</p>
99111
) : null}
100-
<div className="!mt-0 absolute top-0 right-0">
101-
<AIActionsDropdown />
102-
</div>
103112
</header>
104113
);
105114
}

packages/gitbook/src/components/primitives/Button.tsx

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,19 @@ export function Button({
7474
const domClassName = tcls(variantClasses[variant], sizeClasses, className);
7575
const buttonOnlyClassNames = useClassnames(['ButtonStyles']);
7676

77+
const content = (
78+
<>
79+
{icon ? (
80+
typeof icon === 'string' ? (
81+
<Icon icon={icon as IconName} className={tcls('size-[1em]')} />
82+
) : (
83+
icon
84+
)
85+
) : null}
86+
{iconOnly ? null : label}
87+
</>
88+
);
89+
7790
if (href) {
7891
return (
7992
<Link
@@ -85,14 +98,7 @@ export function Button({
8598
target={target}
8699
{...rest}
87100
>
88-
{icon ? (
89-
typeof icon === 'string' ? (
90-
<Icon icon={icon as IconName} className={tcls('size-[1em]')} />
91-
) : (
92-
icon
93-
)
94-
) : null}
95-
{iconOnly ? null : label}
101+
{content}
96102
</Link>
97103
);
98104
}
@@ -104,14 +110,7 @@ export function Button({
104110
aria-label={label}
105111
{...rest}
106112
>
107-
{icon ? (
108-
typeof icon === 'string' ? (
109-
<Icon icon={icon as IconName} className={tcls('size-[1em]')} />
110-
) : (
111-
icon
112-
)
113-
) : null}
114-
{iconOnly ? null : label}
113+
{content}
115114
</button>
116115
);
117116

0 commit comments

Comments
 (0)