mirror of
https://github.com/pawelmalak/snippet-box.git
synced 2025-12-21 13:23:05 +01:00
Merge pull request #9 from pawelmalak/clipboard-fix
Clipboard fix & pin icon
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
*.css
|
*.css
|
||||||
|
CHANGELOG.md
|
||||||
6
CHANGELOG.md
Normal file
6
CHANGELOG.md
Normal 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
|
||||||
5
client/package-lock.json
generated
5
client/package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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'>
|
||||||
|
|||||||
@@ -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
|
<Button
|
||||||
text='Edit'
|
text='Edit'
|
||||||
color='dark'
|
color='dark'
|
||||||
small
|
small
|
||||||
outline
|
outline
|
||||||
classes='me-3'
|
handler={() => {
|
||||||
handler={() => setSnippet(id)}
|
setSnippet(id);
|
||||||
/>
|
history.push({
|
||||||
</Link>
|
pathname: `/editor/${id}`,
|
||||||
<Button
|
state: { from: window.location.pathname }
|
||||||
text={`${isPinned ? 'Unpin snippet' : 'Pin snippet'}`}
|
});
|
||||||
color='dark'
|
}}
|
||||||
small
|
|
||||||
outline
|
|
||||||
handler={() => toggleSnippetPin(id)}
|
|
||||||
classes='me-3'
|
|
||||||
/>
|
/>
|
||||||
<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>
|
||||||
|
|||||||
24
client/src/components/Snippets/SnippetPin.tsx
Normal file
24
client/src/components/Snippets/SnippetPin.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
if (!isLocal) {
|
||||||
history.push({
|
history.push({
|
||||||
pathname: `/snippet/${res.data.data.id}`,
|
pathname: `/snippet/${res.data.data.id}`,
|
||||||
state: { from: '/snippets' }
|
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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user