Merge pull request 'version/0.5' (#3) from version/0.5 into master
Reviewed-on: #3
This commit is contained in:
commit
8b6322d4cc
47
README.md
47
README.md
|
@ -10,20 +10,6 @@ Il progetto è partito come un fork di [Book Searcher](https://github.com/book-s
|
||||||
|
|
||||||
L'obiettivo è trasformarlo in una piattaforma partecipativa: gli utenti avranno la possibilità di richiedere l'aggiunta di libri, oltre a fare ricerche.
|
L'obiettivo è trasformarlo in una piattaforma partecipativa: gli utenti avranno la possibilità di richiedere l'aggiunta di libri, oltre a fare ricerche.
|
||||||
|
|
||||||
## Copyright e pirateria
|
|
||||||
|
|
||||||
Nessun materiale protetto da copyright è ospitato su questo repo.
|
|
||||||
|
|
||||||
Nessun link a materiale protetto da copyright è ospitato su questo repo.
|
|
||||||
|
|
||||||
Il motore fa ricerche su degli indici, e fornisce dei risultati.
|
|
||||||
|
|
||||||
Non abbiamo nulla contro la pirateria, ma vogliamo evitare di trovarci in situazioni spiacevoli.
|
|
||||||
|
|
||||||
Non riusciamo a capire in quali occasioni la condivisione di link esterni a materiale protetto da copyright sia reato e in quali no. Quindi, per tutelarci, abbiamo disabilitato la generazione automatica di link IPFS alle risorse. Tuttavia è possibile riabilitarla modificando il codice sorgente (spiegato sotto).
|
|
||||||
|
|
||||||
Viene fornita solo un'informazione parziale. Sta all'utente mettere insieme i pezzi.
|
|
||||||
|
|
||||||
## Installazione rapida
|
## Installazione rapida
|
||||||
|
|
||||||
E' possibile buildare il progetto via Docker. Occorre avere prima installato e configurato sia Docker che il tool `docker-compose`.
|
E' possibile buildare il progetto via Docker. Occorre avere prima installato e configurato sia Docker che il tool `docker-compose`.
|
||||||
|
@ -48,39 +34,6 @@ npm run build
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## Riabilitare la generazione dei link IPFS
|
|
||||||
|
|
||||||
Per riabilitare i bottoni/link ai Gateway IPFS, aprire il file `frontend/src/components/BooksDetailsCard.tsx` e de-commentare le seguenti linee di codice:
|
|
||||||
|
|
||||||
(da riga 22)
|
|
||||||
|
|
||||||
```
|
|
||||||
// const downloadLinkFromIPFS = (gateway: string, book: Book) => {
|
|
||||||
// return (
|
|
||||||
// `https://${gateway}/ipfs/${book.ipfs_cid}?filename=` +
|
|
||||||
// encodeURIComponent(`${book.title}_${book.author}.${book.extension}`)
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
```
|
|
||||||
|
|
||||||
(da riga 91)
|
|
||||||
|
|
||||||
```
|
|
||||||
{/* <SimpleGrid columns={{ sm: 2, md: 3, lg: 4, xl: 5 }} spacing={{ base: 2, md: 4 }}>
|
|
||||||
{ipfsGateways.map((gateway) => (
|
|
||||||
<Button
|
|
||||||
as={ExternalLink}
|
|
||||||
href={downloadLinkFromIPFS(gateway, row.original)}
|
|
||||||
key={gateway}
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
{gateway}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
|
|
||||||
</SimpleGrid> */}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Licenza
|
## Licenza
|
||||||
|
|
||||||
Book Searcher è rilasciato sotto licenza [BSD-3-Clause](https://github.com/book-searcher-org/book-searcher/blob/master/LICENSE). Millelibri eredita la licenza da Book Searcher.
|
Book Searcher è rilasciato sotto licenza [BSD-3-Clause](https://github.com/book-searcher-org/book-searcher/blob/master/LICENSE). Millelibri eredita la licenza da Book Searcher.
|
|
@ -19,12 +19,12 @@ interface IProps {
|
||||||
|
|
||||||
const BookDetailsCard: React.FC<IProps> = (props) => {
|
const BookDetailsCard: React.FC<IProps> = (props) => {
|
||||||
|
|
||||||
// const downloadLinkFromIPFS = (gateway: string, book: Book) => {
|
const downloadLinkFromIPFS = (gateway: string, book: Book) => {
|
||||||
// return (
|
return (
|
||||||
// `https://${gateway}/ipfs/${book.ipfs_cid}?filename=` +
|
`https://${gateway}/ipfs/${book.ipfs_cid}?filename=` +
|
||||||
// encodeURIComponent(`${book.title}_${book.author}.${book.extension}`)
|
encodeURIComponent(`${book.title}_${book.author}.${book.extension}`)
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ const BookDetailsCard: React.FC<IProps> = (props) => {
|
||||||
</CardBody>
|
</CardBody>
|
||||||
<CardFooter flexDirection="column">
|
<CardFooter flexDirection="column">
|
||||||
|
|
||||||
{/* <SimpleGrid columns={{ sm: 2, md: 3, lg: 4, xl: 5 }} spacing={{ base: 2, md: 4 }}>
|
<SimpleGrid columns={{ sm: 2, md: 3, lg: 4, xl: 5 }} spacing={{ base: 2, md: 4 }}>
|
||||||
{ipfsGateways.map((gateway) => (
|
{ipfsGateways.map((gateway) => (
|
||||||
<Button
|
<Button
|
||||||
as={ExternalLink}
|
as={ExternalLink}
|
||||||
|
@ -100,9 +100,9 @@ const BookDetailsCard: React.FC<IProps> = (props) => {
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
</SimpleGrid> */}
|
</SimpleGrid>
|
||||||
|
|
||||||
<Flex><Text fontWeight={'bold'}>{t('disclaimer.nolink_warning')}</Text></Flex>
|
{/* <Flex><Text fontWeight={'bold'}>{t('disclaimer.nolink_warning')}</Text></Flex> */}
|
||||||
|
|
||||||
<Flex justify="flex-end">
|
<Flex justify="flex-end">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { Box, Button, Card, Flex, Icon, Spacer } from '@chakra-ui/react';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { IoShareSocialOutline } from 'react-icons/io5';
|
||||||
|
import MediaQuery from 'react-responsive';
|
||||||
|
import { MEDIA_QUERY_DESKTOP_STARTS, MEDIA_QUERY_MOBILE_ENDS } from '../constants/mediaquery';
|
||||||
|
|
||||||
|
export interface IProps {
|
||||||
|
show: boolean
|
||||||
|
onClick(): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const CopyToClipboardButton: React.FC<IProps> = (props) => {
|
||||||
|
|
||||||
|
const { show, onClick } = props
|
||||||
|
|
||||||
|
const [showSuccess, setShowSuccess] = useState<boolean>(false)
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
onClick()
|
||||||
|
setShowSuccess(true)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setShowSuccess(false)
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{show &&
|
||||||
|
<Box p='4' mx={{ base: 0, md: 4 }}>
|
||||||
|
<Flex justifyContent={'flex-end'} alignItems={'center'}>
|
||||||
|
<Button bgColor={showSuccess ? 'green.400' : ''} onClick={handleClick}>
|
||||||
|
{showSuccess ? t('search.copy_success') : t('search.copy_search_link')} <Icon as={IoShareSocialOutline} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Box>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CopyToClipboardButton;
|
|
@ -1,5 +1,6 @@
|
||||||
import { GridItem, Icon, SimpleGrid } from '@chakra-ui/react';
|
import { Button, GridItem, Icon, SimpleGrid, Flex, Box, Card } from '@chakra-ui/react';
|
||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import MediaQuery from 'react-responsive'
|
||||||
import {
|
import {
|
||||||
TbBook2,
|
TbBook2,
|
||||||
TbBuilding,
|
TbBuilding,
|
||||||
|
@ -11,10 +12,13 @@ import {
|
||||||
import search, { Book } from '../scripts/searcher';
|
import search, { Book } from '../scripts/searcher';
|
||||||
|
|
||||||
import { IoLanguage } from 'react-icons/io5';
|
import { IoLanguage } from 'react-icons/io5';
|
||||||
|
import { IoShareSocialOutline } from 'react-icons/io5';
|
||||||
import SearchInput from './SearchInput';
|
import SearchInput from './SearchInput';
|
||||||
import { useDebounceEffect } from 'ahooks';
|
import { useDebounceEffect } from 'ahooks';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import SearchLanguage from './SearchLanguage';
|
import SearchLanguage from './SearchLanguage';
|
||||||
|
import { MEDIA_QUERY_DESKTOP_STARTS, MEDIA_QUERY_MOBILE_ENDS } from '../constants/mediaquery';
|
||||||
|
import CopyToClipboardButton from './CopyToClipboardButton';
|
||||||
|
|
||||||
function constructQuery(parts: Record<string, string>): string {
|
function constructQuery(parts: Record<string, string>): string {
|
||||||
return Object.keys(parts)
|
return Object.keys(parts)
|
||||||
|
@ -44,6 +48,27 @@ const Search: React.FC<SearchProps> = ({ setBooks }) => {
|
||||||
const [complexQuery, setComplexQuery] = useState<string>('');
|
const [complexQuery, setComplexQuery] = useState<string>('');
|
||||||
const [showLanguageDropdown, setShowLanguageDropdown] = useState<boolean>(true)
|
const [showLanguageDropdown, setShowLanguageDropdown] = useState<boolean>(true)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const params = new Proxy(new URLSearchParams(decodeURIComponent(window.location.search)), {
|
||||||
|
//@ts-ignore
|
||||||
|
get: (searchParams, prop) => searchParams.get(prop),
|
||||||
|
});
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
params.title && setTitle(String(params.title))
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
params.author && setAuthor(String(params.author))
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
if (params.language) {
|
||||||
|
//@ts-ignore
|
||||||
|
setLanguage(String(params.language))
|
||||||
|
setShowLanguageDropdown(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handleLanguageChange = (language: string) => {
|
const handleLanguageChange = (language: string) => {
|
||||||
if (language == 'input') {
|
if (language == 'input') {
|
||||||
setShowLanguageDropdown(false)
|
setShowLanguageDropdown(false)
|
||||||
|
@ -57,6 +82,28 @@ const Search: React.FC<SearchProps> = ({ setBooks }) => {
|
||||||
setLanguage('')
|
setLanguage('')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasAnySearchBeenMade = () => {
|
||||||
|
return (
|
||||||
|
title !== ''
|
||||||
|
|| author !== ''
|
||||||
|
|| language !== ''
|
||||||
|
|| publisher !== ''
|
||||||
|
|| extension !== ''
|
||||||
|
|| isbn !== ''
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyToClipboard = () => {
|
||||||
|
const searchBase = `${window.location.host}/?`;
|
||||||
|
const searchQuery = encodeURIComponent(`title=${title}&author=${author}&language=${language}&publisher=${publisher}&isbn=${isbn}&extension=${extension}`)
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(`${searchBase}${searchQuery}`).then(() => {
|
||||||
|
//console.log('Async: Copying to clipboard was successful!');
|
||||||
|
}, (err) => {
|
||||||
|
//console.error('Async: Could not copy text: ', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
useDebounceEffect(
|
useDebounceEffect(
|
||||||
() => {
|
() => {
|
||||||
const query = complexQuery
|
const query = complexQuery
|
||||||
|
@ -72,66 +119,71 @@ const Search: React.FC<SearchProps> = ({ setBooks }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SimpleGrid
|
<>
|
||||||
columns={{ sm: 1, md: 2, lg: 3 }}
|
<SimpleGrid
|
||||||
spacing={{ base: 2, md: 4 }}
|
columns={{ sm: 1, md: 2, lg: 3 }}
|
||||||
px={{ base: 4, md: 8 }}
|
spacing={{ base: 2, md: 4 }}
|
||||||
>
|
px={{ base: 4, md: 8 }}
|
||||||
<SearchInput
|
>
|
||||||
icon={<Icon as={TbBook2} />}
|
|
||||||
placeholder={t('book.title')}
|
|
||||||
value={title}
|
|
||||||
onChange={setTitle}
|
|
||||||
/>
|
|
||||||
<SearchInput
|
|
||||||
icon={<Icon as={TbUserCircle} />}
|
|
||||||
placeholder={t('book.author')}
|
|
||||||
value={author}
|
|
||||||
onChange={setAuthor}
|
|
||||||
/>
|
|
||||||
<SearchInput
|
|
||||||
icon={<Icon as={TbBuilding} />}
|
|
||||||
placeholder={t('book.publisher')}
|
|
||||||
value={publisher}
|
|
||||||
onChange={setPublisher}
|
|
||||||
/>
|
|
||||||
<SearchInput
|
|
||||||
icon={<Icon as={TbFileDescription} />}
|
|
||||||
placeholder={t('book.extension')}
|
|
||||||
value={extension}
|
|
||||||
onChange={setExtension}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!showLanguageDropdown && (<SearchInput
|
|
||||||
icon={<Icon as={IoLanguage} />}
|
|
||||||
placeholder={t('input.select_language')}
|
|
||||||
value={language}
|
|
||||||
onChange={handleLanguageChange}
|
|
||||||
onClear={handleLanguageReset}
|
|
||||||
/>)}
|
|
||||||
|
|
||||||
{showLanguageDropdown && (<SearchLanguage
|
|
||||||
icon={<Icon as={IoLanguage} />}
|
|
||||||
placeholder={t('input.select_language')}
|
|
||||||
value={language}
|
|
||||||
onChange={handleLanguageChange}
|
|
||||||
/>)}
|
|
||||||
|
|
||||||
<SearchInput
|
|
||||||
icon={<Icon as={TbHash} />}
|
|
||||||
placeholder={t('book.isbn')}
|
|
||||||
value={isbn}
|
|
||||||
onChange={setISBN}
|
|
||||||
/>
|
|
||||||
<GridItem colSpan={{ sm: 1, md: 2, lg: 3 }}>
|
|
||||||
<SearchInput
|
<SearchInput
|
||||||
icon={<Icon as={TbReportSearch} />}
|
icon={<Icon as={TbBook2} />}
|
||||||
placeholder={t('search.complex')}
|
placeholder={t('book.title')}
|
||||||
value={complexQuery}
|
value={title}
|
||||||
onChange={setComplexQuery}
|
onChange={setTitle}
|
||||||
/>
|
/>
|
||||||
</GridItem>
|
<SearchInput
|
||||||
</SimpleGrid>
|
icon={<Icon as={TbUserCircle} />}
|
||||||
|
placeholder={t('book.author')}
|
||||||
|
value={author}
|
||||||
|
onChange={setAuthor}
|
||||||
|
/>
|
||||||
|
<SearchInput
|
||||||
|
icon={<Icon as={TbBuilding} />}
|
||||||
|
placeholder={t('book.publisher')}
|
||||||
|
value={publisher}
|
||||||
|
onChange={setPublisher}
|
||||||
|
/>
|
||||||
|
<SearchInput
|
||||||
|
icon={<Icon as={TbFileDescription} />}
|
||||||
|
placeholder={t('book.extension')}
|
||||||
|
value={extension}
|
||||||
|
onChange={setExtension}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{!showLanguageDropdown && (<SearchInput
|
||||||
|
icon={<Icon as={IoLanguage} />}
|
||||||
|
placeholder={t('input.select_language')}
|
||||||
|
value={language}
|
||||||
|
onChange={handleLanguageChange}
|
||||||
|
onClear={handleLanguageReset}
|
||||||
|
/>)}
|
||||||
|
|
||||||
|
{showLanguageDropdown && (<SearchLanguage
|
||||||
|
icon={<Icon as={IoLanguage} />}
|
||||||
|
placeholder={t('input.select_language')}
|
||||||
|
value={language}
|
||||||
|
onChange={handleLanguageChange}
|
||||||
|
/>)}
|
||||||
|
|
||||||
|
<SearchInput
|
||||||
|
icon={<Icon as={TbHash} />}
|
||||||
|
placeholder={t('book.isbn')}
|
||||||
|
value={isbn}
|
||||||
|
onChange={setISBN}
|
||||||
|
/>
|
||||||
|
<GridItem colSpan={{ sm: 1, md: 2, lg: 3 }}>
|
||||||
|
<SearchInput
|
||||||
|
icon={<Icon as={TbReportSearch} />}
|
||||||
|
placeholder={t('search.complex')}
|
||||||
|
value={complexQuery}
|
||||||
|
onChange={setComplexQuery}
|
||||||
|
/>
|
||||||
|
</GridItem>
|
||||||
|
</SimpleGrid>
|
||||||
|
<CopyToClipboardButton
|
||||||
|
show={hasAnySearchBeenMade()}
|
||||||
|
onClick={copyToClipboard} />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,9 @@
|
||||||
"collapse": "Collapse"
|
"collapse": "Collapse"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"complex": "Complex search"
|
"complex": "Complex search",
|
||||||
|
"copy_search_link": "Copy to clipboard",
|
||||||
|
"copy_success": "Copied!"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Settings",
|
"title": "Settings",
|
||||||
|
@ -205,7 +207,9 @@
|
||||||
"collapse": "Richiudi"
|
"collapse": "Richiudi"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"complex": "Ricerca dettagliata"
|
"complex": "Ricerca dettagliata",
|
||||||
|
"copy_search_link": "Copia link ricerca",
|
||||||
|
"copy_success": "Copiato negli appunti!"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Impostazioni",
|
"title": "Impostazioni",
|
||||||
|
|
Loading…
Reference in New Issue