├── src
├── Components
│ ├── Common
│ │ ├── index.js
│ │ ├── Loader.js
│ │ └── LoaderSvg.js
│ ├── Blog
│ │ ├── index.js
│ │ ├── Card
│ │ │ ├── CardTitle.js
│ │ │ ├── CardHeader.js
│ │ │ ├── CardDescription.js
│ │ │ ├── index.js
│ │ │ ├── CardReadingTime.js
│ │ │ ├── CardContainer.js
│ │ │ ├── CardCategory.js
│ │ │ └── Card.js
│ │ └── BlogContainer.js
│ ├── Post
│ │ ├── Author
│ │ │ ├── index.js
│ │ │ ├── AuthorDetails.js
│ │ │ ├── AuthorAvatar.js
│ │ │ └── AuthorName.js
│ │ ├── PostTitle.js
│ │ ├── Comment
│ │ │ ├── CommentLinkContainer.js
│ │ │ ├── index.js
│ │ │ ├── CommentContainer.js
│ │ │ ├── CommentLink.js
│ │ │ └── Comment.js
│ │ ├── ReactionCard
│ │ │ ├── ReactionCardBorder.js
│ │ │ ├── ReactionCardContainer.js
│ │ │ ├── ReactionCardText.js
│ │ │ ├── index.js
│ │ │ ├── ReactionCardLink.js
│ │ │ └── ReactionCard.js
│ │ ├── PostContainer.js
│ │ ├── PostDate.js
│ │ ├── index.js
│ │ ├── PostReaction.js
│ │ ├── PostDateLink.js
│ │ └── BackButton.js
│ ├── Header
│ │ ├── HeaderWrapper.js
│ │ ├── index.js
│ │ ├── HeaderTitle.js
│ │ ├── HeaderSubtitle.js
│ │ ├── HeaderContainer.js
│ │ ├── Header.js
│ │ └── GithubLogin.js
│ ├── Theme
│ │ ├── GlobalStyles.js
│ │ ├── Theme.js
│ │ ├── Moon.js
│ │ ├── Sun.js
│ │ ├── useDarkMode.js
│ │ └── Toggler.js
│ └── Markdown
│ │ └── Overrides.js
├── logo.png
├── video.gif
├── kentsTweet.png
├── Utils
│ ├── tools.js
│ ├── emoji.js
│ ├── auth.js
│ ├── apollo.js
│ └── docco.js
├── setupTests.js
├── index.js
├── Containers
│ ├── CommentsSection.js
│ ├── GithubCallback.js
│ ├── Blog.js
│ └── BlogPost.js
├── Router.js
├── config.js
├── Application.js
├── index.css
└── serviceWorker.js
├── public
├── robots.txt
├── favicon.ico
├── manifest.json
└── index.html
├── .gitignore
├── LICENSE
├── .github
└── workflows
│ └── deploy.yml
├── package.json
├── .all-contributorsrc
└── README.md
/src/Components/Common/index.js:
--------------------------------------------------------------------------------
1 | export { Loader } from './Loader'
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/src/Components/Blog/index.js:
--------------------------------------------------------------------------------
1 | export { BlogContainer } from './BlogContainer'
2 |
--------------------------------------------------------------------------------
/src/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StrongBlue0703/react-blog/HEAD/src/logo.png
--------------------------------------------------------------------------------
/src/video.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StrongBlue0703/react-blog/HEAD/src/video.gif
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StrongBlue0703/react-blog/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/kentsTweet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StrongBlue0703/react-blog/HEAD/src/kentsTweet.png
--------------------------------------------------------------------------------
/src/Components/Blog/Card/CardTitle.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const CardTitle = styled.h2`
4 | font-size: 34px;
5 | `
--------------------------------------------------------------------------------
/src/Components/Post/Author/index.js:
--------------------------------------------------------------------------------
1 | export { AuthorDetails } from './AuthorDetails'
2 | export { AuthorAvatar } from './AuthorAvatar'
3 | export { AuthorName } from './AuthorName'
--------------------------------------------------------------------------------
/src/Components/Post/Author/AuthorDetails.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const AuthorDetails = styled.div`
4 | display: flex;
5 | align-items: center;
6 | `
--------------------------------------------------------------------------------
/src/Utils/tools.js:
--------------------------------------------------------------------------------
1 | export const reverseMapping = (obj) => {
2 | const reversed = {};
3 | Object.keys(obj).forEach((key) => {
4 | reversed[obj[key]] = key
5 | });
6 | return reversed;
7 | };
--------------------------------------------------------------------------------
/src/Components/Post/PostTitle.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const PostTitle = styled.h1`
4 | @media only screen and (max-width: 768px) {
5 | font-size: 30px;
6 | }
7 | `
--------------------------------------------------------------------------------
/src/Components/Blog/Card/CardHeader.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const CardHeader = styled.div`
4 | display: flex;
5 | align-items: center;
6 | justify-content: space-between;
7 | `
--------------------------------------------------------------------------------
/src/Components/Blog/BlogContainer.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const BlogContainer = styled.div`
4 | margin: 2rem auto 0;
5 | width: 90%;
6 | max-width: 700px;
7 | padding: 20px 10px;
8 | `
--------------------------------------------------------------------------------
/src/Components/Header/HeaderWrapper.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const HeaderWrapper = styled.div`
4 | margin: 0 auto;
5 | padding: 0 1em;
6 | max-width: 1140px;
7 | text-align: center;
8 | `
--------------------------------------------------------------------------------
/src/Components/Post/Comment/CommentLinkContainer.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const CommentLinkContainer = styled.div`
4 | display: flex;
5 | align-items: center;
6 | margin-top: 3rem;
7 | `;
8 |
--------------------------------------------------------------------------------
/src/Components/Post/ReactionCard/ReactionCardBorder.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const ReactionCardBorder = styled.div`
4 | height: 1px;
5 | margin: 8px 1px 0px;
6 | background-color: rgb(229, 229, 229);
7 | `
--------------------------------------------------------------------------------
/src/Components/Post/Author/AuthorAvatar.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const AuthorAvatar = styled.img`
4 | width: 50px;
5 | height: 50px;
6 | border-radius: 40px;
7 | margin: 10px;
8 | margin-left: 0px;
9 | `
--------------------------------------------------------------------------------
/src/Components/Post/Author/AuthorName.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const AuthorName = styled.p`
4 | margin-bottom: 0px;
5 | margin-top: 0px;
6 | font-size: 16px;
7 | color: ${ props => props.theme.text };
8 | `
--------------------------------------------------------------------------------
/src/Components/Post/Comment/index.js:
--------------------------------------------------------------------------------
1 | export {Comment} from "./Comment";
2 | export {CommentContainer} from "./CommentContainer";
3 | export {CommentLink} from "./CommentLink";
4 | export {CommentLinkContainer} from "./CommentLinkContainer";
5 |
--------------------------------------------------------------------------------
/src/Components/Post/PostContainer.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const PostContainer = styled.div`
4 | margin: 2rem auto 0;
5 | width: 90%;
6 | max-width: 700px;
7 | padding: 20px 10px;
8 | position: relative;
9 | `
--------------------------------------------------------------------------------
/src/Components/Post/PostDate.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const PostDate = styled.p`
4 | color: ${ props => props.theme.toggleBorder };
5 | font-weight: 400;
6 | font-size: 14px;
7 | margin-bottom: 0px;
8 | margin-top: 0px;
9 | `
--------------------------------------------------------------------------------
/src/Components/Blog/Card/CardDescription.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const CardDescription = styled.p`
4 | font-size: 18px;
5 | overflow: hidden;
6 | display: -webkit-box;
7 | -webkit-line-clamp: 3;
8 | -webkit-box-orient: vertical;
9 | `
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/src/Components/Theme/GlobalStyles.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from "styled-components";
2 | export const GlobalStyles = createGlobalStyle`
3 | body {
4 | background: ${({ theme }) => theme.body};
5 | color: ${({ theme }) => theme.text};
6 | transition: all 0.25s linear;
7 | }
8 | `;
9 |
--------------------------------------------------------------------------------
/src/Components/Post/index.js:
--------------------------------------------------------------------------------
1 | export { PostContainer } from "./PostContainer";
2 | export { PostTitle } from "./PostTitle";
3 | export { PostDate } from "./PostDate";
4 | export { PostDateLink } from "./PostDateLink";
5 | export { PostReaction } from "./PostReaction";
6 | export { BackButton } from "./BackButton";
7 |
--------------------------------------------------------------------------------
/src/Components/Header/index.js:
--------------------------------------------------------------------------------
1 | export { HeaderContainer } from './HeaderContainer'
2 | export { HeaderWrapper } from './HeaderWrapper'
3 | export { HeaderTitle } from './HeaderTitle'
4 | export { HeaderSubtitle } from './HeaderSubtitle'
5 | export { Header } from './Header'
6 | export { GithubLogin } from './GithubLogin'
--------------------------------------------------------------------------------
/src/Components/Post/PostReaction.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const PostReaction = styled.span`
4 | position: static;
5 | bottom: 60px;
6 | animation-name: scale-in;
7 | animation-duration: 0.15s;
8 | animation-timing-function: cubic-bezier(0.2, 0, 0.13, 1.5);
9 | `
10 |
--------------------------------------------------------------------------------
/src/Components/Post/ReactionCard/ReactionCardContainer.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const ReactionCardContainer = styled.div`
4 | padding-top: 5px;
5 | border: 1px solid rgba(0, 0, 0, 0.15);
6 | border-radius: 4px;
7 | box-shadow: rgba(0, 0, 0, 0.15) 0px 3px 12px;
8 | display: inline-block;
9 | `
--------------------------------------------------------------------------------
/src/Components/Post/ReactionCard/ReactionCardText.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const ReactionCardText = styled.p`
4 | font-size: 14px;
5 | line-height: 1.5;
6 | color: rgb(118, 118, 118);
7 | margin: 6px 12px;
8 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica;
9 | `
--------------------------------------------------------------------------------
/src/Components/Post/ReactionCard/index.js:
--------------------------------------------------------------------------------
1 | export { ReactionCardContainer } from './ReactionCardContainer'
2 | export { ReactionCardText } from './ReactionCardText'
3 | export { ReactionCardBorder } from './ReactionCardBorder'
4 | export { ReactionCardLink } from './ReactionCardLink'
5 | export { ReactionCard } from './ReactionCard'
6 |
--------------------------------------------------------------------------------
/src/Components/Blog/Card/index.js:
--------------------------------------------------------------------------------
1 | export { CardContainer } from './CardContainer'
2 | export { CardHeader } from './CardHeader'
3 | export { CardCategory } from './CardCategory'
4 | export { CardTitle } from './CardTitle'
5 | export { CardDescription } from './CardDescription'
6 | export { CardReadingTime } from './CardReadingTime'
7 | export { Card } from './Card'
--------------------------------------------------------------------------------
/src/Components/Theme/Theme.js:
--------------------------------------------------------------------------------
1 | export const lightTheme = {
2 | mode: "light",
3 | body: "#fff",
4 | text: "#363537",
5 | toggleBorder: "#070707",
6 | background: "#363537",
7 | };
8 | export const darkTheme = {
9 | mode: "dark",
10 | body: "#2E3440",
11 | text: "#FAFAFA",
12 | toggleBorder: "#6B8096",
13 | background: "#999",
14 | };
15 |
--------------------------------------------------------------------------------
/src/Components/Blog/Card/CardReadingTime.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 |
4 | const ReadingTime = styled.p`
5 | color: #b5b5b5;
6 | font-size: 15px;
7 | position: absolute;
8 | top: -10px;
9 | right: 10px;
10 | `
11 |
12 | export const CardReadingTime = ({ time }) => (
13 | {time} Min Read
14 | )
--------------------------------------------------------------------------------
/src/Components/Post/Comment/CommentContainer.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const CommentContainer = styled.div`
4 | margin-top: 3rem;
5 | border-radius: 10px;
6 | padding: 10px 30px;
7 | position: relative;
8 | color: ${(props) => props.theme.text};
9 | border: ${(props) => props.theme.text} solid 1px;
10 | font-size: 16px;
11 | `;
12 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React Blog Github",
3 | "name": "React Blog Github",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/Components/Blog/Card/CardContainer.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const CardContainer = styled.div`
4 | cursor: pointer;
5 | margin-bottom: 6rem;
6 | border-radius: 10px;
7 | padding: 10px;
8 | transition: 0.2s;
9 | position: relative;
10 |
11 | :hover {
12 | background-color: ${props => props.theme.mode === 'light' ? '#F5F5F5' : '#3B4252'};
13 | }
14 | `
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/src/Components/Common/Loader.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 |
4 | import LoaderSvg from './LoaderSvg'
5 |
6 | const LoaderContainer = styled.span`
7 | display: flex;
8 | justify-content: center;
9 | align-items: center;
10 | height: 200px;
11 | `
12 |
13 | export const Loader = () => (
14 |
15 |
16 |
17 | )
--------------------------------------------------------------------------------
/src/Components/Post/PostDateLink.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const PostDateLink = styled.a`
4 | font-size: 14px;
5 | text-decoration: none;
6 | transition-duration: 0.15s, 0.15s, 0.15s;
7 | transition-timing-function: ease-in, ease-in, ease-in;
8 | transition-delay: 0s, 0s, 0s;
9 | color: ${ props => props.theme.toggleBorder };
10 |
11 | :hover {
12 | color: ${ props => props.theme.text };
13 | }
14 | `
--------------------------------------------------------------------------------
/src/Utils/emoji.js:
--------------------------------------------------------------------------------
1 | import { reverseMapping } from './tools'
2 |
3 | const emojis = {
4 | 'THUMBS_UP': '👍',
5 | 'THUMBS_DOWN': '👎',
6 | 'LAUGH': '😄',
7 | 'HOORAY': '🎉',
8 | 'CONFUSED': '😕',
9 | 'HEART': '❤️',
10 | 'ROCKET': '🚀',
11 | 'EYES': '👀',
12 | }
13 |
14 | export const getEmojiByName = (emojiName) => {
15 | return emojis[emojiName] || '';
16 | };
17 |
18 | export const getNameByEmoji = (emoji) => {
19 | return reverseMapping(emojis)[emoji] || '';
20 | };
--------------------------------------------------------------------------------
/src/Components/Theme/Moon.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function Moon() {
4 | return (
5 |
15 |
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/src/Utils/auth.js:
--------------------------------------------------------------------------------
1 | export const getToken = () => {
2 | return localStorage.getItem('githubToken');
3 | }
4 |
5 | export const getAuthenticatedUser = async () => {
6 | const token = localStorage.getItem('githubToken');
7 | if (token) {
8 | const response = await fetch('https://api.github.com/user', {
9 | headers: new Headers({
10 | authorization: `Bearer ${token}`
11 | }),
12 | });
13 | const result = await response.json();
14 | result.token = token;
15 | return result;
16 | }
17 | }
--------------------------------------------------------------------------------
/src/Components/Header/HeaderTitle.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | import { config } from '../../config'
4 | const { titleColor, titleColorDark } = config.header
5 |
6 | export const HeaderTitle = styled.h1`
7 | font-weight: 700;
8 | line-height: 1.15;
9 | margin: 1.25rem 0;
10 | font-size: 4.5em;
11 | text-align: center;
12 | color: ${(props) => props.theme.mode === "light" ? titleColor : titleColorDark};
13 |
14 | @media only screen and (max-width: 768px) {
15 | font-size: 3em;
16 | }
17 | `
--------------------------------------------------------------------------------
/src/Components/Post/ReactionCard/ReactionCardLink.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const ReactionCardLink = styled.a`
4 | display: block;
5 | text-align: center;
6 | margin: 0;
7 | padding: 5px;
8 | line-height: 1.5;
9 | font-size: 14px;
10 | text-decoration: none;
11 | transition-duration: 0.15s, 0.15s, 0.15s;
12 | transition-timing-function: ease-in, ease-in, ease-in;
13 | transition-delay: 0s, 0s, 0s;
14 | color: rgba(0, 0, 0, 0.54);
15 |
16 | :hover {
17 | color: black;
18 | }
19 | `
--------------------------------------------------------------------------------
/src/Components/Header/HeaderSubtitle.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | import { config } from '../../config'
4 | const { subtitleColor, subtitleColorDark } = config.header
5 |
6 | export const HeaderSubtitle = styled.h1`
7 | font-weight: 400;
8 | color: ${(props) => props.theme.mode === "light" ? subtitleColor : subtitleColorDark};
9 | line-height: 1.15;
10 | margin: 1.25rem 0;
11 | font-size: 2.5em;
12 | text-align: center;
13 |
14 | @media only screen and (max-width: 768px) {
15 | font-size: 2em;
16 | }
17 | `
--------------------------------------------------------------------------------
/src/Components/Theme/Sun.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function Sun() {
4 | return (
5 |
15 |
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/src/Components/Header/HeaderContainer.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | import { config } from '../../config'
4 | const { backgroundColor, backgroundColorDark } = config.header;
5 |
6 | export const HeaderContainer = styled.div`
7 | position: relative;
8 | padding: 5em;
9 | text-align: center;
10 | background: ${(props) => props.theme.mode === "light" ? backgroundColor : backgroundColorDark};
11 | background-repeat: no-repeat;
12 | background-position: center center;
13 | background-size: cover;
14 |
15 | @media only screen and (max-width: 768px) {
16 | padding: 3em;
17 | }
18 | `
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import Application from './Application';
5 | import * as serviceWorker from './serviceWorker';
6 | import './index.css';
7 |
8 | ReactDOM.render( , document.getElementById('root'));
9 |
10 | // If you want your app to work offline and load faster, you can change
11 | // unregister() to register() below. Note this comes with some pitfalls.
12 | // Learn more about service workers: https://bit.ly/CRA-PWA
13 | if(process.env.NODE_ENV === 'production') serviceWorker.register();
14 | else serviceWorker.unregister();
15 |
16 |
--------------------------------------------------------------------------------
/src/Components/Header/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import {
4 | HeaderContainer,
5 | HeaderWrapper,
6 | HeaderTitle,
7 | HeaderSubtitle,
8 | GithubLogin
9 | } from './'
10 | import { config } from "../../config"
11 |
12 | export const Header = () => {
13 | return (
14 |
15 |
16 |
17 | {config.title}
18 | {config.subtitle}
19 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/src/Components/Theme/useDarkMode.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | export const useDarkMode = () => {
3 | const [theme, setTheme] = useState('light');
4 |
5 | const setMode = mode => {
6 | window.localStorage.setItem('theme', mode)
7 | setTheme(mode)
8 | };
9 |
10 | const themeToggler = () => {
11 | theme === 'light' ? setMode('dark') : setMode('light')
12 | };
13 |
14 | useEffect(() => {
15 | const localTheme = window.localStorage.getItem('theme');
16 | localTheme && setTheme(localTheme)
17 | }, []);
18 | return [theme, themeToggler]
19 | };
20 |
--------------------------------------------------------------------------------
/src/Utils/apollo.js:
--------------------------------------------------------------------------------
1 | import ApolloClient from "apollo-boost";
2 |
3 | export const client = new ApolloClient({
4 | uri: "https://api.github.com/graphql",
5 | request: operation => {
6 | operation.setContext({
7 | headers: {
8 | authorization: `Bearer ${atob(process.env.REACT_APP_GITHUB_ACCESS_TOKEN)}`
9 | }
10 | });
11 | }
12 | });
13 |
14 | export const userClient = (token) => new ApolloClient({
15 | uri: "https://api.github.com/graphql",
16 | request: operation => {
17 | operation.setContext({
18 | headers: {
19 | authorization: `Bearer ${token}`
20 | }
21 | });
22 | }
23 | });
--------------------------------------------------------------------------------
/src/Containers/CommentsSection.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import {
4 | Comment,
5 | CommentLink,
6 | CommentLinkContainer,
7 | } from "../Components/Post/Comment";
8 |
9 | const CommentsSection = ({postUrl, comments}) => {
10 | return (
11 | <>
12 |
13 |
14 | Post a comment
15 |
16 |
17 | {comments.map((v, id) => (
18 |
19 | ))}
20 | >
21 | );
22 | };
23 |
24 | export default CommentsSection;
25 |
--------------------------------------------------------------------------------
/src/Components/Post/Comment/CommentLink.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const CommentLink = styled.a`
4 | outline: none;
5 | border: 1px solid;
6 | font-size: 22px;
7 | font-family: "Shadows Into Light", serif;
8 | border-radius: 5px;
9 | padding: 0px 20px 0 20px;
10 | cursor: pointer;
11 | position: relative;
12 | background-color: ${ props => props.theme.body };
13 | color: ${ props => props.theme.text };
14 | text-decoration: none;
15 | margin: auto;
16 |
17 | :hover {
18 | background-color: ${ props => props.theme.mode === 'light' ? '#373737' : '#6B8096' };
19 | color: white;
20 | }
21 | `;
22 |
--------------------------------------------------------------------------------
/src/Router.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { HashRouter, Switch, Route } from "react-router-dom";
3 |
4 | import Blog from "./Containers/Blog"
5 | import BlogPost from "./Containers/BlogPost";
6 |
7 | var createBrowserHistory = require("history").createBrowserHistory;
8 | const history = createBrowserHistory();
9 |
10 | const Router = () => {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | export default Router;
22 |
--------------------------------------------------------------------------------
/src/Components/Post/ReactionCard/ReactionCard.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import {
4 | ReactionCardContainer,
5 | ReactionCardText,
6 | ReactionCardBorder,
7 | ReactionCardLink,
8 | } from './'
9 |
10 | export const ReactionCard = ({ link }) => {
11 | return (
12 |
13 | Give your reaction on Github
14 |
15 |
16 | Github link ⚡️
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/src/Components/Blog/Card/CardCategory.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 |
4 | const StyledCategory = styled.div`
5 | display: inline-block;
6 | background: #009bbb;
7 | border-radius: 3px;
8 | padding: 3px 15px;
9 | font-size: 12px;
10 | text-transform: uppercase;
11 | color: ${ props => props.theme.body };
12 | font-weight: 300;
13 | line-height: 28px;
14 | font-family: "Quicksand";
15 | margin-right: 10px;
16 | `
17 |
18 | export const CardCategory = ({ value }) => (
19 |
22 | {value.name}
23 |
24 | )
--------------------------------------------------------------------------------
/src/Components/Markdown/Overrides.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import SyntaxHighlighter from "react-syntax-highlighter";
4 | import {docco} from "react-syntax-highlighter/dist/esm/styles/hljs";
5 |
6 | export const HyperLink = ({children, ...props}) => (
7 |
13 | {children}
14 |
22 |
23 | );
24 |
25 | export const CodeBlock = ({children}) => (
26 |
27 | {children.props.children}
28 |
29 | );
30 |
--------------------------------------------------------------------------------
/src/Components/Post/BackButton.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const BackButton = styled.button`
4 | outline: none;
5 | border: 1px solid;
6 | font-size: 22px;
7 | font-family: "Shadows Into Light", serif;
8 | border-radius: 5px;
9 | padding: 0px 20px 0 30px;
10 | cursor: pointer;
11 | position: relative;
12 | background-color: ${ props => props.theme.body };
13 | color: ${ props => props.theme.text };
14 |
15 | :hover {
16 | background-color: ${ props => props.theme.mode === 'light' ? '#373737' : '#6B8096' };
17 | color: white;
18 | }
19 |
20 | :before {
21 | content: "\\27A4";
22 | position: absolute;
23 | transform: translateY(-50%) rotate(180deg);
24 | left: 10px;
25 | font-size: 16px;
26 | top: 54%;
27 | }
28 | `;
29 |
--------------------------------------------------------------------------------
/src/Components/Common/LoaderSvg.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function LoaderSvg({ fill }) {
4 | return (
5 |
12 |
13 |
14 |
15 |
16 |
24 |
25 |
26 |
27 |
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | export const config = {
2 | // Your Github Converted Token To Know How To Get Your Token Look at Readme.md
3 | githubConvertedToken: "Z2hwX0xhcG9DOW9qSktOVTlNUHJUdjVuM3dvTzBoYlZlTTJNWU5VYw==",
4 |
5 | // Your Github UserName
6 | githubUserName: "saadpasta",
7 |
8 | // Your Github Repo Name Where You Have your issues as Blog
9 | githubRepo: "react-blog-github",
10 |
11 | // Set it to true if you have a Github app to add to this project
12 | // and fill the client ID & secret
13 | enableOAuth: true,
14 | OAuthClientID: 'Iv1.9f40fd53257d4102',
15 | OAuthSecret: 'cc3db76ffe9848c92d83172807eececa79f0eb22',
16 |
17 | // Your Personal Blog Title
18 | title : "Saad Pasta" ,
19 |
20 | // Your Personal Blog Subtitle
21 | subtitle : "Software Developer",
22 |
23 | // Header customization
24 | header: {
25 | backgroundColor: '#f1f6f8', // can be a CSS gradient
26 | backgroundColorDark: '#4C566A',
27 | titleColor: '#ff5252',
28 | titleColorDark: '#16a085',
29 | subtitleColor: '#37474f',
30 | subtitleColorDark: '#D8DEE9',
31 | },
32 | };
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Saad Pasta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Build and Deploy
2 | env:
3 | CI: false
4 | REACT_APP_GITHUB_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
5 |
6 | on:
7 | push:
8 | branches:
9 | - master
10 | jobs:
11 | build-and-deploy:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout 🛎️
15 | uses: actions/checkout@v2 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly.
16 | with:
17 | persist-credentials: false
18 |
19 | - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built.
20 | run: |
21 | npm install
22 | npm run build
23 | - name: Deploy 🚀
24 | uses: JamesIves/github-pages-deploy-action@releases/v3
25 | with:
26 | ACCESS_TOKEN: ${{ secrets.SECRET_TOKEN }}
27 | BRANCH: gh-pages # The branch the action should deploy to.
28 | FOLDER: build # The folder the action should deploy.
29 |
--------------------------------------------------------------------------------
/src/Components/Post/Comment/Comment.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Markdown from "markdown-to-jsx";
3 | import moment from "moment";
4 |
5 | import {PostDate} from "../";
6 | import {AuthorAvatar, AuthorDetails, AuthorName} from "../Author";
7 | import {CommentContainer} from "./";
8 |
9 | import {HyperLink, CodeBlock} from "../../Markdown/Overrides";
10 |
11 | export const Comment = ({comment}) => {
12 | return (
13 |
14 |
15 |
19 |
20 |
{comment.author.login}
21 |
{moment(comment.updatedAt).format("DD MMM YYYY")}
22 |
23 |
24 |
25 |
37 | {comment.body}
38 |
39 |
40 | );
41 | };
42 |
--------------------------------------------------------------------------------
/src/Components/Theme/Toggler.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Sun from "./Sun";
3 | import Moon from "./Moon";
4 | import { func, string } from "prop-types";
5 | import styled from "styled-components";
6 | const Button = styled.button`
7 | background-color: ${({ theme }) => theme.background};
8 | border: 2px solid ${({ theme }) => theme.background};
9 | color: ${({ theme }) => theme.text};
10 | width: 70px;
11 | height: 70px;
12 | outline: none;
13 | border-radius: 50%;
14 | transition: all 0.1s ease-in-out;
15 | color: white;
16 | text-align: center;
17 | position: fixed;
18 | cursor: pointer;
19 | right: 30px;
20 | bottom: 30px;
21 | box-shadow:
22 | 0 2.8px 2.2px rgba(0, 0, 0, 0.034),
23 | 0 6.7px 5.3px rgba(0, 0, 0, 0.048),
24 | 0 12.5px 10px rgba(0, 0, 0, 0.06),
25 | 0 22.3px 17.9px rgba(0, 0, 0, 0.072),
26 | 0 41.8px 33.4px rgba(0, 0, 0, 0.086),
27 | 0 100px 80px rgba(0, 0, 0, 0.12);
28 | }`;
29 | const Toggle = ({ theme, toggleTheme }) => {
30 | return (
31 |
32 | {theme === "light" ? : }
33 |
34 | );
35 | };
36 | Toggle.propTypes = {
37 | theme: string.isRequired,
38 | toggleTheme: func.isRequired,
39 | };
40 | export default Toggle;
41 |
--------------------------------------------------------------------------------
/src/Components/Blog/Card/Card.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import readingTime from "reading-time";
3 | import { useHistory } from "react-router-dom";
4 |
5 | import {
6 | CardContainer,
7 | CardHeader,
8 | CardCategory,
9 | CardReadingTime,
10 | CardTitle,
11 | CardDescription,
12 | } from './'
13 |
14 | export const Card = ({ blog }) => {
15 | const [labels, setLabels] = useState([]);
16 | const history = useHistory();
17 |
18 | const openBlog = (title, number) => {
19 | history.push(`/blog/${title}/${number}`);
20 | }
21 |
22 | useEffect(() => {
23 | const labels = blog.labels.nodes.filter((value) => {
24 | return value.name !== "blog";
25 | });
26 |
27 | setLabels(labels);
28 | }, [blog.labels.nodes]);
29 |
30 | return (
31 |
32 |
33 | <>
34 | {labels.map((value, i) => {
35 | return (
36 |
37 | );
38 | })}
39 | >
40 |
41 |
42 | openBlog(blog.title, blog.number)}>
43 | {blog.title}
44 |
45 | {blog.bodyText}
46 |
47 |
48 |
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/src/Application.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { ApolloProvider } from '@apollo/react-hooks';
3 | import { Helmet } from "react-helmet";
4 | import { ThemeProvider } from "styled-components";
5 |
6 | import { config } from './config'
7 | import { client } from './Utils/apollo';
8 | import Router from './Router';
9 | import GithubCallback from './Containers/GithubCallback';
10 | import Toggle from "./Components/Theme/Toggler";
11 | import { GlobalStyles } from "./Components/Theme/GlobalStyles";
12 | import { lightTheme, darkTheme } from "./Components/Theme/Theme";
13 | import { useDarkMode } from "./Components/Theme/useDarkMode";
14 |
15 | const Application = () => {
16 | const urlParams = new URLSearchParams(window.location.search);
17 | const [theme, themeToggler] = useDarkMode();
18 | const themeMode = theme === 'light' ? lightTheme : darkTheme;
19 |
20 | if (urlParams.get('code')) {
21 | return
22 | }
23 |
24 | return (
25 | <>
26 |
27 | {config.title}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | >
40 | )
41 | };
42 |
43 | export default Application;
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
22 |
23 |
24 | You need to enable JavaScript to run this app.
25 |
26 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/Components/Header/GithubLogin.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
4 | import { faGithub } from '@fortawesome/free-brands-svg-icons'
5 | import { config } from '../../config'
6 |
7 | const { enableOAuth, OAuthClientID } = config
8 |
9 | const ButtonContainer = styled.div`
10 | display: ${enableOAuth ? 'block' : 'none'};
11 | position: ${({ absolute }) => absolute ? 'absolute' : 'initial'};
12 | top: 10px;
13 | right: 10px;
14 | `
15 | const Button = styled.button`
16 | -webkit-appearance: none;
17 | background: linear-gradient(180deg, rgb(136, 220, 109) 0%, rgb(92, 168, 65) 100%);
18 | color: #FFF;
19 | border: none;
20 | padding: 8px 16px;
21 | font-size: 16px;
22 | border-radius: 5px;
23 | cursor: pointer;
24 | transition: 0.2s;
25 | outline: 0;
26 |
27 | :hover {
28 | box-shadow: 0px 0px 10px 1px rgba(0,0,0,0.10);
29 | }
30 | `
31 |
32 | const Text = styled.span`
33 | color: #FFF;
34 | font-size: 18px;
35 | `
36 |
37 | export const GithubLogin = ({ isAbsolute }) => {
38 | const isLoggedIn = localStorage.getItem('githubToken') || false
39 |
40 | const callOAuth = async () => {
41 | window.location.href = 'https://github.com/login/oauth/authorize?client_id=' + OAuthClientID;
42 | }
43 |
44 | return (
45 |
46 | {!isLoggedIn
47 | ? (
48 |
49 | Log in
50 |
51 | )
52 | : Logged in as {localStorage.getItem('githubUsername')}
53 | }
54 |
55 | )
56 | }
57 |
--------------------------------------------------------------------------------
/src/Containers/GithubCallback.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 |
3 | import { config } from '../config'
4 | import { getAuthenticatedUser } from '../Utils/auth'
5 | import { Loader } from '../Components/Common/Loader'
6 |
7 | const { enableOAuth, OAuthClientID, OAuthSecret } = config
8 |
9 | const GithubCallback = () => {
10 | const [loading, setLoading] = useState(true)
11 |
12 | const authenticate = async (code) => {
13 | // Apparently we need to use a proxy to make this request (CORS blocked for client-side only applications)
14 | const response = await fetch('https://cors-anywhere.herokuapp.com/https://github.com/login/oauth/access_token', {
15 | method: 'POST',
16 | body: new URLSearchParams({
17 | client_id: OAuthClientID,
18 | client_secret: OAuthSecret,
19 | code: code,
20 | }),
21 | headers: new Headers({
22 | 'Accept': 'application/json',
23 | 'Content-Type': 'application/x-www-form-urlencoded'
24 | }),
25 | })
26 |
27 | return response.json()
28 | }
29 |
30 | const setUsername = async () => {
31 | const { login } = await getAuthenticatedUser()
32 | localStorage.setItem('githubUsername', login)
33 | }
34 |
35 | useEffect(() => {
36 | const urlParams = new URLSearchParams(window.location.search);
37 | authenticate(urlParams.get('code'))
38 | .then((response) => {
39 | if (response) {
40 | localStorage.setItem('githubToken', response.access_token)
41 | setUsername().then(() => setLoading(false))
42 | }
43 | })
44 | }, [])
45 |
46 | if (!enableOAuth || !loading) {
47 | return window.location.replace(window.location.origin + window.location.pathname);
48 | }
49 |
50 | return
51 | }
52 |
53 | export default GithubCallback
54 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-blog-github",
3 | "homepage": "https://saadpasta.github.io/react-blog-github",
4 | "version": "0.1.0",
5 | "private": true,
6 | "dependencies": {
7 | "@apollo/react-hooks": "^3.1.3",
8 | "@fortawesome/fontawesome-svg-core": "^1.2.27",
9 | "@fortawesome/free-brands-svg-icons": "^5.12.1",
10 | "@fortawesome/free-regular-svg-icons": "^5.12.1",
11 | "@fortawesome/free-solid-svg-icons": "^5.12.1",
12 | "@fortawesome/react-fontawesome": "^0.1.8",
13 | "@testing-library/jest-dom": "^4.2.4",
14 | "@testing-library/react": "^9.3.2",
15 | "@testing-library/user-event": "^7.1.2",
16 | "apollo-boost": "^0.4.7",
17 | "gh-pages": "^2.2.0",
18 | "graphql": "^14.5.8",
19 | "markdown-to-jsx": "^6.10.3",
20 | "medium.css": "^1.0.2",
21 | "moment": "^2.24.0",
22 | "react": "^16.12.0",
23 | "react-dom": "^16.12.0",
24 | "react-ga": "^2.7.0",
25 | "react-helmet": "^5.2.1",
26 | "react-reactions": "github:randomdipesh/react-reactions",
27 | "react-router-dom": "^5.1.2",
28 | "react-scripts": "3.3.0",
29 | "react-syntax-highlighter": "^12.2.1",
30 | "reading-time": "^1.2.0",
31 | "styled-components": "^5.0.1"
32 | },
33 | "scripts": {
34 | "predeploy": "npm run build",
35 | "deploy": "gh-pages -d build",
36 | "start": "react-scripts start",
37 | "build": "react-scripts build",
38 | "test": "react-scripts test",
39 | "eject": "react-scripts eject"
40 | },
41 | "eslintConfig": {
42 | "extends": "react-app"
43 | },
44 | "browserslist": {
45 | "production": [
46 | ">0.2%",
47 | "not dead",
48 | "not op_mini all"
49 | ],
50 | "development": [
51 | "last 1 chrome version",
52 | "last 1 firefox version",
53 | "last 1 safari version"
54 | ]
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Containers/Blog.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { gql } from "apollo-boost";
3 | import { useQuery } from '@apollo/react-hooks';
4 |
5 | import { config } from "../config";
6 | import { Header } from "../Components/Header";
7 | import { Loader } from '../Components/Common'
8 | import { BlogContainer } from '../Components/Blog'
9 | import { Card } from '../Components/Blog/Card'
10 |
11 | const GET_POSTS = gql`
12 | {
13 | repository(owner: "${config.githubUserName}", name: "${config.githubRepo}") {
14 | issues(first: 100, states: OPEN, filterBy: { labels: "blog" }, orderBy: { direction: DESC, field: CREATED_AT }) {
15 | nodes {
16 | title
17 | body
18 | bodyHTML
19 | bodyText
20 | number
21 | labels(first: 100) {
22 | nodes {
23 | color
24 | name
25 | id
26 | }
27 | }
28 | author {
29 | url
30 | avatarUrl
31 | login
32 | }
33 | updatedAt
34 | id
35 | }
36 | }
37 | }
38 | }
39 | `
40 |
41 | const Blog = () => {
42 | const [posts, setPosts] = useState([]);
43 | const { loading, error, data } = useQuery(GET_POSTS);
44 |
45 | useEffect(() => {
46 | if (!loading) {
47 | if (error) {
48 | console.error(error)
49 | }
50 |
51 | if (data) {
52 | setPosts(data?.repository?.issues?.nodes)
53 | }
54 | }
55 | }, [loading, error, data]);
56 |
57 | return (
58 | <>
59 |
60 |
61 | {
62 | loading
63 | ?
64 | : posts.map((v, i) => {
65 | return ;
66 | })
67 | }
68 |
69 | >
70 | );
71 | }
72 |
73 | export default Blog;
74 |
--------------------------------------------------------------------------------
/src/Utils/docco.js:
--------------------------------------------------------------------------------
1 | export const docco = {
2 | hljs: {
3 | display: 'block',
4 | overflowX: 'auto',
5 | padding: '0.5em',
6 | color: '#000',
7 | background: '#f7fafc',
8 | border: '1px solid #e3e8ee',
9 | },
10 | 'hljs-comment': {
11 | color: '#408080',
12 | fontStyle: 'italic',
13 | },
14 | 'hljs-quote': {
15 | color: '#408080',
16 | fontStyle: 'italic',
17 | },
18 | 'hljs-keyword': {
19 | color: '#954121',
20 | },
21 | 'hljs-selector-tag': {
22 | color: '#954121',
23 | },
24 | 'hljs-literal': {
25 | color: '#954121',
26 | },
27 | 'hljs-subst': {
28 | color: '#954121',
29 | },
30 | 'hljs-number': {
31 | color: '#40a070',
32 | },
33 | 'hljs-string': {
34 | color: '#219161',
35 | },
36 | 'hljs-doctag': {
37 | color: '#219161',
38 | },
39 | 'hljs-selector-id': {
40 | color: '#19469d',
41 | },
42 | 'hljs-selector-class': {
43 | color: '#19469d',
44 | },
45 | 'hljs-section': {
46 | color: '#19469d',
47 | },
48 | 'hljs-type': {
49 | color: '#19469d',
50 | },
51 | 'hljs-params': {
52 | color: '#00f',
53 | },
54 | 'hljs-title': {
55 | color: '#458',
56 | fontWeight: 'bold',
57 | },
58 | 'hljs-tag': {
59 | color: '#000080',
60 | fontWeight: 'normal',
61 | },
62 | 'hljs-name': {
63 | color: '#000080',
64 | fontWeight: 'normal',
65 | },
66 | 'hljs-attribute': {
67 | color: '#000080',
68 | fontWeight: 'normal',
69 | },
70 | 'hljs-variable': {
71 | color: '#008080',
72 | },
73 | 'hljs-template-variable': {
74 | color: '#008080',
75 | },
76 | 'hljs-regexp': {
77 | color: '#b68',
78 | },
79 | 'hljs-link': {
80 | color: '#b68',
81 | },
82 | 'hljs-symbol': {
83 | color: '#990073',
84 | },
85 | 'hljs-bullet': {
86 | color: '#990073',
87 | },
88 | 'hljs-built_in': {
89 | color: '#0086b3',
90 | },
91 | 'hljs-builtin-name': {
92 | color: '#0086b3',
93 | },
94 | 'hljs-meta': {
95 | color: '#999',
96 | fontWeight: 'bold',
97 | },
98 | 'hljs-deletion': {
99 | background: '#fdd',
100 | },
101 | 'hljs-addition': {
102 | background: '#dfd',
103 | },
104 | 'hljs-emphasis': {
105 | fontStyle: 'italic',
106 | },
107 | 'hljs-strong': {
108 | fontWeight: 'bold',
109 | },
110 | }
111 |
--------------------------------------------------------------------------------
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "README.md"
4 | ],
5 | "imageSize": 100,
6 | "commit": false,
7 | "contributors": [
8 | {
9 | "login": "Muhammad-Hammad",
10 | "name": "SyedMuhammadHammadGhani",
11 | "avatar_url": "https://avatars0.githubusercontent.com/u/37264033?v=4",
12 | "profile": "http://github.com/MuhammadHammad",
13 | "contributions": [
14 | "doc"
15 | ]
16 | },
17 | {
18 | "login": "saadpasta",
19 | "name": "Saad Pasta",
20 | "avatar_url": "https://avatars2.githubusercontent.com/u/23307811?v=4",
21 | "profile": "http://saadpasta.github.io",
22 | "contributions": [
23 | "code",
24 | "doc",
25 | "design",
26 | "maintenance"
27 | ]
28 | },
29 | {
30 | "login": "jvm-odoo",
31 | "name": "Jason Van Malder",
32 | "avatar_url": "https://avatars0.githubusercontent.com/u/9156538?v=4",
33 | "profile": "https://github.com/jvm-odoo",
34 | "contributions": [
35 | "code",
36 | "infra"
37 | ]
38 | },
39 | {
40 | "login": "viveksharmaui",
41 | "name": "Slim Coder",
42 | "avatar_url": "https://avatars1.githubusercontent.com/u/28563357?v=4",
43 | "profile": "https://github.com/viveksharmaui",
44 | "contributions": [
45 | "code"
46 | ]
47 | },
48 | {
49 | "login": "waleed345",
50 | "name": "waleed345",
51 | "avatar_url": "https://avatars3.githubusercontent.com/u/42063633?v=4",
52 | "profile": "https://github.com/waleed345",
53 | "contributions": [
54 | "code"
55 | ]
56 | },
57 | {
58 | "login": "abhishekashyap",
59 | "name": "Abhishek Kashyap",
60 | "avatar_url": "https://avatars3.githubusercontent.com/u/29458374?v=4",
61 | "profile": "https://abhishekashyap.studio/",
62 | "contributions": [
63 | "code"
64 | ]
65 | },
66 | {
67 | "login": "xzebra",
68 | "name": "Zebra",
69 | "avatar_url": "https://avatars1.githubusercontent.com/u/20362769?v=4",
70 | "profile": "https://github.com/xzebra",
71 | "contributions": [
72 | "code",
73 | "ideas"
74 | ]
75 | },
76 | {
77 | "login": "jaeyeonling",
78 | "name": "Jaeyeon Kim",
79 | "avatar_url": "https://avatars0.githubusercontent.com/u/40811999?v=4",
80 | "profile": "https://github.com/jaeyeonling",
81 | "contributions": [
82 | "code"
83 | ]
84 | },
85 | {
86 | "login": "codebytec",
87 | "name": "Parthiv",
88 | "avatar_url": "https://avatars.githubusercontent.com/u/76069950?v=4",
89 | "profile": "https://parthiv.dev",
90 | "contributions": [
91 | "doc"
92 | ]
93 | }
94 | ],
95 | "contributorsPerLine": 7,
96 | "projectName": "react-blog-github",
97 | "projectOwner": "saadpasta",
98 | "repoType": "github",
99 | "repoHost": "https://github.com",
100 | "skipCi": true
101 | }
102 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css?family=Shadows+Into+Light|Quicksand|Playfair+Display:700i,900");
2 |
3 | html,
4 | body {
5 | margin: 0;
6 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | h1,
12 | h2,
13 | p,
14 | i,
15 | a,
16 | .first-letter,
17 | .authorName a {
18 | text-rendering: optimizeLegibility;
19 | }
20 |
21 | h1 {
22 | font-family: "Shadows Into Light", serif;
23 | font-size: 48px;
24 | text-align: left;
25 | margin-bottom: 8px;
26 | }
27 |
28 | h2 {
29 | font-family: "Shadows Into Light", sans-serif;
30 | font-size: 30px;
31 | font-weight: 700;
32 | padding: 0;
33 | margin: 40px 0 40px 0px;
34 | text-align: left;
35 | line-height: 34.5px;
36 | letter-spacing: -0.45px;
37 | }
38 |
39 | p,
40 | i,
41 | a,
42 | li {
43 | margin-top: 21px;
44 | }
45 |
46 | p,
47 | i,
48 | a,
49 | li,
50 | span {
51 | font-family: "Quicksand";
52 | font-size: 21px;
53 | letter-spacing: -0.03px;
54 | line-height: 1.58;
55 | }
56 |
57 | a {
58 | text-decoration: underline;
59 | }
60 |
61 | blockquote {
62 | font-family: "Playfair Display", serif;
63 | font-size: 25px;
64 | font-style: italic;
65 | letter-spacing: -0.36px;
66 | line-height: 44.4px;
67 | overflow-wrap: break-word;
68 | margin: 30px 0 33px 0;
69 | padding: 0 0 0 1em;
70 | border-left: .25em solid #dfe2e5;
71 | }
72 |
73 | code {
74 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
75 | font-size: 18px;
76 | background: rgba(0, 0, 0, 0.05);
77 | border-radius: 2px;
78 | padding: 3px 5px;
79 | }
80 |
81 | pre > code {
82 | background: transparent !important;
83 | font-family: "Quicksand";
84 | font-size: 18px;
85 | padding: 0px;
86 | }
87 |
88 | pre {
89 | margin-top: 30px;
90 | border-radius: 10px;
91 | padding: 20px !important;
92 | }
93 |
94 | mark,
95 | .highlighted {
96 | background: #7dffb3;
97 | }
98 |
99 | .first-letter {
100 | overflow-wrap: break-word;
101 | font-family: "Playfair Display", serif;
102 | font-size: 60px;
103 | line-height: 60px;
104 | display: block;
105 | position: relative;
106 | float: left;
107 | margin: 0px 7px 0 -5px;
108 | }
109 |
110 | .subtitle {
111 | font-family: "Shadows Into Light", sans-serif;
112 | color: rgba(0, 0, 0, 0.54);
113 | margin: 0 0 24px 0;
114 | }
115 |
116 | ::selection {
117 | background-color: lavender;
118 | }
119 |
120 | /* Slide up */
121 |
122 | .blog-post-anchor {
123 | color: inherit;
124 | text-decoration: none;
125 | box-shadow: inset 0 -0.125em 0 #434C5E;
126 | transition: box-shadow 270ms cubic-bezier(0.77, 0, 0.175, 1), color 270ms cubic-bezier(0.77, 0, 0.175, 1);
127 | padding-right: 2px;
128 | padding-left: 2px;
129 | }
130 |
131 | .blog-post-anchor:hover {
132 | box-shadow: inset 0 -1.125em 0 #434C5E;
133 | color: white;
134 | }
135 |
136 | img {
137 | max-width: 100%;
138 | height: auto;
139 | }
140 |
141 | @keyframes scale-in {
142 | 0% {
143 | opacity: 0;
144 | transform: scale(0.5);
145 | }
146 | 100% {
147 | opacity: 1;
148 | transform: scale(1);
149 | }
150 | }
151 |
152 | @media only screen and (max-width: 768px) {
153 | p,
154 | i,
155 | a,
156 | li {
157 | font-size: 18px;
158 | }
159 | blockquote {
160 | font-size: 20px;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' }
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready.then(registration => {
134 | registration.unregister();
135 | });
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/Containers/BlogPost.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useCallback, useRef } from "react";
2 | import moment from "moment";
3 | import Markdown from "markdown-to-jsx";
4 | import readingTime from "reading-time";
5 | import { GithubSelector, GithubCounter } from "react-reactions";
6 | import { userClient } from '../Utils/apollo'
7 | import { gql } from "apollo-boost";
8 | import { useQuery } from "@apollo/react-hooks";
9 |
10 | import { config } from "../config";
11 | import { getEmojiByName, getNameByEmoji } from '../Utils/emoji';
12 | import { getAuthenticatedUser } from '../Utils/auth'
13 | import { Loader } from "../Components/Common";
14 | import { PostContainer, PostTitle, PostDate, PostDateLink, PostReaction, BackButton } from "../Components/Post";
15 | import { AuthorDetails, AuthorAvatar, AuthorName } from "../Components/Post/Author";
16 | import { GithubLogin } from '../Components/Header'
17 | import { HyperLink, CodeBlock } from '../Components/Markdown/Overrides';
18 | import CommentsSection from "./CommentsSection";
19 |
20 | export default function BlogHome() {
21 | const issueNumber = parseInt(window.location.href.split("/").pop());
22 | const GET_POSTS = gql`{
23 | repository(owner: "${config.githubUserName}", name: "${config.githubRepo}") {
24 | issue(number: ${issueNumber}) {
25 | title
26 | body
27 | bodyHTML
28 | url
29 | bodyText
30 | number
31 | bodyHTML
32 | author {
33 | url
34 | avatarUrl
35 | login
36 | }
37 | reactions(first:100){
38 | nodes{
39 | content
40 | user{
41 | id
42 | login
43 | }
44 | }
45 | }
46 | updatedAt
47 | id
48 | comments(first:100) {
49 | nodes {
50 | author {
51 | ... on User {
52 | avatarUrl
53 | name
54 | login
55 | }
56 | }
57 | body
58 | bodyHTML
59 | bodyText
60 | publishedAt
61 | updatedAt
62 | }
63 | }
64 | }
65 | }
66 | }
67 | `;
68 | const [post, setPost] = useState([]);
69 | const [postNodeId, setPostNodeId] = useState('');
70 | const [reactionPopup, setReactionPopup] = useState(false);
71 | const [postReactions, setPostReactions] = useState([]);
72 | const [postComments, setPostComments] = useState([]);
73 | const { loading, error, data } = useQuery(GET_POSTS);
74 | const reactionsContainer = useRef(null);
75 | const userToken = localStorage.getItem('githubToken');
76 |
77 | const setReactionFun = useCallback((reactions) => {
78 | // {
79 | // emoji: "👍", // String emoji reaction
80 | // by: "case" // String of persons name
81 | // }
82 |
83 | let reactions_array = [];
84 | reactions.forEach(element => {
85 | let obj = {
86 | by: element.user.login,
87 | emoji: getEmojiByName(element.content)
88 | };
89 | reactions_array.push(obj);
90 | });
91 |
92 | setPostReactions(reactions_array);
93 | }, []);
94 |
95 | const toggleReaction = async (emoji) => {
96 | let reactions = postReactions;
97 | const user = await getAuthenticatedUser();
98 | const existingReaction = reactions.filter(r => (r.emoji === emoji && r.by === user.login))
99 |
100 | if (existingReaction.length === 0) {
101 | const reactionToAdd = {
102 | by: user.login,
103 | emoji: emoji,
104 | }
105 |
106 | // Add the reaction
107 | await userClient(userToken).mutate({
108 | mutation: gql`
109 | mutation AddReaction {
110 | addReaction(input:{subjectId:"${postNodeId}",content:${getNameByEmoji(emoji)},clientMutationId:"${user.node_id}"}) {
111 | reaction {
112 | id
113 | }
114 | }
115 | }
116 | `
117 | });
118 |
119 | reactions.push(reactionToAdd);
120 | } else {
121 | // Remove the reaction
122 | await userClient(userToken).mutate({
123 | mutation: gql`
124 | mutation RemoveReaction {
125 | removeReaction(input:{subjectId:"${postNodeId}",content:${getNameByEmoji(emoji)},clientMutationId:"${user.node_id}"}) {
126 | reaction {
127 | id
128 | }
129 | }
130 | }
131 | `
132 | });
133 |
134 | // Remove the reaction from the state
135 | reactions = reactions.filter(r => !(r.by === user.login && r.emoji === emoji))
136 | }
137 |
138 | setPostReactions(reactions);
139 | reactionsContainer.current.forceUpdate(); // refresh the counter
140 | setReactionPopup(false); // hiding the reactions choice
141 | }
142 |
143 | useEffect(() => {
144 | if (!loading) {
145 | if (data) {
146 | const issues = data.repository.issue;
147 | setPostNodeId(issues.id);
148 | setPost(issues);
149 | setReactionFun(issues.reactions.nodes);
150 | setPostComments(issues.comments.nodes);
151 | }
152 | }
153 | }, [loading, error, data, setReactionFun]);
154 |
155 | if (loading) {
156 | return ;
157 | }
158 |
159 | const onBackClick = () => {
160 | // ifthe previous page does not exist in the history list. this method to load the previous (or next) URL in the history list.
161 | window.history.go();
162 | // The back() method loads the previous URL in the history list.
163 | window.history.back();
164 | };
165 |
166 | return (
167 | <>
168 | {post.title && (
169 |
170 | onBackClick()}>Back
171 |
172 | {post.title}
173 |
174 |
175 |
176 |
177 |
{post.author.login}
178 |
179 | {moment(post.updatedAt).format("DD MMM YYYY")} .{readingTime(post.body).minutes} Min Read .
180 |
181 | View On Github
182 |
183 |
184 |
185 |
186 |
187 |
199 | {post.body}
200 |
201 | {reactionPopup && (
202 |
203 | {userToken
204 | ? toggleReaction(emoji)} />
205 | :
206 | }
207 |
208 | )}
209 | toggleReaction(emoji)}
213 | onAdd={() => setReactionPopup(!reactionPopup)}
214 | />
215 |
216 |
217 | )}
218 | >
219 | );
220 | }
221 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | React Blog
3 |
4 |
5 |
6 | React + Github Issues 👉 Your Personal Blog 🔥
7 |
8 |
9 |
10 | React Blog is a personal blog system build on React that helps you create your own personal blog using Github Issues
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | ---
29 |
30 | ## :fire: Features
31 |
32 | :white_check_mark: Own your content \
33 | :white_check_mark: Write using Markdown On Github Issues \
34 | :white_check_mark: Syntax/Code Highlighting \
35 | :white_check_mark: Fully customizable \
36 | :white_check_mark: Tags - Topics \
37 | :white_check_mark: Links \
38 | :white_check_mark: Reactions \
39 | :white_check_mark: View Comments \
40 | :white_check_mark: Images \
41 | :white_check_mark: Minutes Read \
42 | :white_check_mark: Beautiful UI Like Medium \
43 | :white_check_mark: Easy deployment: Using Github Pages \
44 | :white_check_mark: Instant Effects on Blog when changing github issues \
45 | :white_check_mark: Beautiful blockquote
46 |
47 |
48 | ## :link: Live Demo
49 |
50 | Here's a [live demo](https://saadpasta.github.io/react-blog-github/#/)
51 |
52 | Github [Issues / Blogs](https://github.com/saadpasta/react-blog-github/issues)
53 |
54 |
55 | ---
56 |
57 | ## 🚀 Get Up and Running in 10 Minutes
58 | You can get a react-blog site up and running on your local dev environment in 10 minutes with these five steps:
59 |
60 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
61 |
62 | You'll need [Git](https://git-scm.com) and [Node.js](https://nodejs.org/en/download/) (which comes with [npm](http://npmjs.com)) installed on your computer
63 |
64 | ```
65 | node@v10.16.0 or higher
66 | npm@6.9.0 or higher
67 | git@2.17.1 or higher
68 |
69 | ```
70 |
71 | ### 1. From your command line, clone and run react-blog-github:
72 |
73 | ```bash
74 | # Clone this repository
75 | $ git clone https://github.com/saadpasta/react-blog-github.git
76 |
77 | # Go into the repository
78 | $ cd react-blog-github
79 |
80 | # Install dependencies
81 | $ npm install
82 |
83 | ```
84 |
85 | ### 2. **Generate a Github Personal Access Token.**
86 |
87 | Generate a Github personal access token using these [Instructions](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) `Make sure you don't select any scope just generate a simple token without any scope`
88 |
89 | After that copy your generated token and then encode your token into base 64 using this [Website](http://www.utilities-online.info/base64/)
90 |
91 | `Copy your base64 converted token`
92 |
93 | ### 3. Create a GitHub App
94 |
95 | Go on the following URL: https://github.com/settings/apps/new
96 |
97 | There are a few fields to fill carefully:
98 | - Homepage URL: https://your_username.github.io/react-blog-github/
99 | - Callback URL: https://your_username.github.io/react-blog-github/
100 | - Check "Request user authorization (OAuth) during installation"
101 |
102 | In permissions & events, set `Read & Write` on Issues
103 | In "Install App", install the app on your user and select your repository
104 |
105 | ### 4. **Change `/src/config.js`.**
106 | Go to your cloned repository and make the following changes in `/src/config.js`
107 |
108 | ```javascript
109 | export const config = {
110 | // Your Github Converted Token To Know How To Get Your Token Look at Readme.md
111 | githubConvertedToken: "Your token here",
112 |
113 | // Your Github UserName
114 | githubUserName: "Your username here",
115 |
116 | // Your Github Repo Name Where You Have your issues as Blog
117 | githubRepo: "Your repo's name here",
118 |
119 | // Set it to true if you have a Github app to add to this project
120 | // and fill the client ID & secret
121 | enableOAuth: true,
122 | OAuthClientID: 'Github App Client ID',
123 | OAuthSecret: 'Github App Secret',
124 |
125 | // Your Personal Blog Title
126 | title : "Title of your blog" ,
127 |
128 | // Your Personal Blog Subtitle
129 | subtitle : "Subtitle of your blog",
130 |
131 | // Header customization
132 | header: {
133 | backgroundColor: '#f1f6f8', // can be a CSS gradient
134 | titleColor: '#ff5252',
135 | subtitleColor: '#37474f',
136 | },
137 | };
138 | ```
139 |
140 | ### 5. **Write A Blog.**
141 | After doing following changes now you just need to write a blog on repository issues that you have mentioned in your `config.js`
142 |
143 | - Open your repository [github issues](https://github.com/saadpasta/react-blog-github/issues)
144 | - Create a new issue
145 | - Now write your blog in github issue in markdown. You can also use [slack edit](https://stackedit.io/app#) to write your markdown
146 | - Add a label `blog` on your github issue to make it different from other standard issues. Keep in mind `blog` label is mandatory otherwise it won't show on your website.
147 |
148 | ### 6. **Start Your React App.**
149 | That's it now you just need to start your react application From your command line run .
150 |
151 | ```bash
152 | # Run
153 | $ npm start
154 |
155 | ```
156 |
157 | ---
158 |
159 | ## 🛠️ Technologies used
160 | This project is only possible thanks to the awesomeness of the following projects:
161 |
162 | - [React](https://reactjs.org/)
163 | - [graphql](https://graphql.org/)
164 | - [apollo-boost](https://www.apollographql.com/docs/react/get-started/)
165 | - [GitHub](https://github.com)
166 | - [markdown-to-jsx](https://probablyup.com/markdown-to-jsx/)
167 | - [React Syntax Highlighter](https://github.com/conorhastings/react-syntax-highlighter)
168 | - [react-reactions](https://casesandberg.github.io/react-reactions/)
169 |
170 |
171 | ## :seedling: Inspiration
172 | This project is inspired from many other similar projects.
173 |
174 | - [gatsby-theme-blog-with-github](https://github.com/mddanishyusuf/gatsby-theme-blog-with-github)
175 | - [utterances](https://github.com/utterance/utterances)
176 |
177 |
178 |
179 | ## 🤝 Contributing
180 |
181 | Any idea on how we can make this more awesome ? [Open a new issue](https://github.com/saadpasta/react-blog-github/issues)! We need all the help we can get to make this project awesome!
182 |
183 | ## :boom: Todo
184 | - Add Comment From Github
185 | - Authentication using Github
186 | - Footer
187 | - Header
188 | - Social Media Sharing
189 |
190 |
191 | ## 📄 License
192 |
193 | This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE) file for details
194 |
195 | ## Contributors ✨
196 |
197 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
198 |
199 |
200 |
201 |
202 |
217 |
218 |
219 |
220 |
221 |
222 |
223 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
224 |
--------------------------------------------------------------------------------