├── 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 | 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 | 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 | 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 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 |

SyedMuhammadHammadGhani

📖

Saad Pasta

💻 📖 🎨 🚧

Jason Van Malder

💻 🚇

Slim Coder

💻

waleed345

💻

Abhishek Kashyap

💻

Zebra

💻 🤔

Jaeyeon Kim

💻

Parthiv

📖
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 | --------------------------------------------------------------------------------