Update remaining assets and code, add webber

This commit is contained in:
Angelos Chalaris
2019-08-13 14:22:41 +03:00
parent 91acf51cf7
commit 902fea8b1e
106 changed files with 3025 additions and 6159 deletions

View File

@ -0,0 +1,78 @@
import React from 'react';
import Helmet from 'react-helmet';
import { useStaticQuery, graphql } from 'gatsby';
import '../styles/index.scss';
// ===================================================
// Page metadata (using Helmet)
// ===================================================
const Meta = ({ description = '', lang = 'en', meta = [], title }) => {
const { site, file } = useStaticQuery(
graphql`
query {
site {
siteMetadata {
title
description
author
}
}
file(relativePath: { eq: "logo.png" }) {
id
childImageSharp {
fluid(maxHeight: 400) {
src
}
}
}
}
`,
);
const metaDescription = description || site.siteMetadata.description;
return (
<Helmet
htmlAttributes={{
lang,
}}
title={title ? title : site.siteMetadata.title}
titleTemplate={title ? `%s - ${site.siteMetadata.title}` : '%s'}
meta={[
{
name: `description`,
content: metaDescription,
},
{
name: `author`,
content: site.siteMetadata.author,
},
{
name: `viewport`,
content: `width=device-width, initial-scale=1`,
},
{
name: `og:title`,
content: site.siteMetadata.title,
},
{
name: `og:description`,
content: metaDescription,
},
{
name: `og:type`,
content: `website`,
},
{
name: `og:image`,
content: file.childImageSharp.fluid.src,
},
].concat(meta)}
bodyAttributes={{
class: 'card-page',
}}
/>
);
};
export default Meta;

View File

@ -0,0 +1,22 @@
import React from 'react';
const BackArrowIcon = ({ className, onClick }) => (
<svg
onClick={onClick}
xmlns='http://www.w3.org/2000/svg'
width='18'
height='18'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
className={`feather feather-arrow-left ${className}`}
>
<line x1='19' y1='12' x2='5' y2='12'></line>
<polyline points='12 19 5 12 12 5'></polyline>
</svg>
);
export default BackArrowIcon;

View File

@ -0,0 +1,22 @@
import React from 'react';
const ClipboardIcon = ({ className, onClick }) => (
<svg
onClick={onClick}
xmlns='http://www.w3.org/2000/svg'
width='24'
height='24'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
className={`feather feather-clipboard ${className}`}
>
<path d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'></path>
<rect x='8' y='2' width='8' height='4' rx='1' ry='1'></rect>
</svg>
);
export default ClipboardIcon;

View File

@ -0,0 +1,23 @@
import React from 'react';
const CollapseClosedIcon = ({ className, onClick }) => (
<svg
onClick={onClick}
xmlns='http://www.w3.org/2000/svg'
width='16'
height='16'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='1'
strokeLinecap='round'
strokeLinejoin='round'
className={`feather feather-plus-square ${className}`}
>
<rect x='3' y='3' width='18' height='18' rx='2' ry='2'></rect>
<line x1='12' y1='8' x2='12' y2='16'></line>
<line x1='8' y1='12' x2='16' y2='12'></line>
</svg>
);
export default CollapseClosedIcon;

View File

@ -0,0 +1,22 @@
import React from 'react';
const CollapseOpenIcon = ({ className, onClick }) => (
<svg
onClick={onClick}
xmlns='http://www.w3.org/2000/svg'
width='16'
height='16'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='1'
strokeLinecap='round'
strokeLinejoin='round'
className={`feather feather-minus-square ${className}`}
>
<rect x='3' y='3' width='18' height='18' rx='2' ry='2'></rect>
<line x1='8' y1='12' x2='16' y2='12'></line>
</svg>
);
export default CollapseOpenIcon;

View File

@ -0,0 +1,21 @@
import React from 'react';
const DarkModeIcon = ({ className, onClick }) => (
<svg
onClick={onClick}
xmlns='http://www.w3.org/2000/svg'
width='24'
height='24'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
className={`feather feather-moon ${className}`}
>
<path d='M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z'></path>
</svg>
);
export default DarkModeIcon;

View File

