Skip to content

Commit 683bb8f

Browse files
authored
Add confirmation to "Copy MCP URL" page action (#3763)
1 parent 7d6a316 commit 683bb8f

File tree

2 files changed

+46
-19
lines changed

2 files changed

+46
-19
lines changed

.changeset/slick-candies-learn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"gitbook": patch
3+
---
4+
5+
Add confirmation to "Copy MCP URL" page action

packages/gitbook/src/components/PageActions/PageActions.tsx

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { Icon, type IconName, IconStyle } from '@gitbook/icons';
1111
import assertNever from 'assert-never';
1212
import QuickLRU from 'quick-lru';
1313
import React from 'react';
14-
import { create } from 'zustand';
14+
import { createStore, useStore } from 'zustand';
1515

1616
type PageActionType = 'button' | 'dropdown-menu-item';
1717

@@ -41,18 +41,13 @@ export function ActionOpenAssistant(props: { assistant: Assistant; type: PageAct
4141
type CopiedStore = {
4242
copied: boolean;
4343
loading: boolean;
44+
setLoading: (loading: boolean) => void;
45+
copy: (data: string, opts?: { onSuccess?: () => void }) => void;
4446
};
4547

46-
// We need to store everything in a store to share the state between every instance of the component.
47-
const useCopiedStore = create<
48-
CopiedStore & {
49-
setLoading: (loading: boolean) => void;
50-
copy: (data: string, opts?: { onSuccess?: () => void }) => void;
51-
}
52-
>((set) => {
48+
const createCopiedStateStore = () => {
5349
let timeoutRef: ReturnType<typeof setTimeout> | null = null;
54-
55-
return {
50+
return createStore<CopiedStore>()((set) => ({
5651
copied: false,
5752
loading: false,
5853
setLoading: (loading: boolean) => set({ loading }),
@@ -70,13 +65,25 @@ const useCopiedStore = create<
7065
timeoutRef = setTimeout(() => {
7166
set({ copied: false });
7267
onSuccess?.();
73-
74-
// Reset the timeout ref to avoid multiple timeouts
7568
timeoutRef = null;
7669
}, 1500);
7770
},
78-
};
79-
});
71+
}));
72+
};
73+
74+
const copiedStores = new Map<string, ReturnType<typeof createCopiedStateStore>>();
75+
76+
const getOrCreateCopiedStoreByKey = (storeKey: string) => {
77+
const existing = copiedStores.get(storeKey);
78+
if (existing) return existing;
79+
const created = createCopiedStateStore();
80+
copiedStores.set(storeKey, created);
81+
return created;
82+
};
83+
84+
function useCopiedStore(stateKey: string) {
85+
return useStore(getOrCreateCopiedStoreByKey(stateKey));
86+
}
8087

8188
/**
8289
* Cache for the markdown versbion of the page.
@@ -96,7 +103,7 @@ export function ActionCopyMarkdown(props: {
96103

97104
const closeDropdown = useDropdownMenuClose();
98105

99-
const { copied, loading, setLoading, copy } = useCopiedStore();
106+
const { copied, loading, setLoading, copy } = useCopiedStore('markdown');
100107

101108
// Fetch the markdown from the page
102109
const fetchMarkdown = async () => {
@@ -293,14 +300,29 @@ export function CopyToClipboard(props: {
293300
icon: IconName;
294301
}) {
295302
const { type, data, label, description, icon } = props;
303+
304+
const closeDropdown = useDropdownMenuClose();
305+
306+
const language = useLanguage();
307+
const labelKey = label.toLowerCase().replace(/\s+/g, '_');
308+
const { copied, copy } = useCopiedStore(labelKey);
309+
296310
return (
297311
<PageActionWrapper
298312
type={type}
299-
icon={icon}
300-
label={label}
313+
icon={copied ? 'check' : icon}
314+
label={copied ? tString(language, 'code_copied') : label}
301315
description={description}
302-
onClick={() => {
303-
navigator.clipboard.writeText(data);
316+
onClick={(e) => {
317+
e.preventDefault();
318+
319+
copy(data, {
320+
onSuccess: () => {
321+
if (type === 'dropdown-menu-item') {
322+
closeDropdown();
323+
}
324+
},
325+
});
304326
}}
305327
/>
306328
);

0 commit comments

Comments
 (0)