diff --git a/package-lock.json b/package-lock.json index 92a5faa..f5f3af9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@reduxjs/toolkit": "^1.8.3", "@types/node": "^16.11.47", "@types/react": "^18.0.15", + "@types/react-copy-to-clipboard": "^5.0.4", "@types/react-dom": "^18.0.6", "@uiw/react-md-editor": "^3.18.1", "axios": "^0.27.2", @@ -25,6 +26,7 @@ "jwt-decode": "^3.1.2", "lodash": "^4.17.21", "react": "^18.2.0", + "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18.2.0", "react-hotkeys": "^2.0.0", "react-i18next": "^11.18.3", @@ -11479,6 +11481,14 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-copy-to-clipboard": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz", + "integrity": "sha512-otTJsJpofYAeaIeOwV5xBUGpo6exXG2HX7X4nseToCB2VgPEBxGBHCm/FecZ676doNR7HCSTVtmohxfG2b3/yQ==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-dom": { "version": "18.0.6", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", @@ -15021,6 +15031,14 @@ "node": ">=0.10.0" } }, + "node_modules/copy-to-clipboard": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz", + "integrity": "sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg==", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, "node_modules/core-js": { "version": "3.24.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz", @@ -30025,6 +30043,18 @@ "node": ">=14" } }, + "node_modules/react-copy-to-clipboard": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", + "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==", + "dependencies": { + "copy-to-clipboard": "^3.3.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^15.3.0 || 16 || 17 || 18" + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -37574,6 +37604,11 @@ "node": ">=8.0" } }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -48448,6 +48483,14 @@ "csstype": "^3.0.2" } }, + "@types/react-copy-to-clipboard": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz", + "integrity": "sha512-otTJsJpofYAeaIeOwV5xBUGpo6exXG2HX7X4nseToCB2VgPEBxGBHCm/FecZ676doNR7HCSTVtmohxfG2b3/yQ==", + "requires": { + "@types/react": "*" + } + }, "@types/react-dom": { "version": "18.0.6", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", @@ -51233,6 +51276,14 @@ "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", "dev": true }, + "copy-to-clipboard": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz", + "integrity": "sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg==", + "requires": { + "toggle-selection": "^1.0.6" + } + }, "core-js": { "version": "3.24.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz", @@ -62246,6 +62297,15 @@ "whatwg-fetch": "^3.6.2" } }, + "react-copy-to-clipboard": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", + "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==", + "requires": { + "copy-to-clipboard": "^3.3.1", + "prop-types": "^15.8.1" + } + }, "react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -67797,6 +67857,11 @@ "is-number": "^7.0.0" } }, + "toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", diff --git a/package.json b/package.json index 5d32e57..9561c6c 100755 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@reduxjs/toolkit": "^1.8.3", "@types/node": "^16.11.47", "@types/react": "^18.0.15", + "@types/react-copy-to-clipboard": "^5.0.4", "@types/react-dom": "^18.0.6", "@uiw/react-md-editor": "^3.18.1", "axios": "^0.27.2", @@ -20,6 +21,7 @@ "jwt-decode": "^3.1.2", "lodash": "^4.17.21", "react": "^18.2.0", + "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18.2.0", "react-hotkeys": "^2.0.0", "react-i18next": "^11.18.3", diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index 961208e..38e40e0 100755 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -62,7 +62,15 @@ }, "articlePage": { "abstract": "Введение", - "keywords": "Ключевые слова" + "keywords": "Ключевые слова", + "interactionButtons":{ + "abstract": "Развернуть", + "readFile": "Читать", + "download" : "Скачать", + "share" : "Поделиться", + "cite" : "Цитировать", + "copied": "Скопировано" + } }, "navbar": { "createNew": "Создать статью", @@ -124,5 +132,4 @@ "totalResults":"Всего найдено", "nothingFound": "Ничего не найдено" } - } diff --git a/src/components/Article/ArticleParts/InteractionButtons/ArticleInteractionButtons.tsx b/src/components/Article/ArticleParts/InteractionButtons/ArticleInteractionButtons.tsx index 6301918..1de4324 100755 --- a/src/components/Article/ArticleParts/InteractionButtons/ArticleInteractionButtons.tsx +++ b/src/components/Article/ArticleParts/InteractionButtons/ArticleInteractionButtons.tsx @@ -13,14 +13,10 @@ import { import classNames from "classnames"; import { Transition } from "@headlessui/react"; import Link from "components/typography/Link"; +import { useTranslation } from "react-i18next"; +import { ShareButton } from "./ArticleShareButton"; const interactionButtonsStore = [ - { - icon: , - title: "Read file", - buttonEmphasis: "high", - iconClassName: "h-6 fill-white stroke-white", - }, { icon: , title: "Download", @@ -47,18 +43,22 @@ type ArticleButtonProps = { children?: React.ReactNode; className?: string; emphasis?: "high" | "low"; - articleID?: string, + articleID?: string; } & Omit, "">; export function ArticleInteractionButtons({ isAbstractOpen = false, children, - openAbstract = () => { }, + openAbstract = () => {}, className, articleID, emphasis = "high", //to change displaying of component ...props }: ArticleButtonProps) { + const [t, i18next] = useTranslation("translation", { + keyPrefix: "articlePage.interactionButtons", + }); + /* ----------------------------- Abstract Button ---------------------------- */ const abstractButton = ( ); - const fileInteractionButtons = interactionButtonsStore.map((button) => { - return ( - button.title === 'Read file' ? - - - - : - - ); - }); + + /* ---------------------------- Read file button ---------------------------- */ + const readFileButton = ( + +
+ + {emphasis === "high" && {t("readFile")}} +
+ + ); + /* ----------------------------- Download button ---------------------------- */ + const downLoadButton = ( + + ); + + /* ------------------------------- Cite button ------------------------------ */ + + const citeButton = ( + + ); return ( -
- {emphasis === "low" && !children ? abstractButton : null} - {children ? children : fileInteractionButtons} +
+ {emphasis != "high" && abstractButton} + {readFileButton} + {downLoadButton} + {citeButton} +
); } diff --git a/src/components/Article/ArticleParts/InteractionButtons/ArticleShareButton.tsx b/src/components/Article/ArticleParts/InteractionButtons/ArticleShareButton.tsx new file mode 100644 index 0000000..4a5c734 --- /dev/null +++ b/src/components/Article/ArticleParts/InteractionButtons/ArticleShareButton.tsx @@ -0,0 +1,87 @@ +import { BASE_URL } from "core/httpClient"; +import { useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Button } from "../../../Button/Button"; +import { SVGCopy, SVGShare, SVGXmark } from "../../../icons"; +import Typography from "../../../typography/Typography"; +import { CopyToClipboard } from "react-copy-to-clipboard"; +import { Popover } from "@headlessui/react"; + +type Props = { + emphasis?: "high" | "low"; + linktoCopy?: string; +}; + +export function ShareButton({ emphasis, linktoCopy }: Props) { + const [t, i18next] = useTranslation("translation", { + keyPrefix: "articlePage.interactionButtons", + }); + const [copied, setCopied] = useState(false); + + const copyValue = + BASE_URL != undefined && linktoCopy != undefined + ? BASE_URL + linktoCopy + : t("searchResults.nothingFound"); + + function onCopy() { + setCopied(true); + setTimeout(() => { + setCopied(false); + }, 1500); + }; + + const handleFocus = (event: any) => event.target.select(); + + return ( + + + + + + +
+ +
+ + + + + {copied && ( +
+ {t("copied")} +
+ )} +
+ + + +
+
+
+ ); +} diff --git a/src/components/ArticleSearchResult.tsx b/src/components/ArticleSearchResult.tsx index c569005..a1571be 100644 --- a/src/components/ArticleSearchResult.tsx +++ b/src/components/ArticleSearchResult.tsx @@ -47,10 +47,11 @@ export const ArticleSearchResult = ({ searchItem }: Props) => { {searchItem.tags} ); - return
{link}
; + return link; } diff --git a/src/core/httpClient.ts b/src/core/httpClient.ts index 3b4d76b..83f7924 100755 --- a/src/core/httpClient.ts +++ b/src/core/httpClient.ts @@ -1,5 +1,5 @@ import axios from "axios"; -const BASE_URL = process.env.REACT_APP_INTEGRATOR_URL; +export const BASE_URL = process.env.REACT_APP_INTEGRATOR_URL; export const GRAPHQL_URL = process.env.REACT_APP_GRAPHQL_URL ?? ""; const instance = axios.create({ diff --git a/src/pages/SearchResultsPage.tsx b/src/pages/SearchResultsPage.tsx index e6d3f35..00f7833 100644 --- a/src/pages/SearchResultsPage.tsx +++ b/src/pages/SearchResultsPage.tsx @@ -3,9 +3,6 @@ import BaseLayout from "components/BaseLayout"; import { SearchSection } from "components/SearchSection"; import { ColumnLayout } from "components/layouts/ThreeColumn/ColumnLayout"; import { SearchResultSection } from "components/SearchResultsSection"; -import { useSearchStoreImplementation } from "searchResults/data/searchStoreImplementation"; -import { useSearchViewModel } from "searchResults/controller/searchResultsViewModel"; -import { Loader } from "components/Loader/Loader"; import Fiter from "components/Filters/Filter"; export const SearchResultsPage = () => { @@ -14,7 +11,7 @@ export const SearchResultsPage = () => { -
+
diff --git a/src/searchResults/data/searchService.ts b/src/searchResults/data/searchService.ts index dbf0cf6..0ddc137 100755 --- a/src/searchResults/data/searchService.ts +++ b/src/searchResults/data/searchService.ts @@ -10,7 +10,7 @@ const searchEndpoint = "/papers/search"; async function search(request: string): Promise { try { const response = await integratorApiClient.get( - // searchEndpoint + `?query=` + request + `&limit=10&offset=0` + // searchEndpoint + `?query=` + request "https://run.mocky.io/v3/ea705665-2479-4039-8b81-412e011fc145" ); const dto = response.data;