2
2
3
3
import { useAIChatState } from '@/components/AI' ;
4
4
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' ;
8
8
import { Button } from '@/components/primitives/Button' ;
9
9
import { DropdownMenuItem , useDropdownMenuClose } from '@/components/primitives/DropdownMenu' ;
10
10
import { tString , useLanguage } from '@/intl/client' ;
11
11
import type { TranslationLanguage } from '@/intl/translations' ;
12
+ import type { GitSyncState } from '@gitbook/api' ;
12
13
import { Icon , type IconName , IconStyle } from '@gitbook/icons' ;
13
14
import assertNever from 'assert-never' ;
14
15
import QuickLRU from 'quick-lru' ;
15
16
import type React from 'react' ;
16
17
import { create } from 'zustand' ;
17
18
18
- type AIActionType = 'button' | 'dropdown-menu-item' ;
19
+ type PageActionType = 'button' | 'dropdown-menu-item' ;
19
20
20
21
/**
21
22
* Opens our AI Docs Assistant.
22
23
*/
23
- export function OpenAIAssistant ( props : { assistant : Assistant ; type : AIActionType } ) {
24
+ export function OpenAIAssistant ( props : { assistant : Assistant ; type : PageActionType } ) {
24
25
const { assistant, type } = props ;
25
26
const chat = useAIChatState ( ) ;
26
27
const language = useLanguage ( ) ;
27
28
28
29
return (
29
- < AIActionWrapper
30
+ < PageActionWrapper
30
31
type = { type }
31
32
icon = { assistant . icon }
32
33
label = { assistant . label }
@@ -90,7 +91,7 @@ const markdownCache = new QuickLRU<string, string>({ maxSize: 10 });
90
91
*/
91
92
export function CopyMarkdown ( props : {
92
93
markdownPageUrl : string ;
93
- type : AIActionType ;
94
+ type : PageActionType ;
94
95
isDefaultAction ?: boolean ;
95
96
} ) {
96
97
const { markdownPageUrl, type, isDefaultAction } = props ;
@@ -130,7 +131,7 @@ export function CopyMarkdown(props: {
130
131
} ;
131
132
132
133
return (
133
- < AIActionWrapper
134
+ < PageActionWrapper
134
135
type = { type }
135
136
icon = { copied ? 'check' : 'copy' }
136
137
label = { copied ? tString ( language , 'code_copied' ) : tString ( language , 'copy_page' ) }
@@ -145,12 +146,12 @@ export function CopyMarkdown(props: {
145
146
/**
146
147
* Redirects to the markdown version of the page.
147
148
*/
148
- export function ViewAsMarkdown ( props : { markdownPageUrl : string ; type : AIActionType } ) {
149
+ export function ViewAsMarkdown ( props : { markdownPageUrl : string ; type : PageActionType } ) {
149
150
const { markdownPageUrl, type } = props ;
150
151
const language = useLanguage ( ) ;
151
152
152
153
return (
153
- < AIActionWrapper
154
+ < PageActionWrapper
154
155
type = { type }
155
156
icon = { < MarkdownIcon className = "size-4 fill-current" /> }
156
157
label = { tString ( language , 'view_page_markdown' ) }
@@ -166,15 +167,15 @@ export function ViewAsMarkdown(props: { markdownPageUrl: string; type: AIActionT
166
167
export function OpenInLLM ( props : {
167
168
provider : 'chatgpt' | 'claude' ;
168
169
url : string ;
169
- type : AIActionType ;
170
+ type : PageActionType ;
170
171
} ) {
171
172
const { provider, url, type } = props ;
172
173
const language = useLanguage ( ) ;
173
174
174
175
const providerLabel = provider === 'chatgpt' ? 'ChatGPT' : 'Claude' ;
175
176
176
177
return (
177
- < AIActionWrapper
178
+ < PageActionWrapper
178
179
type = { type }
179
180
icon = {
180
181
provider === 'chatgpt' ? (
@@ -191,11 +192,48 @@ export function OpenInLLM(props: {
191
192
) ;
192
193
}
193
194
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
+
194
232
/**
195
233
* Wraps an action in a button (for the default action) or dropdown menu item.
196
234
*/
197
- function AIActionWrapper ( props : {
198
- type : AIActionType ;
235
+ function PageActionWrapper ( props : {
236
+ type : PageActionType ;
199
237
icon : IconName | React . ReactNode ;
200
238
label : string ;
201
239
/**
@@ -205,10 +243,22 @@ function AIActionWrapper(props: {
205
243
onClick ?: ( e : React . MouseEvent ) => void ;
206
244
description ?: string ;
207
245
href ?: string ;
246
+ target ?: React . HTMLAttributeAnchorTarget ;
208
247
disabled ?: boolean ;
209
248
loading ?: boolean ;
210
249
} ) {
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 ;
212
262
213
263
if ( type === 'button' ) {
214
264
return (
@@ -222,7 +272,7 @@ function AIActionWrapper(props: {
222
272
className = "bg-tint-base text-sm"
223
273
onClick = { onClick }
224
274
href = { href }
225
- target = { href ? '_blank' : undefined }
275
+ target = { href ? target : undefined }
226
276
disabled = { disabled || loading }
227
277
>
228
278
{ shortLabel }
@@ -234,7 +284,7 @@ function AIActionWrapper(props: {
234
284
< DropdownMenuItem
235
285
className = "flex items-stretch gap-2.5 p-2"
236
286
href = { href }
237
- target = "_blank"
287
+ target = { target }
238
288
onClick = { onClick }
239
289
disabled = { disabled || loading }
240
290
>
@@ -255,9 +305,11 @@ function AIActionWrapper(props: {
255
305
</ div >
256
306
257
307
< 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" >
259
309
< 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 }
261
313
</ span >
262
314
{ description && < span className = "text-tint text-xs" > { description } </ span > }
263
315
</ div >
0 commit comments