diff --git a/package.json b/package.json
index eeaabe15e..f765555cd 100644
--- a/package.json
+++ b/package.json
@@ -103,8 +103,6 @@
"ramda-adjunct": "^3.3.0",
"react": "^18.3.1",
"react-compound-slider": "^3.4.0",
- "react-copy-html-to-clipboard": "^6.0.4",
- "react-copy-to-clipboard": "^5.1.0",
"react-device-detect": "^2.2.3",
"react-error-boundary": "^4.0.4",
"react-google-recaptcha-v3": "^1.10.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c20e3f16c..8e3aad65a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -206,12 +206,6 @@ importers:
react-compound-slider:
specifier: ^3.4.0
version: 3.4.0(react@18.3.1)
- react-copy-html-to-clipboard:
- specifier: ^6.0.4
- version: 6.0.4(react@18.3.1)
- react-copy-to-clipboard:
- specifier: ^5.1.0
- version: 5.1.0(react@18.3.1)
react-device-detect:
specifier: ^2.2.3
version: 2.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -3266,9 +3260,6 @@ packages:
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
engines: {node: '>=12.13'}
- copy-html-to-clipboard@4.0.1:
- resolution: {integrity: sha512-uvEr7smwMQReydH7XzSgpxM1SywZfxS+1NLhlSvzLIGz8esPbY248y1Tg7LTYsr/yVP/1kUyf8Ypgd9ljJJkBw==}
-
copy-to-clipboard@3.3.3:
resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==}
@@ -5307,16 +5298,6 @@ packages:
peerDependencies:
react: '>=16.9'
- react-copy-html-to-clipboard@6.0.4:
- resolution: {integrity: sha512-lUV84N9OiL3exfM+lK7XEN30xgK7XLRqAqcr+8m9ZtQ82xGUjT7fCWwvOS88YH/TaANqokWWZRtrgSMK1m8huw==}
- peerDependencies:
- react: ^15.3.0
-
- react-copy-to-clipboard@5.1.0:
- resolution: {integrity: sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==}
- peerDependencies:
- react: ^15.3.0 || 16 || 17 || 18
-
react-device-detect@2.2.3:
resolution: {integrity: sha512-buYY3qrCnQVlIFHrC5UcUoAj7iANs/+srdkwsnNjI7anr3Tt7UY6MqNxtMLlr0tMBied0O49UZVK8XKs3ZIiPw==}
peerDependencies:
@@ -9591,10 +9572,6 @@ snapshots:
dependencies:
is-what: 4.1.16
- copy-html-to-clipboard@4.0.1:
- dependencies:
- toggle-selection: 1.0.6
-
copy-to-clipboard@3.3.3:
dependencies:
toggle-selection: 1.0.6
@@ -11971,18 +11948,6 @@ snapshots:
react: 18.3.1
warning: 4.0.3
- react-copy-html-to-clipboard@6.0.4(react@18.3.1):
- dependencies:
- copy-html-to-clipboard: 4.0.1
- prop-types: 15.8.1
- react: 18.3.1
-
- react-copy-to-clipboard@5.1.0(react@18.3.1):
- dependencies:
- copy-to-clipboard: 3.3.3
- prop-types: 15.8.1
- react: 18.3.1
-
react-device-detect@2.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
react: 18.3.1
diff --git a/src/api/export/types.ts b/src/api/export/types.ts
index 0480dcf37..9950bc5ea 100644
--- a/src/api/export/types.ts
+++ b/src/api/export/types.ts
@@ -30,6 +30,20 @@ export enum ExportApiFormatKey {
votable = 'votable',
}
+export const MostUsedExportFormats = [
+ ExportApiFormatKey.bibtex,
+ ExportApiFormatKey.agu,
+ ExportApiFormatKey.ris,
+ ExportApiFormatKey.aastex,
+ ExportApiFormatKey.endnote,
+ ExportApiFormatKey.ieee,
+ ExportApiFormatKey.mnras,
+ ExportApiFormatKey.icarus,
+ ExportApiFormatKey.soph,
+ ExportApiFormatKey.procite,
+ ExportApiFormatKey.refworks,
+];
+
export enum ExportApiErrorKey {
NO_RESULT = 'no result from solr',
QUERY_ISSUE = 'unable to query solr',
@@ -61,10 +75,16 @@ export interface IExportApiParams {
keyformat?: [string];
journalformat?: [ExportApiJournalFormat];
maxauthor?: [number];
+ outputformat?: number;
}
export interface IExportApiResponse {
- export: string;
- msg: string;
- error?: ExportApiErrorKey;
+ docs?: {
+ bibcode: string;
+ reference: string;
+ }[];
+ numFound?: number;
+ export?: string;
+ msg?: string;
+ error?: string;
}
diff --git a/src/components/AbstractDetails/AbstractCitationModal.tsx b/src/components/AbstractDetails/AbstractCitationModal.tsx
index ca03e40bd..ce4bd0f0b 100644
--- a/src/components/AbstractDetails/AbstractCitationModal.tsx
+++ b/src/components/AbstractDetails/AbstractCitationModal.tsx
@@ -1,5 +1,5 @@
import { useGetExportCitation } from '@/api/export/export';
-import { ExportApiFormatKey } from '@/api/export/types';
+import { ExportApiFormatKey, MostUsedExportFormats } from '@/api/export/types';
import { useExportFormats } from '@/lib/useExportFormats';
import { useSettings } from '@/lib/useSettings';
import { parseAPIError } from '@/utils/common/parseAPIError';
@@ -16,11 +16,13 @@ import {
AlertTitle,
AlertDescription,
Flex,
+ Textarea,
} from '@chakra-ui/react';
import { useState } from 'react';
import { SimpleCopyButton } from '../CopyButton';
import { LoadingMessage } from '../Feedbacks';
import { Select } from '../Select';
+import { SimpleLink } from '../SimpleLink';
export const AbstractCitationModal = ({
isOpen,
@@ -35,7 +37,7 @@ export const AbstractCitationModal = ({
const { formatOptions, getFormatOptionById } = useExportFormats();
- const options = formatOptions.filter((o) => o.type === 'HTML');
+ const options = formatOptions.filter((o) => MostUsedExportFormats.includes(o.id));
const defaultOption = settings.defaultCitationFormat
? getFormatOptionById(settings.defaultCitationFormat)
@@ -68,6 +70,11 @@ export const AbstractCitationModal = ({
onChange={(o) => setSelectedOption(o)}
stylesTheme="default.sm"
/>
+
+
+ Advanced options
+
+
{isLoading ? (
@@ -79,10 +86,21 @@ export const AbstractCitationModal = ({
) : (
<>
-
-
-
-
+ {selectedOption.type === 'HTML' ? (
+ <>
+
+
+
+
+ >
+ ) : (
+ <>
+
+
+
+
+ >
+ )}
>
)}
diff --git a/src/components/CopyButton/CopyButton.tsx b/src/components/CopyButton/CopyButton.tsx
index c630855bb..aeec29640 100644
--- a/src/components/CopyButton/CopyButton.tsx
+++ b/src/components/CopyButton/CopyButton.tsx
@@ -1,18 +1,38 @@
import { CheckIcon, CopyIcon } from '@chakra-ui/icons';
-import { Button, ButtonProps, IconButton, MenuItem, MenuItemProps, useClipboard } from '@chakra-ui/react';
+import { Button, ButtonProps, IconButton, MenuItem, MenuItemProps } from '@chakra-ui/react';
import { ReactElement, useEffect, useState } from 'react';
-import CopyToClipboard from 'react-copy-html-to-clipboard';
-export interface ICopyButtonProps extends ButtonProps {
+interface ICopyProps {
text: string;
onCopyComplete?: () => void;
timeout?: number;
asHtml?: boolean;
+}
+
+export interface ICopyButtonProps extends ICopyProps, ButtonProps {
iconPos?: 'left' | 'right';
}
const DEFAULT_TIMEOUT = 1500;
+const copyPlainTextToClipboard = (text: string) => {
+ navigator.clipboard.writeText(text);
+};
+
+const copyHtmlToClipboard = (html: string) => {
+ const blobHtml = new Blob([html], { type: 'text/html' });
+ const blobText = new Blob([html.replace(/<[^>]+>/g, '')], {
+ type: 'text/plain',
+ });
+
+ const item = new ClipboardItem({
+ 'text/html': blobHtml,
+ 'text/plain': blobText,
+ });
+
+ navigator.clipboard.write([item]);
+};
+
export const SimpleCopyButton = (props: ICopyButtonProps): ReactElement => {
const { text, onCopyComplete, timeout = DEFAULT_TIMEOUT, asHtml = false, ...rest } = props;
@@ -28,13 +48,18 @@ export const SimpleCopyButton = (props: ICopyButtonProps): ReactElement => {
}
}, [hasCopied]);
- const handleCopied = () => {
+ const handleCopied = (asHtml: boolean) => {
+ if (asHtml) {
+ copyHtmlToClipboard(text);
+ } else {
+ copyPlainTextToClipboard(text);
+ }
setHasCopied(true);
onCopyComplete?.();
};
return (
-
+ <>
{hasCopied ? (
} variant="link" color="green.500" {...rest} />
) : (
@@ -42,11 +67,11 @@ export const SimpleCopyButton = (props: ICopyButtonProps): ReactElement => {
icon={}
variant="link"
aria-label="copy to clipboard"
- onClick={handleCopied}
+ onClick={() => handleCopied(asHtml)}
{...rest}
/>
)}
-
+ >
);
};
@@ -64,35 +89,56 @@ export const LabeledCopyButton = (props: ICopyButtonProps & { label: string }):
}
}, [hasCopied]);
- const handleCopied = () => {
+ const handleCopied = (asHtml: boolean) => {
+ if (asHtml) {
+ copyHtmlToClipboard(text);
+ } else {
+ copyPlainTextToClipboard(text);
+ }
setHasCopied(true);
onCopyComplete?.();
};
return (
-
- } : { rightIcon: })}
- {...rest}
- >
- {hasCopied ? 'Copied to clipboard!' : label}
-
-
+
);
};
-export const CopyMenuItem = (props: MenuItemProps & { label: string; text: string }): ReactElement => {
- const { label, text, ...rest } = props;
- const { onCopy, setValue } = useClipboard(text);
+export const CopyMenuItem = (props: MenuItemProps & ICopyProps & { label: string }): ReactElement => {
+ const { label, text, timeout = DEFAULT_TIMEOUT, onCopyComplete, asHtml = false, ...rest } = props;
+
+ const [hasCopied, setHasCopied] = useState(false);
useEffect(() => {
- setValue(text);
- }, [text]);
+ if (hasCopied) {
+ const timeoutId = setTimeout(() => {
+ setHasCopied(false);
+ }, timeout);
+
+ return () => clearTimeout(timeoutId);
+ }
+ }, [hasCopied]);
+
+ const handleCopied = (asHtml: boolean) => {
+ if (asHtml) {
+ copyHtmlToClipboard(text);
+ } else {
+ copyPlainTextToClipboard(text);
+ }
+ setHasCopied(true);
+ onCopyComplete?.();
+ };
return (
-