@ -0,0 +1,21 @@
import React from 'react';
const GithubIcon = ({ className, onClick }) => (
<svg
onClick={onClick}
xmlns='http://www.w3.org/2000/svg'
width='24'
height='24'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
className={`feather feather-github ${className}`}
>
<path d='M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22'></path>
</svg>
);
export default GithubIcon;

View File

@ -0,0 +1,29 @@
import React from 'react';
const LightModeIcon = ({ className, onClick }) => (
<svg
onClick={onClick}
xmlns='http://www.w3.org/2000/svg'
width='24'
height='24'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
className={`feather feather-sun ${className}`}
>
<circle cx='12' cy='12' r='5'></circle>
<line x1='12' y1='1' x2='12' y2='3'></line>
<line x1='12' y1='21' x2='12' y2='23'></line>
<line x1='4.22' y1='4.22' x2='5.64' y2='5.64'></line>
<line x1='18.36' y1='18.36' x2='19.78' y2='19.78'></line>
<line x1='1' y1='12' x2='3' y2='12'></line>
<line x1='21' y1='12' x2='23' y2='12'></line>
<line x1='4.22' y1='19.78' x2='5.64' y2='18.36'></line>
<line x1='18.36' y1='5.64' x2='19.78' y2='4.22'></line>
</svg>
);
export default LightModeIcon;

View File

@ -0,0 +1,26 @@
import React from 'react';
const ListIcon = ({ className, onClick }) => (
<svg
onClick={onClick}
xmlns='http://www.w3.org/2000/svg'
width='24'
height='24'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
className={`feather feather-list ${className}`}
>
<line x1='8' y1='6' x2='21' y2='6'></line>
<line x1='8' y1='12' x2='21' y2='12'></line>
<line x1='8' y1='18' x2='21' y2='18'></line>
<line x1='3' y1='6' x2='3' y2='6'></line>
<line x1='3' y1='12' x2='3' y2='12'></line>
<line x1='3' y1='18' x2='3' y2='18'></line>
</svg>
);
export default ListIcon;

View File

@ -0,0 +1,24 @@
import React from 'react';
const SearchIcon = ({ className, onClick }) => {
return (
<svg
onClick={onClick}
xmlns='http://www.w3.org/2000/svg'
width='24'
height='24'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
className={`feather feather-search ${className}`}
>
<circle cx='11' cy='11' r='8'></circle>
<line x1='21' y1='21' x2='16.65' y2='16.65'></line>
</svg>
);
};
export default SearchIcon;

View File

@ -0,0 +1,25 @@
import React from 'react';
const ShareIcon = ({ className, onClick }) => (
<svg
onClick={onClick}
xmlns='http://www.w3.org/2000/svg'
width='24'
height='24'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
className={`feather feather-share-2 ${className}`}
>
<circle cx='18' cy='5' r='3'></circle>
<circle cx='6' cy='12' r='3'></circle>
<circle cx='18' cy='19' r='3'></circle>
<line x1='8.59' y1='13.51' x2='15.42' y2='17.49'></line>
<line x1='15.41' y1='6.51' x2='8.59' y2='10.49'></line>
</svg>
);
export default ShareIcon;

View File

@ -0,0 +1,28 @@
import React from 'react';
// ===================================================
// Simple, stateful search component
// ===================================================
const Search = ({ defaultValue = '', setSearchQuery, className = '' }) => {
const [value, setValue] = React.useState(defaultValue);
React.useEffect(() => {
setSearchQuery(value);
}, [value]);
return (
<input
defaultValue={defaultValue}
className='search-box'
type='search'
id='searchInput'
placeholder='Search...'
aria-label='Snippet search'
onKeyUp={e => {
setValue(e.target.value);
}}
/>
);
};
export default Search;

View File

