Consolidate copy to clipboard (#36353)
* Consolidate copy to clipboard * Fix * Fix some extra clipboard events. * Fix the tests. Fix a small issue with the copy button. * Fix --------- Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import { Check, Copy } from 'lucide-react'
|
||||
import { type MouseEvent, useCallback, useEffect, useState } from 'react'
|
||||
import { type ThemedToken } from 'shiki'
|
||||
import { type NodeHover } from 'twoslash'
|
||||
import { cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
import { cn, copyToClipboard, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
|
||||
export function AnnotatedSpan({
|
||||
token,
|
||||
@@ -89,13 +89,10 @@ export function CodeCopyButton({ className, content }: { className?: string; con
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(content)
|
||||
copyToClipboard(content, () => {
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 1000)
|
||||
} catch (error) {
|
||||
console.error('Failed to copy text: ', error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,8 +5,9 @@ import { Item, ItemParams, Menu } from 'react-contexify'
|
||||
import type { SupaRow } from 'components/grid/types'
|
||||
import { useTableEditorStateSnapshot } from 'state/table-editor'
|
||||
import { useTableEditorTableStateSnapshot } from 'state/table-editor-table'
|
||||
import { copyToClipboard } from 'ui'
|
||||
import { ROW_CONTEXT_MENU_ID } from '.'
|
||||
import { copyToClipboard, formatClipboardValue } from '../../utils/common'
|
||||
import { formatClipboardValue } from '../../utils/common'
|
||||
|
||||
export type RowContextMenuProps = {
|
||||
rows: SupaRow[]
|
||||
|
||||
@@ -5,12 +5,3 @@ export function formatClipboardValue(value: any) {
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
export const copyToClipboard = (str: string, callback = () => {}) => {
|
||||
const focused = window.document.hasFocus()
|
||||
if (focused) {
|
||||
window.navigator?.clipboard?.writeText(str).then(callback)
|
||||
} else {
|
||||
console.warn('Unable to copy to clipboard')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ import { useMemo } from 'react'
|
||||
|
||||
import { getAPIKeys, useProjectSettingsV2Query } from 'data/config/project-settings-v2-query'
|
||||
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
import { Badge } from 'ui'
|
||||
import { Badge, copyToClipboard } from 'ui'
|
||||
import type { ICommand } from 'ui-patterns/CommandMenu'
|
||||
import {
|
||||
PageType,
|
||||
|
||||
@@ -2,8 +2,7 @@ import { Link } from 'lucide-react'
|
||||
|
||||
import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query'
|
||||
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
import { Badge } from 'ui'
|
||||
import { Badge, copyToClipboard } from 'ui'
|
||||
import { useRegisterCommands, useSetCommandMenuOpen } from 'ui-patterns/CommandMenu'
|
||||
import { COMMAND_MENU_SECTIONS } from './CommandMenu.utils'
|
||||
import { orderCommandSectionsByPriority } from './ordering'
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { Check, Webhook } from 'lucide-react'
|
||||
import { Badge, Input } from 'ui'
|
||||
import { Badge, Input, copyToClipboard } from 'ui'
|
||||
|
||||
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
|
||||
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
import { Hook } from './hooks.constants'
|
||||
|
||||
interface HookCardProps {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Column, useRowSelection } from 'react-data-grid'
|
||||
|
||||
import { User } from 'data/auth/users-infinite-query'
|
||||
import { BASE_PATH } from 'lib/constants'
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
import {
|
||||
Checkbox_Shadcn_,
|
||||
cn,
|
||||
@@ -14,6 +13,7 @@ import {
|
||||
ContextMenuItem_Shadcn_,
|
||||
ContextMenuSeparator_Shadcn_,
|
||||
ContextMenuTrigger_Shadcn_,
|
||||
copyToClipboard,
|
||||
} from 'ui'
|
||||
import { PROVIDERS_SCHEMAS } from '../AuthProvidersFormValidation'
|
||||
import { ColumnConfiguration, USERS_TABLE_COLUMNS } from './Users.constants'
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Check, ChevronRight, Copy } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
import {
|
||||
Button,
|
||||
cn,
|
||||
Collapsible_Shadcn_,
|
||||
CollapsibleContent_Shadcn_,
|
||||
CollapsibleTrigger_Shadcn_,
|
||||
copyToClipboard,
|
||||
Separator,
|
||||
} from 'ui'
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Check, Clipboard } from 'lucide-react'
|
||||
import { forwardRef, useState } from 'react'
|
||||
import { cn } from 'ui'
|
||||
|
||||
import { cn, copyToClipboard } from 'ui'
|
||||
|
||||
const CommandRender = forwardRef<HTMLDivElement, { commands: any[]; className?: string }>(
|
||||
({ commands, className }, ref) => {
|
||||
@@ -35,10 +36,8 @@ const Command = ({ item }: any) => {
|
||||
onClick={() => {
|
||||
function onCopy(value: any) {
|
||||
setIsCopied(true)
|
||||
navigator.clipboard.writeText(value).then()
|
||||
setTimeout(function () {
|
||||
setIsCopied(false)
|
||||
}, 3000)
|
||||
copyToClipboard(value)
|
||||
setTimeout(() => setIsCopied(false), 3000)
|
||||
}
|
||||
onCopy(item.command)
|
||||
}}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectConte
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query'
|
||||
import type { EdgeFunctionsResponse } from 'data/edge-functions/edge-functions-query'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
import { copyToClipboard, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
|
||||
interface EdgeFunctionsListItemProps {
|
||||
function: EdgeFunctionsResponse
|
||||
@@ -55,10 +55,8 @@ export const EdgeFunctionsListItem = ({ function: item }: EdgeFunctionsListItemP
|
||||
onClick={(event: any) => {
|
||||
function onCopy(value: any) {
|
||||
setIsCopied(true)
|
||||
navigator.clipboard.writeText(value).then()
|
||||
setTimeout(function () {
|
||||
setIsCopied(false)
|
||||
}, 3000)
|
||||
copyToClipboard(value)
|
||||
setTimeout(() => setIsCopied(false), 3000)
|
||||
}
|
||||
event.stopPropagation()
|
||||
onCopy(endpoint)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useParams } from 'common'
|
||||
import { Button, Input } from 'ui'
|
||||
import { Button, Input, copyToClipboard } from 'ui'
|
||||
|
||||
import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query'
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
import { Copy } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import ContentSnippet from '../ContentSnippet'
|
||||
|
||||
@@ -3,13 +3,13 @@ import { useState } from 'react'
|
||||
import DataGrid, { CalculatedColumn } from 'react-data-grid'
|
||||
|
||||
import { handleCopyCell } from 'components/grid/SupabaseGrid.utils'
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
import {
|
||||
cn,
|
||||
ContextMenu_Shadcn_,
|
||||
ContextMenuContent_Shadcn_,
|
||||
ContextMenuItem_Shadcn_,
|
||||
ContextMenuTrigger_Shadcn_,
|
||||
copyToClipboard,
|
||||
} from 'ui'
|
||||
import { CellDetailPanel } from './CellDetailPanel'
|
||||
|
||||
|
||||
@@ -11,10 +11,16 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip'
|
||||
import { DownloadResultsButton } from 'components/ui/DownloadResultsButton'
|
||||
import { useSelectedLog } from 'hooks/analytics/useSelectedLog'
|
||||
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
import { useProfile } from 'lib/profile'
|
||||
import { ResponseError } from 'types'
|
||||
import { Button, ResizableHandle, ResizablePanel, ResizablePanelGroup, cn } from 'ui'
|
||||
import {
|
||||
Button,
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
cn,
|
||||
copyToClipboard,
|
||||
} from 'ui'
|
||||
import AuthColumnRenderer from './LogColumnRenderers/AuthColumnRenderer'
|
||||
import DatabaseApiColumnRender from './LogColumnRenderers/DatabaseApiColumnRender'
|
||||
import DatabasePostgresColumnRender from './LogColumnRenderers/DatabasePostgresColumnRender'
|
||||
|
||||
@@ -7,10 +7,16 @@ import DatePicker from 'react-datepicker'
|
||||
import { Label } from '@ui/components/shadcn/ui/label'
|
||||
import { RadioGroup, RadioGroupItem } from '@ui/components/shadcn/ui/radio-group'
|
||||
import TimeSplitInput from 'components/ui/DatePicker/TimeSplitInput'
|
||||
import { Button, PopoverContent_Shadcn_, PopoverTrigger_Shadcn_, Popover_Shadcn_, cn } from 'ui'
|
||||
import {
|
||||
Button,
|
||||
PopoverContent_Shadcn_,
|
||||
PopoverTrigger_Shadcn_,
|
||||
Popover_Shadcn_,
|
||||
cn,
|
||||
copyToClipboard,
|
||||
} from 'ui'
|
||||
import { LOGS_LARGE_DATE_RANGE_DAYS_THRESHOLD } from './Logs.constants'
|
||||
import type { DatetimeHelper } from './Logs.types'
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
|
||||
export type DatePickerValue = {
|
||||
to: string
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import dayjs from 'dayjs'
|
||||
import { BookOpen, Check, ChevronDown, Clipboard, ExternalLink, X } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { ReactNode, useState } from 'react'
|
||||
|
||||
import { IS_PLATFORM } from 'common'
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
import dayjs from 'dayjs'
|
||||
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
|
||||
import { useFlag } from 'hooks/ui/useFlag'
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
import { BookOpen, Check, ChevronDown, Clipboard, ExternalLink, X } from 'lucide-react'
|
||||
import { logConstants } from 'shared-data'
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
copyToClipboard,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
|
||||
@@ -27,11 +27,11 @@ import { isTableLike } from 'data/table-editor/table-editor-types'
|
||||
import { fetchAllTableRows } from 'data/table-rows/table-rows-query'
|
||||
import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState'
|
||||
import { formatSql } from 'lib/formatSql'
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
import { useTableEditorStateSnapshot } from 'state/table-editor'
|
||||
import { createTabId, useTabsStateSnapshot } from 'state/tabs'
|
||||
import {
|
||||
cn,
|
||||
copyToClipboard,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { downloadBucketObject } from 'data/storage/bucket-object-download-mutation'
|
||||
import { StorageObject } from 'data/storage/bucket-objects-list-mutation'
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
import { toast } from 'sonner'
|
||||
import { SONNER_DEFAULT_DURATION } from 'ui'
|
||||
import { SONNER_DEFAULT_DURATION, copyToClipboard } from 'ui'
|
||||
import { STORAGE_ROW_STATUS, STORAGE_ROW_TYPES } from '../Storage.constants'
|
||||
import { StorageItem, StorageItemMetadata, StorageItemWithColumn } from '../Storage.types'
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import { toast } from 'sonner'
|
||||
|
||||
import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query'
|
||||
import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query'
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
import { useStorageExplorerStateSnapshot } from 'state/storage-explorer'
|
||||
import { copyToClipboard } from 'ui'
|
||||
import { URL_EXPIRY_DURATION } from '../Storage.constants'
|
||||
import { fetchFileUrl } from './useFetchFileUrlQuery'
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Check, Clipboard } from 'lucide-react'
|
||||
import React, { forwardRef, useEffect, useState } from 'react'
|
||||
import { ComponentProps, forwardRef, useEffect, useState } from 'react'
|
||||
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
import { Button, cn } from 'ui'
|
||||
import { Button, cn, copyToClipboard } from 'ui'
|
||||
|
||||
type CopyButtonBaseProps = {
|
||||
iconOnly?: boolean
|
||||
@@ -22,7 +21,7 @@ type CopyButtonWithAsyncText = CopyButtonBaseProps & {
|
||||
}
|
||||
|
||||
export type CopyButtonProps = (CopyButtonWithText | CopyButtonWithAsyncText) &
|
||||
React.ComponentProps<typeof Button>
|
||||
ComponentProps<typeof Button>
|
||||
|
||||
const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(
|
||||
(
|
||||
@@ -52,7 +51,7 @@ const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(
|
||||
onClick={async (e) => {
|
||||
const textToCopy = asyncText ? await asyncText() : text
|
||||
setShowCopied(true)
|
||||
await copyToClipboard(textToCopy!)
|
||||
copyToClipboard(textToCopy)
|
||||
onClick?.(e)
|
||||
}}
|
||||
{...props}
|
||||
|
||||
@@ -5,9 +5,9 @@ import Papa from 'papaparse'
|
||||
import { useMemo } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
import {
|
||||
Button,
|
||||
copyToClipboard,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
|
||||
@@ -4,9 +4,16 @@ import { useState } from 'react'
|
||||
|
||||
import { LOGS_EXPLORER_DOCS_URL } from 'components/interfaces/Settings/Logs/Logs.constants'
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
import { logConstants } from 'shared-data'
|
||||
import { Button, SidePanel, Tabs, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
import {
|
||||
Button,
|
||||
SidePanel,
|
||||
Tabs,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
copyToClipboard,
|
||||
} from 'ui'
|
||||
import { DocsButton } from '../DocsButton'
|
||||
|
||||
export interface LogsExplorerHeaderProps {
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
tryParseJson,
|
||||
minifyJSON,
|
||||
prettifyJSON,
|
||||
removeJSONTrailingComma,
|
||||
timeout,
|
||||
getURL,
|
||||
makeRandomString,
|
||||
pluckObjectFields,
|
||||
tryParseInt,
|
||||
propsAreEqual,
|
||||
formatBytes,
|
||||
snakeToCamel,
|
||||
copyToClipboard,
|
||||
detectBrowser,
|
||||
detectOS,
|
||||
pluralize,
|
||||
isValidHttpUrl,
|
||||
removeCommentsFromSql,
|
||||
getSemanticVersion,
|
||||
formatBytes,
|
||||
formatCurrency,
|
||||
getDatabaseMajorVersion,
|
||||
getDistanceLatLonKM,
|
||||
formatCurrency,
|
||||
getSemanticVersion,
|
||||
getURL,
|
||||
isValidHttpUrl,
|
||||
makeRandomString,
|
||||
minifyJSON,
|
||||
pluckObjectFields,
|
||||
pluralize,
|
||||
prettifyJSON,
|
||||
propsAreEqual,
|
||||
removeCommentsFromSql,
|
||||
removeJSONTrailingComma,
|
||||
snakeToCamel,
|
||||
timeout,
|
||||
tryParseInt,
|
||||
tryParseJson,
|
||||
} from './helpers'
|
||||
|
||||
import { copyToClipboard } from 'ui'
|
||||
|
||||
describe('tryParseJson', () => {
|
||||
it('should return the parsed JSON', () => {
|
||||
const result = tryParseJson('{"test": "test"}')
|
||||
@@ -170,6 +171,9 @@ describe('copyToClipboard', () => {
|
||||
},
|
||||
})
|
||||
|
||||
// CopyToClipboard uses setTimeout to call the callback
|
||||
vi.useFakeTimers()
|
||||
|
||||
// If ClipboardItem is used
|
||||
vi.stubGlobal('ClipboardItem', function (items: any) {
|
||||
return items
|
||||
@@ -181,10 +185,12 @@ describe('copyToClipboard', () => {
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('uses clipboard.write if available', async () => {
|
||||
await copyToClipboard('hello')
|
||||
vi.runAllTimers()
|
||||
expect(writeMock).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { noop } from 'lodash'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
export { default as passwordStrength } from './password-strength'
|
||||
export { default as uuidv4 } from './uuid'
|
||||
|
||||
@@ -146,42 +143,6 @@ export const snakeToCamel = (str: string) =>
|
||||
group.toUpperCase().replace('-', '').replace('_', '')
|
||||
)
|
||||
|
||||
/**
|
||||
* Copy text content (string or Promise<string>) into Clipboard. Safari doesn't support write text into clipboard async,
|
||||
* so if you need to load text content async before coping, please use Promise<string> for the 1st arg.
|
||||
*
|
||||
* IF YOU NEED TO CHANGE THIS FUNCTION, PLEASE TEST IT IN SAFARI with a promised string. Expiring URL to a file in a
|
||||
* private bucket will do.
|
||||
*
|
||||
* Copied code from https://wolfgangrittner.dev/how-to-use-clipboard-api-in-firefox/
|
||||
*/
|
||||
export const copyToClipboard = async (str: string | Promise<string>, callback = noop) => {
|
||||
const focused = window.document.hasFocus()
|
||||
if (focused) {
|
||||
if (typeof ClipboardItem && navigator.clipboard?.write) {
|
||||
// NOTE: Safari locks down the clipboard API to only work when triggered
|
||||
// by a direct user interaction. You can't use it async in a promise.
|
||||
// But! You can wrap the promise in a ClipboardItem, and give that to
|
||||
// the clipboard API.
|
||||
// Found this on https://developer.apple.com/forums/thread/691873
|
||||
const text = new ClipboardItem({
|
||||
'text/plain': Promise.resolve(str).then((text) => new Blob([text], { type: 'text/plain' })),
|
||||
})
|
||||
navigator.clipboard.write([text]).then(callback)
|
||||
} else {
|
||||
// NOTE: Firefox has support for ClipboardItem and navigator.clipboard.write,
|
||||
// but those are behind `dom.events.asyncClipboard.clipboardItem` preference.
|
||||
// Good news is that other than Safari, Firefox does not care about
|
||||
// Clipboard API being used async in a Promise.
|
||||
Promise.resolve(str)
|
||||
.then((text) => navigator.clipboard?.writeText(text))
|
||||
.then(callback)
|
||||
}
|
||||
} else {
|
||||
toast.error('Unable to copy to clipboard')
|
||||
}
|
||||
}
|
||||
|
||||
export const detectBrowser = () => {
|
||||
if (!navigator) return undefined
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import SVG from 'react-inlinesvg'
|
||||
|
||||
import { AIAssistantOption } from 'components/interfaces/Support/AIAssistantOption'
|
||||
import Success from 'components/interfaces/Support/Success'
|
||||
import { SupportFormV2 } from 'components/interfaces/Support/SupportFormV2'
|
||||
import AppLayout from 'components/layouts/AppLayout/AppLayout'
|
||||
@@ -15,8 +16,7 @@ import { withAuth } from 'hooks/misc/withAuth'
|
||||
import { BASE_PATH } from 'lib/constants'
|
||||
import { toast } from 'sonner'
|
||||
import { NextPageWithLayout } from 'types'
|
||||
import { Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
import { AIAssistantOption } from 'components/interfaces/Support/AIAssistantOption'
|
||||
import { Button, copyToClipboard, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
|
||||
const SupportPage: NextPageWithLayout = () => {
|
||||
const [sentCategory, setSentCategory] = useState<string>()
|
||||
@@ -118,9 +118,7 @@ const SupportPage: NextPageWithLayout = () => {
|
||||
type="text"
|
||||
text="support@supabase.com"
|
||||
iconOnly
|
||||
onClick={() => {
|
||||
toast.success('Copied to clipboard')
|
||||
}}
|
||||
onClick={() => toast.success('Copied to clipboard')}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
@@ -139,7 +137,7 @@ const SupportPage: NextPageWithLayout = () => {
|
||||
<li key={project.id} className="cursor-default">
|
||||
<button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(project.ref)
|
||||
copyToClipboard(project.ref)
|
||||
toast.success('Copied to clipboard')
|
||||
}}
|
||||
className="w-full group py-1.5 px-2 gap-x-1 text-foreground hover:bg-muted grid grid-cols-2 text-left rounded-sm"
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Check, Copy } from 'lucide-react'
|
||||
import {
|
||||
Button,
|
||||
cn,
|
||||
copyToClipboard,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
@@ -26,7 +27,7 @@ export async function copyToClipboardWithMeta(
|
||||
value: string
|
||||
// event?: Event
|
||||
) {
|
||||
navigator.clipboard.writeText(value)
|
||||
copyToClipboard(value)
|
||||
if (event) {
|
||||
// trackEvent(event)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import React, { Fragment, MouseEvent, ReactNode, useRef, useState } from 'react'
|
||||
import { CheckIcon, ClipboardIcon } from '@heroicons/react/outline'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Fragment, MouseEvent, ReactNode, useRef, useState } from 'react'
|
||||
import { useClickAway, useKey } from 'react-use'
|
||||
import { CheckIcon, ClipboardIcon } from '@heroicons/react/outline'
|
||||
import { cn, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from 'ui'
|
||||
import {
|
||||
cn,
|
||||
copyToClipboard,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from 'ui'
|
||||
|
||||
import * as supabaseLogoWordmarkDark from 'common/assets/images/supabase-logo-wordmark--dark.png'
|
||||
import * as supabaseLogoWordmarkLight from 'common/assets/images/supabase-logo-wordmark--light.png'
|
||||
@@ -62,7 +69,7 @@ const RightClickBrandLogo = () => {
|
||||
* Copy to clipboard logo SVG
|
||||
*/
|
||||
const handleCopyToClipboard = (menuItem: MenuItemProps) => {
|
||||
navigator.clipboard.writeText(menuItem.clipboard ?? '').then(() => {
|
||||
copyToClipboard(menuItem.clipboard ?? '', () => {
|
||||
setCopied(true)
|
||||
setTimeout(() => {
|
||||
setCopied(false)
|
||||
|
||||
@@ -6,7 +6,7 @@ import React, {
|
||||
forwardRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { Button, Input_Shadcn_, cn } from 'ui'
|
||||
import { Button, Input_Shadcn_, cn, copyToClipboard } from 'ui'
|
||||
import styleHandler from 'ui/src/lib/theme/styleHandler'
|
||||
import InputIconContainer from '../form/Layout/InputIconContainer'
|
||||
|
||||
@@ -47,20 +47,14 @@ const Input = forwardRef<
|
||||
const __styles = styleHandler('input')
|
||||
|
||||
function _onCopy(value: any) {
|
||||
navigator.clipboard.writeText(value)?.then(
|
||||
function () {
|
||||
/* clipboard successfully set */
|
||||
setCopyLabel('Copied')
|
||||
setTimeout(function () {
|
||||
setCopyLabel('Copy')
|
||||
}, 3000)
|
||||
onCopy?.()
|
||||
},
|
||||
function () {
|
||||
/* clipboard write failed */
|
||||
setCopyLabel('Failed to copy')
|
||||
}
|
||||
)
|
||||
copyToClipboard(value, () => {
|
||||
/* clipboard successfully set */
|
||||
setCopyLabel('Copied')
|
||||
setTimeout(function () {
|
||||
setCopyLabel('Copy')
|
||||
}, 3000)
|
||||
onCopy?.()
|
||||
})
|
||||
}
|
||||
|
||||
function onReveal() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use client'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from 'ui/src/components/shadcn/ui/tooltip'
|
||||
import { cn } from 'ui/src/lib/utils'
|
||||
import { cn, copyToClipboard, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
@@ -103,12 +102,12 @@ export const TimestampInfo = ({
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
navigator.clipboard.writeText(value)
|
||||
setCopied(true)
|
||||
|
||||
setTimeout(() => {
|
||||
setCopied(false)
|
||||
}, 1000)
|
||||
copyToClipboard(value, () => {
|
||||
setCopied(true)
|
||||
setTimeout(() => {
|
||||
setCopied(false)
|
||||
}, 1000)
|
||||
})
|
||||
}}
|
||||
className={cn(
|
||||
'relative cursor-pointer flex gap-y-2 gap-x-0.5 hover:bg-surface-100 px-2 py-1 group',
|
||||
|
||||
@@ -8,6 +8,7 @@ import InputErrorIcon from '../../lib/Layout/InputErrorIcon'
|
||||
import InputIconContainer from '../../lib/Layout/InputIconContainer'
|
||||
import { HIDDEN_PLACEHOLDER } from '../../lib/constants'
|
||||
import styleHandler from '../../lib/theme/styleHandler'
|
||||
import { copyToClipboard } from '../../lib/utils'
|
||||
import { cn } from '../../lib/utils/cn'
|
||||
import { Button } from '../Button'
|
||||
import { useFormContext } from '../Form/FormContext'
|
||||
@@ -115,20 +116,13 @@ function Input({
|
||||
// }, [errors, touched])
|
||||
|
||||
function _onCopy(value: any) {
|
||||
navigator.clipboard.writeText(value)?.then(
|
||||
function () {
|
||||
/* clipboard successfully set */
|
||||
setCopyLabel('Copied')
|
||||
setTimeout(function () {
|
||||
setCopyLabel('Copy')
|
||||
}, 3000)
|
||||
onCopy?.()
|
||||
},
|
||||
function () {
|
||||
/* clipboard write failed */
|
||||
setCopyLabel('Failed to copy')
|
||||
}
|
||||
)
|
||||
copyToClipboard(value, () => {
|
||||
setCopyLabel('Copied')
|
||||
setTimeout(() => {
|
||||
setCopyLabel('Copy')
|
||||
}, 3000)
|
||||
onCopy?.()
|
||||
})
|
||||
}
|
||||
|
||||
function onReveal() {
|
||||
@@ -256,20 +250,14 @@ function TextArea({
|
||||
const [copyLabel, setCopyLabel] = useState('Copy')
|
||||
|
||||
function _onCopy(value: any) {
|
||||
navigator.clipboard.writeText(value).then(
|
||||
function () {
|
||||
/* clipboard successfully set */
|
||||
setCopyLabel('Copied')
|
||||
setTimeout(function () {
|
||||
setCopyLabel('Copy')
|
||||
}, 3000)
|
||||
onCopy?.()
|
||||
},
|
||||
function () {
|
||||
/* clipboard write failed */
|
||||
setCopyLabel('Failed to copy')
|
||||
}
|
||||
)
|
||||
copyToClipboard(value, () => {
|
||||
/* clipboard successfully set */
|
||||
setCopyLabel('Copied')
|
||||
setTimeout(() => {
|
||||
setCopyLabel('Copy')
|
||||
}, 3000)
|
||||
onCopy?.()
|
||||
})
|
||||
}
|
||||
|
||||
const { formContextOnChange, values, errors, handleBlur, touched, fieldLevelValidation } =
|
||||
|
||||
@@ -22,7 +22,11 @@ export const copyToClipboard = async (str: string | Promise<string>, callback =
|
||||
const text = new ClipboardItem({
|
||||
'text/plain': Promise.resolve(str).then((text) => new Blob([text], { type: 'text/plain' })),
|
||||
})
|
||||
navigator.clipboard.write([text]).then(callback)
|
||||
// [Joshen] Adding a timeout based on this comment here about a workaround
|
||||
// https://stackoverflow.com/questions/62327358/javascript-clipboard-api-safari-ios-notallowederror-message
|
||||
setTimeout(() => {
|
||||
navigator.clipboard.write([text]).then(callback)
|
||||
}, 0)
|
||||
} else {
|
||||
// NOTE: Firefox has support for ClipboardItem and navigator.clipboard.write,
|
||||
// but those are behind `dom.events.asyncClipboard.clipboardItem` preference.
|
||||
|
||||
Reference in New Issue
Block a user