Merge pull request #9 from pawelmalak/clipboard-fix

Clipboard fix & pin icon
This commit is contained in:
pawelmalak
2021-09-24 11:29:58 +02:00
committed by GitHub
9 changed files with 74 additions and 46 deletions

View File

@@ -1 +1,2 @@
*.css *.css
CHANGELOG.md

6
CHANGELOG.md Normal file
View File

@@ -0,0 +1,6 @@
### v1.1 (2021-09-24)
- Added pin icon directly to snippet card ([#4](https://github.com/pawelmalak/snippet-box/issues/4))
- Fixed issue with copying snippets ([#6](https://github.com/pawelmalak/snippet-box/issues/6))
### v1.0 (2021-09-23)
Initial release

View File

@@ -4277,6 +4277,11 @@
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="
}, },
"clipboard-copy": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/clipboard-copy/-/clipboard-copy-4.0.1.tgz",
"integrity": "sha512-wOlqdqziE/NNTUJsfSgXmBMIrYmfd5V0HCGsR8uAKHcg+h9NENWINcfRjtWGU77wDHC8B8ijV4hMTGYbrKovng=="
},
"cliui": { "cliui": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",

View File

@@ -15,6 +15,7 @@
"@types/react-dom": "^17.0.9", "@types/react-dom": "^17.0.9",
"@types/react-router-dom": "^5.3.0", "@types/react-router-dom": "^5.3.0",
"axios": "^0.21.4", "axios": "^0.21.4",
"clipboard-copy": "^4.0.1",
"dayjs": "^1.10.7", "dayjs": "^1.10.7",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",

View File

@@ -4,8 +4,8 @@ import { Snippet } from '../../typescript/interfaces';
import { dateParser, badgeColor } from '../../utils'; import { dateParser, badgeColor } from '../../utils';
import { Badge, Button, Card } from '../UI'; import { Badge, Button, Card } from '../UI';
import { SnippetsContext } from '../../store'; import { SnippetsContext } from '../../store';
import Icon from '@mdi/react'; import copy from 'clipboard-copy';
import { mdiPin } from '@mdi/js'; import { SnippetPin } from './SnippetPin';
interface Props { interface Props {
snippet: Snippet; snippet: Snippet;
@@ -17,7 +17,7 @@ export const SnippetCard = (props: Props): JSX.Element => {
const { setSnippet } = useContext(SnippetsContext); const { setSnippet } = useContext(SnippetsContext);
const copyHandler = () => { const copyHandler = () => {
navigator.clipboard.writeText(code); copy(code);
}; };
return ( return (
@@ -25,7 +25,7 @@ export const SnippetCard = (props: Props): JSX.Element => {
{/* TITLE */} {/* TITLE */}
<h5 className='card-title d-flex align-items-center justify-content-between'> <h5 className='card-title d-flex align-items-center justify-content-between'>
{title} {title}
{isPinned ? <Icon path={mdiPin} size={0.8} color='#212529' /> : ''} <SnippetPin id={id} isPinned={isPinned} />
</h5> </h5>
<h6 className='card-subtitle mb-2 text-muted'> <h6 className='card-subtitle mb-2 text-muted'>

View File

@@ -1,11 +1,11 @@
import { useContext } from 'react'; import { useContext } from 'react';
import { Link } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { SnippetsContext } from '../../store'; import { SnippetsContext } from '../../store';
import { Snippet } from '../../typescript/interfaces'; import { Snippet } from '../../typescript/interfaces';
import { dateParser } from '../../utils'; import { dateParser } from '../../utils';
import { Button, Card } from '../UI'; import { Button, Card } from '../UI';
import Icon from '@mdi/react'; import copy from 'clipboard-copy';
import { mdiPin } from '@mdi/js'; import { SnippetPin } from './SnippetPin';
interface Props { interface Props {
snippet: Snippet; snippet: Snippet;
@@ -23,21 +23,22 @@ export const SnippetDetails = (props: Props): JSX.Element => {
isPinned isPinned
} = props.snippet; } = props.snippet;
const { deleteSnippet, toggleSnippetPin, setSnippet } = const history = useHistory();
useContext(SnippetsContext);
const { deleteSnippet, setSnippet } = useContext(SnippetsContext);
const creationDate = dateParser(createdAt); const creationDate = dateParser(createdAt);
const updateDate = dateParser(updatedAt); const updateDate = dateParser(updatedAt);
const copyHandler = () => { const copyHandler = () => {
navigator.clipboard.writeText(code); copy(code);
}; };
return ( return (
<Card> <Card>
<h5 className='card-title d-flex align-items-center justify-content-between'> <h5 className='card-title d-flex align-items-center justify-content-between'>
{title} {title}
{isPinned ? <Icon path={mdiPin} size={0.8} color='#212529' /> : ''} <SnippetPin id={id} isPinned={isPinned} />
</h5> </h5>
<p>{description}</p> <p>{description}</p>
@@ -58,33 +59,22 @@ export const SnippetDetails = (props: Props): JSX.Element => {
<span>Last updated</span> <span>Last updated</span>
<span>{updateDate.relative}</span> <span>{updateDate.relative}</span>
</div> </div>
<hr /> <hr />
{/* ACTIONS */} {/* ACTIONS */}
<div className='d-flex justify-content-between'> <div className='d-grid g-2' style={{ rowGap: '10px' }}>
<Link
to={{
pathname: `/editor/${id}`,
state: { from: window.location.pathname }
}}
>
<Button
text='Edit'
color='dark'
small
outline
classes='me-3'
handler={() => setSnippet(id)}
/>
</Link>
<Button <Button
text={`${isPinned ? 'Unpin snippet' : 'Pin snippet'}`} text='Edit'
color='dark' color='dark'
small small
outline outline
handler={() => toggleSnippetPin(id)} handler={() => {
classes='me-3' setSnippet(id);
history.push({
pathname: `/editor/${id}`,
state: { from: window.location.pathname }
});
}}
/> />
<Button <Button
text='Delete' text='Delete'
@@ -93,12 +83,6 @@ export const SnippetDetails = (props: Props): JSX.Element => {
outline outline
handler={() => deleteSnippet(id)} handler={() => deleteSnippet(id)}
/> />
</div>
<hr />
{/* COPY */}
<div className='d-grid'>
<Button text='Copy code' color='dark' small handler={copyHandler} /> <Button text='Copy code' color='dark' small handler={copyHandler} />
</div> </div>
</Card> </Card>

View File

@@ -0,0 +1,24 @@
import { useContext } from 'react';
import { SnippetsContext } from '../../store';
import Icon from '@mdi/react';
import { mdiPin, mdiPinOutline } from '@mdi/js';
interface Props {
id: number;
isPinned: boolean;
}
export const SnippetPin = (props: Props): JSX.Element => {
const { toggleSnippetPin } = useContext(SnippetsContext);
const { id, isPinned } = props;
return (
<div onClick={() => toggleSnippetPin(id)} className='cursor-pointer'>
{isPinned ? (
<Icon path={mdiPin} size={0.8} color='#212529' />
) : (
<Icon path={mdiPinOutline} size={0.8} color='#9a9a9a' />
)}
</div>
);
};

View File

@@ -17,7 +17,7 @@ export const SnippetsContext = createContext<Context>({
getSnippetById: (id: number) => {}, getSnippetById: (id: number) => {},
setSnippet: (id: number) => {}, setSnippet: (id: number) => {},
createSnippet: (snippet: NewSnippet) => {}, createSnippet: (snippet: NewSnippet) => {},
updateSnippet: (snippet: NewSnippet, id: number) => {}, updateSnippet: (snippet: NewSnippet, id: number, isLocal?: boolean) => {},
deleteSnippet: (id: number) => {}, deleteSnippet: (id: number) => {},
toggleSnippetPin: (id: number) => {}, toggleSnippetPin: (id: number) => {},
countSnippets: () => {} countSnippets: () => {}
@@ -81,7 +81,11 @@ export const SnippetsContextProvider = (props: Props): JSX.Element => {
.catch(err => redirectOnError()); .catch(err => redirectOnError());
}; };
const updateSnippet = (snippet: NewSnippet, id: number): void => { const updateSnippet = (
snippet: NewSnippet,
id: number,
isLocal?: boolean
): void => {
axios axios
.put<Response<Snippet>>(`/api/snippets/${id}`, snippet) .put<Response<Snippet>>(`/api/snippets/${id}`, snippet)
.then(res => { .then(res => {
@@ -92,10 +96,13 @@ export const SnippetsContextProvider = (props: Props): JSX.Element => {
...snippets.slice(oldSnippetIdx + 1) ...snippets.slice(oldSnippetIdx + 1)
]); ]);
setCurrentSnippet(res.data.data); setCurrentSnippet(res.data.data);
history.push({
pathname: `/snippet/${res.data.data.id}`, if (!isLocal) {
state: { from: '/snippets' } history.push({
}); pathname: `/snippet/${res.data.data.id}`,
state: { from: '/snippets' }
});
}
}) })
.catch(err => redirectOnError()); .catch(err => redirectOnError());
}; };
@@ -121,7 +128,7 @@ export const SnippetsContextProvider = (props: Props): JSX.Element => {
const snippet = snippets.find(s => s.id === id); const snippet = snippets.find(s => s.id === id);
if (snippet) { if (snippet) {
updateSnippet({ ...snippet, isPinned: !snippet.isPinned }, id); updateSnippet({ ...snippet, isPinned: !snippet.isPinned }, id, true);
} }
}; };

View File

@@ -8,7 +8,7 @@ export interface Context {
getSnippetById: (id: number) => void; getSnippetById: (id: number) => void;
setSnippet: (id: number) => void; setSnippet: (id: number) => void;
createSnippet: (snippet: NewSnippet) => void; createSnippet: (snippet: NewSnippet) => void;
updateSnippet: (snippet: NewSnippet, id: number) => void; updateSnippet: (snippet: NewSnippet, id: number, isLocal?: boolean) => void;
deleteSnippet: (id: number) => void; deleteSnippet: (id: number) => void;
toggleSnippetPin: (id: number) => void; toggleSnippetPin: (id: number) => void;
countSnippets: () => void; countSnippets: () => void;