@ -0,0 +1,148 @@
import React from 'react';
import { graphql, useStaticQuery } from 'gatsby';
import { connect } from 'react-redux';
import AniLink from 'gatsby-plugin-transition-link/AniLink';
import ReactCSSTransitionReplace from 'react-css-transition-replace';
import config from '../../../config';
import { toggleDarkMode } from '../state/app';
import SearchIcon from './SVGs/SearchIcon';
import GithubIcon from './SVGs/GithubIcon';
import DarkModeIcon from './SVGs/DarkModeIcon';
import LightModeIcon from './SVGs/LightModeIcon';
import ListIcon from './SVGs/ListIcon';
// ===================================================
// Application-level UI component
// ===================================================
const Shell = ({
isDarkMode,
isSearch,
isList,
dispatch,
withIcon = true,
withTitle = true,
children,
}) => {
const data = useStaticQuery(graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
description
}
}
file(relativePath: { eq: "30s-icon.png" }) {
id
childImageSharp {
original {
src
}
}
}
snippetDataJson(meta: { type: { eq: "snippetListingArray" } }) {
data {
title
id
attributes {
tags
}
}
}
}
`);
let viewportWidth = typeof window !== 'undefined' && window.innerWidth;
return (
<div className={isDarkMode ? 'page-container dark' : 'page-container'}>
{/* Menu */}
<header className='menu'>
<AniLink
cover
direction={viewportWidth < 600 ? 'up' : 'right'}
bg={isDarkMode ? '#434E76' : '#FFFFFF'}
to='/search'
aria-label='Search'
className={isSearch ? 'menu-button active' : 'menu-button'}
>
<SearchIcon />
</AniLink>
<AniLink
cover
direction={viewportWidth < 600 ? 'up' : 'right'}
bg={isDarkMode ? '#434E76' : '#FFFFFF'}
to='/list'
aria-label='Snippet list'
className={isList ? 'menu-button active' : 'menu-button'}
>
<ListIcon />
</AniLink>
{/* eslint-disable-next-line */}
<a target='_blank'
rel='noopener'
href={config.repositoryUrl}
aria-label='View on GitHub'
className='menu-button'
>
<GithubIcon />
</a>
{/*
The use of React.Fragment here will cause a lot of errors to show up in webber:dev.
Truth is, this is the only decent way I found to deal with this module's odd behavior.
Keep as is, unless you can provide a better solution, in which case please send a PR.
*/}
<ReactCSSTransitionReplace
transitionName='cross-fade'
transitionEnterTimeout={300}
transitionLeaveTimeout={300}
component='button'
className='menu-button'
childComponent={React.Fragment}
>
{isDarkMode ? (
<LightModeIcon
key='lmit'
onClick={() => dispatch(toggleDarkMode(!isDarkMode))}
/>
) : (
<DarkModeIcon
key='dmit'
onClick={() => dispatch(toggleDarkMode(!isDarkMode))}
/>
)}
</ReactCSSTransitionReplace>
</header>
{/* Content */}
<div className='content'>
{withTitle ? (
<h1 className='website-title'>
{data.site.siteMetadata.title}
{withIcon ? (
<img
src={data.file.childImageSharp.original.src}
alt='Logo'
className='website-logo'
/>
) : (
''
)}
</h1>
) : (
''
)}
{children}
</div>
</div>
);
};
export default connect(
state => ({
isDarkMode: state.app.isDarkMode,
lastPageTitle: state.app.lastPageTitle,
lastPageUrl: state.app.lastPageUrl,
searchQuery: state.app.searchQuery,
}),
null,
)(Shell);

View File

@ -0,0 +1,21 @@
import React from 'react';
// ===================================================
// Generic card (displays textual content)
// ===================================================
const SimpleCard = ({
title,
children,
isDarkMode
}) => (
<div className='card short'>
<h4 className='card-title'>
{title}
</h4>
<div className='card-description'>
{children}
</div>
</div>
);
export default SimpleCard;

View File

@ -0,0 +1,175 @@
import React from 'react';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import config from '../../../config';
import { getTextualContent, getCodeBlocks, optimizeAllNodes } from '../util';
import ClipboardIcon from './SVGs/ClipboardIcon';
// import ShareIcon from './SVGs/ShareIcon';
import AniLink from 'gatsby-plugin-transition-link/AniLink';
import CollapseOpenIcon from './SVGs/CollapseOpenIcon';
import CollapseClosedIcon from './SVGs/CollapseClosedIcon';
import ReactCSSTransitionReplace from 'react-css-transition-replace';
// ===================================================
// Snippet Card HOC - check components below for more
// ===================================================
const SnippetCard = ({ short, snippetData, ...rest }) => {
let difficulty = snippetData.tags.includes('advanced')
? 'advanced'
: snippetData.tags.includes('beginner')
? 'beginner'
: 'intermediate';
return short ? (
<ShortCard snippetData={snippetData} difficulty={difficulty} {...rest} />
) : (
<FullCard snippetData={snippetData} difficulty={difficulty} {...rest} />
);
};
// ===================================================
// Simple card corner for difficulty display
// ===================================================
const CardCorner = ({ difficulty = 'intermediate' }) => (
<div
className={`card-corner ${difficulty}`}
aria-label={difficulty}
title={difficulty}
/>
);
// ===================================================
// Full snippet view (tags, code, title, description)
// ===================================================
const FullCard = ({ snippetData, difficulty, isDarkMode }) => {
const [examplesOpen, setExamplesOpen] = React.useState(false);
const tags = snippetData.tags;
let cardCodeHtml = `${optimizeAllNodes(
getCodeBlocks(snippetData.html).code,
)}`;
let cardExamplesHtml = `${optimizeAllNodes(
getCodeBlocks(snippetData.html).example,
)}`;
return (
<div className='card'>
<CardCorner difficulty={difficulty} />
<h4 className='card-title'>{snippetData.title}</h4>
{tags.map(tag => (
<span className='tag' key={`tag_${tag}`}>{tag}</span>
))}
<div
className='card-description'
dangerouslySetInnerHTML={{
__html: `${getTextualContent(snippetData.html)}`,
}}
/>
<div className='card-bottom'>
<CopyToClipboard
text={snippetData.code}
onCopy={() => {
let tst = document.createElement('div');
tst.classList = 'toast';
tst.innerHTML = 'Snippet copied to clipboard!';
document.body.appendChild(tst);
setTimeout(function() {
tst.style.opacity = 0;
setTimeout(function() {
document.body.removeChild(tst);
}, 300);
}, 1700);
}}
>
<button
className='button button-a button-copy'
aria-label='Copy to clipboard'
>
<ClipboardIcon />
</button>
</CopyToClipboard>
{/* <button className="button button-b button-social-sh" aria-label="Share">
<ShareIcon />
</button> */}
<pre
className={`card-code language-${config.language}`}
dangerouslySetInnerHTML={{ __html: cardCodeHtml }}
/>
<button
className='button button-example-toggler'
onClick={() => setExamplesOpen(!examplesOpen)}
>
{examplesOpen ? <CollapseOpenIcon /> : <CollapseClosedIcon />}Examples
</button>
<ReactCSSTransitionReplace
transitionName='roll-up'
transitionEnterTimeout={300}
transitionLeaveTimeout={300}
>
{examplesOpen && (
<pre
className='section card-examples language-js'
dangerouslySetInnerHTML={{ __html: cardExamplesHtml }}
/>
)}
</ReactCSSTransitionReplace>
</div>
</div>
);
};
// ===================================================
// Short snippet view (title, description, code*)
// ===================================================
const ShortCard = ({ snippetData, difficulty, isDarkMode }) => {
let cardCodeHtml = `${optimizeAllNodes(
getCodeBlocks(snippetData.html).code,
)}`;
return (
<div className='card short'>
<CardCorner difficulty={difficulty} />
<h4 className='card-title'>
<AniLink
paintDrip
to={`/${snippetData.id}`}
hex={isDarkMode ? '#434E76' : '#FFFFFF'}
>
{snippetData.title}
</AniLink>
</h4>
<div
className='card-description'
dangerouslySetInnerHTML={{
__html: `${getTextualContent(snippetData.html)}`,
}}
/>
<div className='card-bottom'>
<CopyToClipboard
text={snippetData.code}
onCopy={() => {
let tst = document.createElement('div');
tst.classList = 'toast';
tst.innerHTML = 'Snippet copied to clipboard!';
document.body.appendChild(tst);
setTimeout(function() {
tst.style.opacity = 0;
setTimeout(function() {
document.body.removeChild(tst);
}, 300);
}, 1700);
}}
>
<button
className='button button-a button-copy'
aria-label='Copy to clipboard'
>
<ClipboardIcon />
</button>
</CopyToClipboard>
<pre
className={`card-code language-${config.language}`}
dangerouslySetInnerHTML={{ __html: cardCodeHtml }}
/>
</div>
</div>
);
};
export default SnippetCard;