├── .gitignore
├── README.md
├── components
├── CodeBlock.js
├── Error.js
├── Filters.js
├── Footer.js
├── Layout.js
├── SearchBar.js
└── Tags.js
├── netlify.toml
├── next.config.js
├── now.json
├── package.json
├── pages
├── 404.js
├── _app.js
├── _error.js
├── index.js
├── posts
│ └── [slug].js
└── uncopyright.js
├── posts
├── code-styles.md
├── hello-world.md
└── styles.md
├── public
├── images
│ ├── alien.svg
│ └── sorry.svg
└── og
│ └── default.png
├── styles
└── base.css
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # Snowpack dependency directory (https://snowpack.dev/)
45 | web_modules/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 | .parcel-cache
78 |
79 | # Next.js build output
80 | .next
81 | out
82 |
83 | # Nuxt.js build / generate output
84 | .nuxt
85 | dist
86 |
87 | # Gatsby files
88 | .cache/
89 | # Comment in the public line in if your project uses Gatsby and not Next.js
90 | # https://nextjs.org/blog/next-9-1#public-directory-support
91 | # public
92 |
93 | # vuepress build output
94 | .vuepress/dist
95 |
96 | # Serverless directories
97 | .serverless/
98 |
99 | # FuseBox cache
100 | .fusebox/
101 |
102 | # DynamoDB Local files
103 | .dynamodb/
104 |
105 | # TernJS port file
106 | .tern-port
107 |
108 | # Stores VSCode versions used for testing VSCode extensions
109 | .vscode-test
110 |
111 | # yarn v2
112 | .yarn/cache
113 | .yarn/unplugged
114 | .yarn/build-state.yml
115 | .yarn/install-state.gz
116 | .pnp.*
117 |
118 | # dev files
119 | .now
120 | .next
121 |
122 | # local system files
123 | .DS_Store
124 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nebula
2 |
3 | A modern blog template writen in Next.js
4 |
5 | Features:
6 |
7 | - Write posts in markdown
8 | - Search/Filter posts by tags on homepage
9 | - Neat and clean modern UI
10 | - Beautiful Light and Dark theme with preference awareness
11 | - Code syntax highlighting with specific line change highlighting featues
12 | - Reading progress indicator
13 | - Awesome [icon set](https://react-icons.github.io/react-icons/)
14 | - Ready to ship with [vercel](https://vercel.com/)(previously known as `now`)
15 |
16 | ---
17 |
18 | ## How to use this project
19 |
20 | After forking/cloning the project run:
21 |
22 | ```bash
23 | # running with yarn
24 | yarn # to intall dependencies
25 | yarn dev # to run development server
26 |
27 | # running with npm
28 | npm install # to intall dependencies
29 | npm run dev # to run development server
30 | ```
31 |
32 | ---
33 |
34 | ## Development
35 |
36 | ToDo:
37 |
38 | - Text search
39 | - Make it PWA
40 | - Blog series with multiple posts
41 | - Add Disqus for comment section
42 | - Implement tests (should have done it sooner)
43 |
44 | ---
45 |
46 | ## Acknowledgement
47 |
48 | This repo is a fork of this wonderful [blog](https://github.com/telmogoncalves/telmo) by [Telmo](https://telmo.im). I am using [React-icons](https://react-icons.github.io/react-icons/) which is another very useful project. I'm also using illustrations from [undraw](https://undraw.co/illustrations). This repo would not be possible without these wonderful opensource projects, I'm grateful to the opensource community.
49 |
50 | ## License
51 |
52 | I'm not using any license in this repository, there's a section named [uncopyright](https://nebula-blog.netlify.app/uncopyright) in the blog, and I claim no copyright of this content or source code. Credit is appreciated but not required.
53 |
--------------------------------------------------------------------------------
/components/CodeBlock.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import SyntaxHighlighter from "react-syntax-highlighter";
3 | import { shadesOfPurple } from "react-syntax-highlighter/dist/esm/styles/hljs";
4 | import { CopyToClipboard } from "react-copy-to-clipboard";
5 |
6 | const preStyle = {
7 | borderRadius: "0.25rem",
8 | padding: "1.4rem",
9 | lineHeight: "1.6rem",
10 | };
11 |
12 | const codeProps = {
13 | style: {
14 | fontFamily: `ibm-plex-mono, Consolas, Monaco, 'Lucida Console', 'Liberation Mono', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New'`,
15 | fontSize: "0.8rem",
16 | },
17 | };
18 |
19 | class CodeBlock extends PureComponent {
20 | constructor(props) {
21 | super(props);
22 |
23 | this.state = {
24 | removeLines: [],
25 | addLines: [],
26 | updateLines: [],
27 | copiedToClipboard: false,
28 | displayCopyButton: false,
29 | };
30 | }
31 |
32 | copyToClipboard = () => {
33 | this.setState(
34 | {
35 | ...this.state,
36 | copiedToClipboard: true,
37 | },
38 | () => {
39 | setTimeout(() => {
40 | this.setState({
41 | ...this.state,
42 | copiedToClipboard: false,
43 | });
44 | }, 3500);
45 | }
46 | );
47 | };
48 |
49 | componentDidMount() {
50 | const { language } = this.props;
51 | const linesObj = language && language.split(":")[1];
52 |
53 | if (linesObj) {
54 | const splittedValues = linesObj.split(",");
55 | let stateLabel;
56 | let linesToUpdate = {
57 | removeLines: [],
58 | addLines: [],
59 | updateLines: [],
60 | };
61 |
62 | splittedValues.map((lines) => {
63 | const linesRange = lines.split(",");
64 |
65 | linesRange.map((eachLine) => {
66 | const splitted = eachLine.split("-");
67 |
68 | if (splitted[0] === "") {
69 | // Is removing lines
70 | splitted.shift();
71 | stateLabel = "removeLines";
72 | } else if (splitted[0] === "!") {
73 | splitted.shift();
74 | stateLabel = "updateLines";
75 | } else {
76 | stateLabel = "addLines";
77 | }
78 |
79 | if (splitted.length > 1) {
80 | for (
81 | let i = parseInt(splitted[0]);
82 | i <= parseInt(splitted[1]);
83 | i++
84 | ) {
85 | linesToUpdate[stateLabel].push(i);
86 | }
87 | } else {
88 | // Only one liner
89 | linesToUpdate[stateLabel].push(parseInt(splitted[0]));
90 | }
91 |
92 | this.setState({
93 | [stateLabel]: [
94 | ...this.state[stateLabel],
95 | ...linesToUpdate[stateLabel],
96 | ],
97 | });
98 | });
99 | });
100 | }
101 | }
102 |
103 | toggleCopyButton = () => {
104 | this.setState({
105 | ...this.state,
106 | displayCopyButton: !this.state.displayCopyButton,
107 | });
108 | };
109 |
110 | render() {
111 | const { language, value } = this.props;
112 | const {
113 | addLines,
114 | removeLines,
115 | updateLines,
116 | copiedToClipboard,
117 | displayCopyButton,
118 | } = this.state;
119 |
120 | return (
121 |
this.toggleCopyButton()}
123 | onMouseLeave={() => this.toggleCopyButton()}
124 | >
125 |
{
132 | const mergedLines = addLines
133 | .concat(removeLines)
134 | .concat(updateLines);
135 | let style = { display: "block" };
136 |
137 | if (mergedLines.includes(lineNumber)) {
138 | style = {
139 | ...style,
140 | margin: "0 -22px",
141 | padding: "3px 12px 6px",
142 | };
143 | }
144 |
145 | if (removeLines.includes(lineNumber)) {
146 | style = {
147 | ...style,
148 | borderLeft: `6px #f00080 solid`,
149 | background: `rgba(240, 0, 128, .2)`,
150 | };
151 | } else if (addLines.includes(lineNumber)) {
152 | style = {
153 | ...style,
154 | borderLeft: `6px #3ac569 solid`,
155 | background: `rgba(58, 197, 105, .2)`,
156 | };
157 | } else if (updateLines.includes(lineNumber)) {
158 | style = {
159 | ...style,
160 | borderLeft: `6px #f0db4f solid`,
161 | background: `rgba(240, 219, 79, .2)`,
162 | };
163 | }
164 |
165 | return { style };
166 | }}
167 | >
168 | {value}
169 |
170 |
171 |
172 | this.copyToClipboard()}>
173 |
174 |
175 |
176 |
177 | );
178 | }
179 | }
180 |
181 | export default CodeBlock;
182 |
--------------------------------------------------------------------------------
/components/Error.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Link from "next/link";
3 |
4 | import Layout from "./Layout";
5 |
6 | const ErrorLayout = ({ title, image, description }) => (
7 |
8 |
9 |
{title}
10 |
11 |
12 | {description}
13 |
14 | You can go back to{" "}
15 |
16 | home page
17 |
18 | !
19 |
20 |
21 | {image && (
22 |

27 | )}
28 |
29 |
30 | );
31 |
32 | export default ErrorLayout;
33 |
--------------------------------------------------------------------------------
/components/Filters.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Filters = ({ filters, filterDispatcher }) => (
4 |
5 |
Filter:
6 | {[...filters].map((filter) => (
7 |
11 | filterDispatcher({ type: "REMOVE_FILTER", filter: filter })
12 | }
13 | >
14 | {filter}
15 |
16 | ))}
17 |
23 |
24 | );
25 |
26 | export default Filters;
27 |
--------------------------------------------------------------------------------
/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | FiCoffee as Coffee,
4 | FiCode as Code,
5 | FiHeart as Heart,
6 | } from "react-icons/fi";
7 | import Link from "next/link";
8 |
9 | const Footer = () => (
10 |
29 | );
30 |
31 | export default Footer;
32 |
--------------------------------------------------------------------------------
/components/Layout.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { RiMoonClearLine as Moon } from "react-icons/ri";
3 | import { FiSun as Sun } from "react-icons/fi";
4 | import Link from "next/link";
5 |
6 | import Footer from "./Footer";
7 |
8 | function Layout({ children }) {
9 | const onLoadTheme =
10 | typeof localStorage !== "undefined" && localStorage.getItem("BLOG_THEME");
11 | const [theme, setTheme] = useState(onLoadTheme);
12 | const [mounted, setMounted] = useState(false);
13 | const switchTheme = () => {
14 | const setTo = theme === "dark" ? "light" : "dark";
15 |
16 | setTheme(setTo);
17 | };
18 |
19 | useEffect(() => {
20 | if (onLoadTheme) return;
21 |
22 | if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
23 | setTheme("dark");
24 | }
25 | }, []);
26 |
27 | useEffect(() => {
28 | document.documentElement.setAttribute("data-theme", theme);
29 |
30 | localStorage.setItem("BLOG_THEME", theme);
31 |
32 | setMounted(true);
33 | }, [theme]);
34 |
35 | if (!mounted) return ;
36 |
37 | return (
38 | <>
39 |
40 |
41 |
Nebula
42 |
43 |
44 |
51 |
52 |
53 | {children}
54 |
55 |
56 | >
57 | );
58 | }
59 |
60 | export default Layout;
61 |
--------------------------------------------------------------------------------
/components/SearchBar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { FiSearch } from "react-icons/fi";
3 |
4 | const SearchBar = ({ onSearch }) => {
5 | return (
6 |
7 |
8 | {
13 | e.key === "Enter" && onSearch();
14 | }}
15 | placeholder="Search by tags, i.e. style"
16 | required
17 | />
18 |
21 |
22 |
23 | );
24 | };
25 |
26 | export default SearchBar;
27 |
--------------------------------------------------------------------------------
/components/Tags.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Tags = ({ tags, onClick, withTitle = false }) => (
4 |
5 | {withTitle &&
Tags:}
6 | {tags.map((tag) => (
7 |
onClick(tag)}>
8 | {tag}
9 |
10 | ))}
11 |
12 | );
13 |
14 | export default Tags;
15 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | base = "."
3 | publish = "./out"
4 | command = "npm run export"
5 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withSass = require("@zeit/next-sass");
2 | const withCSS = require("@zeit/next-css");
3 | const withTM = require("next-transpile-modules");
4 |
5 | module.exports = withCSS(
6 | withSass(
7 | withTM({
8 | transpileModules: ["react-syntax-highlighter"],
9 | webpack: function (config) {
10 | config.module.rules.push({
11 | test: /\.md$/,
12 | use: "raw-loader",
13 | });
14 | return config;
15 | },
16 | })
17 | )
18 | );
19 |
--------------------------------------------------------------------------------
/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "alias": "nebula.now.sh",
3 | "headers": [{
4 | "source": "/(.*)",
5 | "headers": [{
6 | "key": "Cache-Control",
7 | "value": "dist, s-maxage=1, stale-while-revalidate"
8 | },
9 | {
10 | "key": "Access-Control-Allow-Origin",
11 | "value": "*"
12 | }
13 | ]
14 | }]
15 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nebula",
3 | "version": "1.0.0",
4 | "description": "A modern Next.js blog theme",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "next",
8 | "start": "next start",
9 | "build": "next build",
10 | "export": "next build && next export"
11 | },
12 | "author": "Samnan Rahee",
13 | "license": "MIT",
14 | "dependencies": {
15 | "@zeit/next-css": "^1.0.1",
16 | "@zeit/next-sass": "^1.0.1",
17 | "glob": "^7.1.6",
18 | "gray-matter": "^4.0.2",
19 | "next": "^9.4",
20 | "next-transpile-modules": "^2.3.1",
21 | "raw-loader": "^4.0.1",
22 | "react": "^16.13.1",
23 | "react-copy-to-clipboard": "^5.0.2",
24 | "react-dom": "^16.13.1",
25 | "react-icons": "^3.10.0",
26 | "react-markdown": "^4.3.1",
27 | "react-scroll-progress-bar": "^1.1.12",
28 | "react-syntax-highlighter": "^12.2.1",
29 | "react-text-transition": "^1.0.2",
30 | "simple-icons": "^2.9.0"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/pages/404.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import ErrorLayout from "../components/Error";
4 |
5 | const Error = () => {
6 | const err404 = { src: "/images/alien.svg", alt: "lost in space" };
7 |
8 | return (
9 |
13 | );
14 | };
15 |
16 | export default Error;
17 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 |
3 | import '../styles/base.css'
4 |
5 | function MyApp({ Component, pageProps }) {
6 | const og = pageProps.data?.og
7 | const title = pageProps.data?.title
8 |
9 | return (
10 | <>
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | {title || `Nebula`}
22 |
23 |
24 |
25 | >
26 | )
27 | }
28 |
29 | export default MyApp
30 |
--------------------------------------------------------------------------------
/pages/_error.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import ErrorLayout from "../components/Error";
4 |
5 | const Error = ({ statusCode }) => {
6 | const err404 = { src: "/images/alien.svg", alt: "lost in space" };
7 | const err500 = { src: "/images/sorry.svg", alt: "we're sad and sorry" };
8 | if (statusCode === 404) {
9 | return (
10 |
14 | );
15 | } else if (statusCode === 500) {
16 | return (
17 |
21 | );
22 | } else {
23 | return (
24 | <>
25 | {statusCode ? (
26 |
30 | ) : (
31 |
35 | )}
36 | >
37 | );
38 | }
39 | };
40 |
41 | Error.getInitialProps = ({ res, err }) => {
42 | const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
43 | return { statusCode };
44 | };
45 |
46 | export default Error;
47 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useReducer, useEffect } from "react";
2 | import matter from "gray-matter";
3 | import Link from "next/link";
4 |
5 | import Layout from "../components/Layout";
6 | import Tags from "../components/Tags";
7 | import Filters from "../components/Filters";
8 | import SearchBar from "../components/SearchBar";
9 |
10 | function formatDate(date) {
11 | const options = { year: "numeric", month: "long", day: "numeric" };
12 | const today = new Date(date);
13 |
14 | return today.toLocaleDateString("en-US", options);
15 | }
16 |
17 | function freshWriting(date) {
18 | const writingDate = new Date(date).getTime();
19 | const today = new Date().getTime();
20 |
21 | return today - writingDate < 60 * 60 * 1000 * 24 * 2; // 2 days old
22 | }
23 |
24 | const Homepage = (props) => {
25 | const posts = props.posts;
26 |
27 | const [visiblePosts, setVisiblePosts] = useState(posts);
28 | const filterReducer = (state = new Set([]), action) => {
29 | switch (action.type) {
30 | case "ADD_FILTER":
31 | return new Set([...state, action.filter.trim()]);
32 | case "REMOVE_FILTER":
33 | return new Set([...state].filter((filter) => filter !== action.filter));
34 | case "RESET_FILTER":
35 | return new Set([]);
36 | default:
37 | return state;
38 | }
39 | };
40 |
41 | const [filters, filterDispatcher] = useReducer(filterReducer, new Set([]));
42 |
43 | const searchAction = () => {
44 | const searchInput = document.getElementById("search-input");
45 |
46 | if (searchInput.value.length !== 0) {
47 | searchInput.value.split(",").map((filter) => {
48 | filterDispatcher({ type: "ADD_FILTER", filter: filter });
49 | });
50 | searchInput.value = "";
51 | }
52 | };
53 |
54 | useEffect(() => {
55 | if (filters.size !== 0) {
56 | setVisiblePosts(
57 | [...posts].filter(({ document }) => {
58 | let flag = true;
59 | [...filters].map((filter) => {
60 | if (!document.data.tags.split(",").includes(filter)) flag = false;
61 | });
62 | return flag;
63 | })
64 | );
65 | } else {
66 | setVisiblePosts(posts);
67 | }
68 | }, [filters]);
69 |
70 | return (
71 | <>
72 |
73 |
74 |
75 | {filters.size !== 0 && (
76 |
77 | )}
78 |
79 |
80 | {visiblePosts.map(({ document, slug }) => {
81 | const {
82 | data: { title, date, tags, og },
83 | } = document;
84 | const tagItems = tags.split(",");
85 |
86 | return (
87 |
88 |
89 |
90 |
91 |
{title}
92 |
{og.description}
93 |
94 |
95 |
96 |
97 |
98 |
{
101 | filterDispatcher({
102 | type: "ADD_FILTER",
103 | filter: filter,
104 | });
105 | }}
106 | />
107 |
108 |
109 |
{formatDate(date)}
110 | {freshWriting(date) &&
}
111 |
112 |
113 |
114 | );
115 | })}
116 |
117 |
118 | >
119 | );
120 | };
121 |
122 | export async function getStaticProps() {
123 | const posts = ((context) => {
124 | const keys = context.keys();
125 | const values = keys.map(context);
126 |
127 | const data = keys.map((key, index) => {
128 | const slug = key
129 | .replace(/^.*[\\\/]/, "")
130 | .split(".")
131 | .slice(0, -1)
132 | .join(".");
133 |
134 | const value = values[index];
135 | const document = matter(value.default);
136 |
137 | return {
138 | document: { content: document.content, data: document.data },
139 | slug,
140 | };
141 | });
142 |
143 | return data
144 | .slice()
145 | .sort(
146 | (a, b) =>
147 | new Date(b.document.data.date) - new Date(a.document.data.date)
148 | );
149 | })(require.context("../posts", true, /\.md$/));
150 |
151 | const props = { posts: posts };
152 | // console.log(props);
153 | return {
154 | props: props,
155 | };
156 | }
157 |
158 | export default Homepage;
159 |
--------------------------------------------------------------------------------
/pages/posts/[slug].js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import matter from "gray-matter";
3 | import ReactMarkdown from "react-markdown";
4 | import ProgressBar from "react-scroll-progress-bar";
5 | import { glob } from "glob";
6 |
7 | import Layout from "../../components/Layout";
8 | import Tags from "../../components/Tags";
9 | import CodeBlock from "../../components/CodeBlock";
10 |
11 | function Writing({ content, data }) {
12 | const frontmatter = data;
13 | const { title, author, tags } = frontmatter;
14 | const tagItems = tags.split(",");
15 |
16 | return (
17 | <>
18 |
21 |
22 |
23 |
24 |
{title}
25 |
26 |
{author.name}
27 |
28 |
29 |
{
35 | if (!props.href.startsWith("http")) {
36 | return props.href;
37 | }
38 |
39 | return (
40 |
45 | {props.children}
46 |
47 | );
48 | },
49 | }}
50 | />
51 |
52 |
53 |
54 |
55 |
56 | >
57 | );
58 | }
59 |
60 | export async function getStaticProps({ ...context }) {
61 | const { slug } = context.params;
62 | const content = await import(`../../posts/${slug}.md`);
63 | const data = matter(content.default);
64 |
65 | return {
66 | props: {
67 | data: data.data,
68 | content: data.content,
69 | },
70 | };
71 | }
72 |
73 | export async function getStaticPaths() {
74 | const posts = glob.sync("posts/*.md");
75 | const slugs = posts.map((slug) =>
76 | slug.split("/")[1].replace(/ /g, "-").slice(0, -3).trim()
77 | );
78 |
79 | const paths = slugs.map((slug) => `/posts/${slug}`);
80 |
81 | return {
82 | paths,
83 | fallback: false,
84 | };
85 | }
86 |
87 | export default Writing;
88 |
--------------------------------------------------------------------------------
/pages/uncopyright.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import Layout from "../components/Layout";
4 |
5 | const Uncopyright = () => (
6 |
7 |
8 |
Uncopyright
9 |
10 |
11 |
12 | No permission is needed to copy, distribute, or modify the content or
13 | source of this site. Credit is appreciated but not required.
14 |
15 |
16 |
17 | Terms and Conditions for Copying, Distribution and Modification:
18 |
19 |
20 | - Do whatever the hell you like.
21 |
22 |
23 |
24 |
25 | );
26 |
27 | export default Uncopyright;
28 |
--------------------------------------------------------------------------------
/posts/code-styles.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "How to style code blocks"
3 | date: "2020-06-05"
4 | og:
5 | description: "All about styling your code! This is a short description that summarizes this cool post."
6 | image: ""
7 | author:
8 | name: "Samnan Rahee"
9 | tags: "code,style,blog"
10 | ---
11 |
12 | This is where we start:
13 |
14 | ```py
15 | print("hello world!")
16 |
17 | # this is a comment
18 | def just_a_function():
19 | return "hello world"
20 |
21 | just_a_function() #inline comment
22 | ```
23 |
24 | Let's add some code:
25 |
26 | ```py:7-8
27 | print("hello world!")
28 |
29 | # this is a comment
30 | def just_a_function():
31 | return "hello world"
32 |
33 | def another_function():
34 | return "something else"
35 |
36 | just_a_function() #inline comment
37 | ```
38 |
39 | Whoa! How to do that? Start your code block with this: `py:7-8`. Now let's update some code:
40 |
41 | ```py:!-8
42 | print("hello world!")
43 |
44 | # this is a comment
45 | def just_a_function():
46 | return "hello world"
47 |
48 | def another_function():
49 | return "updated version"
50 |
51 | just_a_function() #inline comment
52 | ```
53 |
54 | That's cool! Do this by writing `py:!-8`. Let's remove the code highlighted in red:
55 |
56 | ```py:-3-5,-10
57 | print("hello world!")
58 |
59 | # this is a comment
60 | def just_a_function():
61 | return "hello world"
62 |
63 | def another_function():
64 | return "updated version"
65 |
66 | just_a_function() #inline comment
67 | ```
68 |
69 | You can do this by starting your code block with `py:-3-5,-10`. This is the final code:
70 |
71 | ```py
72 | print("hello world!")
73 |
74 | def another_function():
75 | return "updated version"
76 |
77 | ```
78 |
--------------------------------------------------------------------------------
/posts/hello-world.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Hello world!"
3 | date: "2020-05-05"
4 | og:
5 | description: "Testing testing"
6 | image: ""
7 | author:
8 | name: "Samnan Rahee"
9 | tags: "hello,world,blog"
10 | ---
11 |
12 | Hello world! This is the very first post of this blog!
13 |
--------------------------------------------------------------------------------
/posts/styles.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Styles"
3 | date: "2020-06-02"
4 | og:
5 | description: "All about styles"
6 | image: ""
7 | author:
8 | name: "Samnan Rahee"
9 | tags: "style"
10 | ---
11 |
12 | # Heading 1
13 |
14 | ## Heading 2
15 |
16 | ### Heading 3
17 |
18 | #### Heading 4
19 |
20 | ##### Heading 5
21 |
22 | ##### Heading 6
23 |
24 | This is a list:
25 |
26 | - Item 1
27 | - Item 2
28 | - Item 2.1
29 | - Item 2.2
30 |
31 | > This is a qoute
32 |
33 | Blockquotes can be written like this:
34 | > Whoa this is an awesome very long quote about how
35 | > to write blockqoutes in markdown format! I love to
36 | > learn how this works! This is so amazing!
37 | >
38 | > — Someone random
39 |
40 | This is about several styles:
41 |
42 | - _italic text_
43 | - **bold text**
44 | - _**bold italic text**_
45 | - [link](https://example.com)
46 |
47 | A long paragraph with random text. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Quis lectus nulla at volutpat diam ut venenatis tellus. Molestie a iaculis at erat pellentesque. Maecenas ultricies mi eget mauris. Suspendisse sed nisi lacus sed viverra tellus. Volutpat ac tincidunt vitae semper. Imperdiet dui accumsan sit amet nulla facilisi morbi tempus iaculis. Lectus quam id leo in vitae. Donec massa sapien faucibus et molestie ac feugiat sed lectus. Eget arcu dictum varius duis at consectetur lorem donec. Pellentesque habitant morbi tristique senectus et. Cursus euismod quis viverra nibh. Nibh nisl condimentum id venenatis. Arcu non odio euismod lacinia. Dignissim enim sit amet venenatis urna.
48 |
49 | This is an `inline code`. Code blocks can be written like this:
50 |
51 | ```py
52 | print("hello world!")
53 |
54 | # this is a comment
55 | def just_a_function():
56 | return "hello world"
57 |
58 | # calling a function
59 | just_a_function() #inline comment
60 | ```
61 |
62 | This is a horizontal divider:
63 |
64 | ---
65 |
--------------------------------------------------------------------------------
/public/images/alien.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/sorry.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/og/default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YAS-opensource/nebula/e45bd4eff73531becefdf6114dfba30470406dbb/public/og/default.png
--------------------------------------------------------------------------------
/styles/base.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --secondary-color: #88c;
3 | --bg-color: #f5f7fe;
4 | --bg-shadow: #e5e7ef;
5 | --heading-color: #000;
6 | --highlight-color: #f0db4f;
7 | --hr-bg: url('data:image/svg+xml,');
8 | --font-color: #224;
9 | --uses-secondary-color: #bbb;
10 | --card-color: #fefeff;
11 | }
12 |
13 | [data-theme="dark"] {
14 | --secondary-color: #99f;
15 | --bg-color: #2a2f3f;
16 | --bg-shadow: #000;
17 | --heading-color: #fff;
18 | --highlight-color: #f00080;
19 | --hr-bg: url('data:image/svg+xml,');
20 | --font-color: #eef;
21 | --card-color: #1a1f2f;
22 | }
23 |
24 | ::-moz-selection {
25 | background: var(--highlight-color);
26 | color: var(--font-color);
27 | }
28 | ::selection {
29 | background: var(--highlight-color);
30 | color: var(--font-color);
31 | }
32 |
33 | html {
34 | font-family: "Inter", Arial, Helvetica, sans-serif;
35 | }
36 |
37 | body {
38 | background-color: var(--bg-color);
39 | color: var(--font-color);
40 | }
41 |
42 | * {
43 | transition: all 0.175s ease-in;
44 | }
45 |
46 | .content {
47 | width: 80vw;
48 | margin: auto;
49 | }
50 |
51 | .top-menu {
52 | padding: 3rem 6rem;
53 | display: flex;
54 | flex-direction: row;
55 | justify-content: space-between;
56 | }
57 |
58 | .logo {
59 | font-size: 1.5rem;
60 | font-weight: 700;
61 | cursor: pointer;
62 | position: relative;
63 | z-index: 0;
64 | transform: translate(1rem, 15%);
65 | }
66 |
67 | .logo::after {
68 | content: "";
69 | background-color: var(--highlight-color);
70 | position: absolute;
71 | left: -1rem;
72 | top: -25%;
73 | z-index: -1;
74 | width: 3rem;
75 | height: 130%;
76 | transition: all 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55);
77 | }
78 |
79 | .logo:hover::after {
80 | content: "";
81 | background-color: var(--highlight-color);
82 | position: absolute;
83 | left: -1rem;
84 | top: -25%;
85 | z-index: -1;
86 | width: calc(100% + 2rem);
87 | height: 130%;
88 | }
89 |
90 | .logo a {
91 | font-size: 1.2rem;
92 | text-decoration: none;
93 | margin: -5px 0 0;
94 | display: inline-block;
95 | vertical-align: middle;
96 | }
97 |
98 | .theme-switch-button {
99 | border: none;
100 | background: none;
101 | cursor: pointer;
102 | cursor: hand;
103 | color: var(--font-color);
104 | outline: 0;
105 | }
106 |
107 | .theme-switch-button svg {
108 | fill: var(--font-color);
109 | }
110 |
111 | h1,
112 | h2,
113 | h3,
114 | h4,
115 | h5,
116 | h6 {
117 | color: var(--heading-color);
118 | }
119 |
120 | .blog-title {
121 | font-weight: 700;
122 | font-size: 80px;
123 | letter-spacing: -0.3rem;
124 | text-align: center;
125 | margin: 100px 0 50px;
126 | }
127 |
128 | .writing-list {
129 | width: inherit;
130 | display: flex;
131 | flex-direction: row;
132 | flex-wrap: wrap;
133 | justify-content: space-evenly;
134 | }
135 |
136 | .writing-row span {
137 | display: inline-flex;
138 | align-items: center;
139 | }
140 |
141 | .writing-row {
142 | color: var(--font-color);
143 | position: relative;
144 | width: 18rem;
145 | height: 12rem;
146 | padding: 1rem;
147 | margin: 1rem 0;
148 | background-color: var(--card-color);
149 | border-radius: 5px;
150 | border-width: 2px;
151 | border-color: var(--font-color);
152 | border-style: solid;
153 | display: flex;
154 | flex-direction: column;
155 | transition: all 0.175s ease-in;
156 | }
157 |
158 | .writing-row::after {
159 | content: "";
160 | position: absolute;
161 | z-index: -1;
162 | top: 0.5rem;
163 | left: 0.5rem;
164 | height: 100%;
165 | width: 100%;
166 | background-color: var(--bg-shadow);
167 | border-radius: 5px;
168 | border-width: 2px;
169 | transition: all 0.175s ease-in;
170 | }
171 |
172 | .writing-row:hover {
173 | background-color: var(--bg-color);
174 | }
175 |
176 | .writing-row:hover::after {
177 | top: 0;
178 | left: 0;
179 | }
180 |
181 | .writing-row a {
182 | color: var(--font-color);
183 | }
184 |
185 | a {
186 | font-weight: 600;
187 | color: var(--secondary-color);
188 | text-decoration: none;
189 | }
190 |
191 | a:hover {
192 | color: var(--font-color);
193 | }
194 |
195 | .date-row {
196 | margin-top: 0.5rem;
197 | display: flex;
198 | flex-direction: row;
199 | align-items: center;
200 | }
201 |
202 | .writing-row .writing-date {
203 | font-size: 0.8rem;
204 | color: var(--secondary-color);
205 | margin-right: 1rem;
206 | }
207 |
208 | .writing-row .writing-title {
209 | font-weight: 600;
210 | font-size: 20px;
211 | line-height: 1.5em;
212 | }
213 |
214 | .writing-subtitle {
215 | font-size: 0.8rem;
216 | font-weight: 400;
217 | margin-top: 0.5rem;
218 | line-height: 1.2rem;
219 | }
220 |
221 | .writing-description {
222 | height: 8rem;
223 | border-bottom: solid;
224 | border-color: var(--secondary-color);
225 | border-width: 1px;
226 | margin-bottom: 1rem;
227 | overflow: hidden;
228 | }
229 |
230 | .writing-container {
231 | margin: 5rem 0;
232 | }
233 |
234 | .writing-container h1,
235 | .writing-container h2,
236 | .writing-container h3,
237 | .writing-container h4,
238 | .writing-container h5,
239 | .writing-container h6 {
240 | letter-spacing: -1px;
241 | }
242 |
243 | .writing-container img {
244 | width: 100%;
245 | }
246 |
247 | .writing-container ul {
248 | margin: 0;
249 | padding: 0 0 30px 20px;
250 | list-style: circle;
251 | }
252 |
253 | .writing-container ul li {
254 | padding: 4px 0;
255 | line-height: 1.8em;
256 | font-size: 20px;
257 | color: var(--font-color);
258 | }
259 |
260 | .writing-container blockquote {
261 | border-left: 5px var(--highlight-color) solid;
262 | font-style: italic;
263 | margin: 0;
264 | padding: 0px 30px;
265 | }
266 |
267 | .writing-container p {
268 | line-height: 1.8em;
269 | margin-bottom: 40px;
270 | font-size: 20px;
271 | color: var(--font-color);
272 | }
273 |
274 | .writing-container *:not(pre) > code {
275 | font-family: "IBM Plex Mono", Monaco, "Lucida Console", monospace;
276 | background: var(--highlight-color);
277 | color: var(--heading-color);
278 | padding: 2px 7px 5px;
279 | border-radius: 4px;
280 | line-height: 1em;
281 | font-size: 16px;
282 | display: inline-flex;
283 | }
284 |
285 | hr {
286 | height: 0.25rem;
287 | border: none;
288 | margin-bottom: 3rem;
289 | background-image: var(--hr-bg);
290 | background-size: 0.3rem 0.3rem;
291 | }
292 |
293 | .writing-container a {
294 | color: var(--heading-color);
295 | text-decoration: none;
296 | position: relative;
297 | }
298 |
299 | .writing-container a::after {
300 | content: "";
301 | position: absolute;
302 | z-index: -1;
303 | bottom: 0;
304 | left: 0;
305 | width: 100%;
306 | height: 0.5rem;
307 | background-color: var(--highlight-color);
308 | transition: height 0.175s cubic-bezier(0.65, 0.05, 0.36, 1);
309 | }
310 |
311 | .writing-container a:hover::after {
312 | height: 100%;
313 | }
314 |
315 | .copy-to-clipboard {
316 | text-align: right;
317 | padding-right: 15px;
318 | margin: -10px 0 25px;
319 | }
320 |
321 | .copy-to-clipboard button {
322 | background: none;
323 | border: none;
324 | font-size: 11px;
325 | text-transform: uppercase;
326 | font-weight: 600;
327 | color: var(--secondary-color);
328 | cursor: pointer;
329 | cursor: hand;
330 | outline: 0;
331 | padding: 0;
332 | }
333 |
334 | .copy-to-clipboard button:hover {
335 | color: var(--heading-color);
336 | }
337 |
338 | footer {
339 | display: flex;
340 | flex-direction: row;
341 | flex-wrap: wrap;
342 | justify-content: center;
343 | margin-top: 4rem;
344 | text-align: center;
345 | font-size: 0.7rem;
346 | color: var(--secondary-color);
347 | }
348 |
349 | .footer-item {
350 | padding: 0.5rem;
351 | }
352 |
353 | .author {
354 | color: var(--font-color);
355 | margin-bottom: 1.5rem;
356 | }
357 |
358 | .author::before {
359 | color: var(--secondary-color);
360 | content: "written by ";
361 | }
362 |
363 | .tags {
364 | display: flex;
365 | flex-direction: row;
366 | flex-wrap: wrap;
367 | }
368 |
369 | .tag {
370 | font-size: 0.8rem;
371 | font-weight: 600;
372 | cursor: pointer;
373 | padding: 0.2rem;
374 | color: var(--secondary-color);
375 | border-radius: 0.12rem;
376 | }
377 |
378 | .tag::before {
379 | content: "#";
380 | }
381 |
382 | .tag:hover {
383 | color: var(--highlight-color);
384 | }
385 |
386 | .tag + .tag {
387 | margin-left: 0.25rem;
388 | }
389 |
390 | .writing-row .tags {
391 | color: var(--font-color);
392 | }
393 |
394 | .search-bar {
395 | width: 92%;
396 | transform: translateX(4%);
397 | }
398 |
399 | .search-bar + .writing-list {
400 | margin-top: 2rem;
401 | }
402 |
403 | .search-field {
404 | width: 100%;
405 | position: relative;
406 | display: flex;
407 | }
408 |
409 | .search-input {
410 | font-size: 1rem;
411 | width: 100%;
412 | padding: 0.5rem 1rem;
413 | height: 1.5rem;
414 | background-color: var(--card-color);
415 | border: 2px solid var(--card-color);
416 | border-right: none;
417 | border-radius: 0.25rem 0 0 0.25rem;
418 | outline: none;
419 | }
420 |
421 | .search-input::placeholder {
422 | color: var(--secondary-color);
423 | }
424 |
425 | .search-input:focus {
426 | color: var(--font-color);
427 | border-color: var(--font-color);
428 | }
429 |
430 | .search-button {
431 | width: 2.9rem;
432 | height: 2.75rem;
433 | margin: 0;
434 | font-size: 1.5rem;
435 | line-height: 0;
436 | background: var(--font-color);
437 | color: var(--bg-color);
438 | border-radius: 0 0.25rem 0.25rem 0;
439 | cursor: pointer;
440 | }
441 |
442 | .search-button:hover {
443 | color: var(--bg-color);
444 | }
445 |
446 | .filters {
447 | display: flex;
448 | margin: 0 2.5rem 1rem 2.5rem;
449 | flex-direction: row;
450 | flex-wrap: wrap;
451 | padding: 0.5rem 1rem 0.8rem 1rem;
452 | border-radius: 0.25rem;
453 | align-items: center;
454 | }
455 |
456 | .filter-label {
457 | font-size: 0.8rem;
458 | font-weight: 600;
459 | margin-right: 1rem;
460 | }
461 |
462 | button {
463 | cursor: pointer;
464 | margin-left: 1rem;
465 | padding: 0.3rem 0.6rem;
466 | background-color: var(--highlight-color);
467 | color: var(--font-color);
468 | border: none;
469 | border-radius: 0.25rem;
470 | }
471 |
472 | button:hover {
473 | background-color: var(--font-color);
474 | color: var(--highlight-color);
475 | }
476 |
477 | .theme-switch-button:hover {
478 | color: var(--font-color);
479 | background-color: var(--bg-color);
480 | }
481 |
482 | /* Override scroll progress */
483 | .writing-progress > div {
484 | background: var(--highlight-color) !important;
485 | margin: 0 -8px !important;
486 | }
487 |
488 | .pulse {
489 | background: var(--highlight-color);
490 | border-radius: 50%;
491 | height: 10px;
492 | width: 10px;
493 | display: inline-block;
494 |
495 | box-shadow: 0 0 0 0 var(--highlight-color);
496 | transform: scale(1.2);
497 | animation: pulse 2s infinite;
498 | margin: -7px 15px 0 0;
499 | vertical-align: middle;
500 | }
501 |
502 | @keyframes pulse {
503 | 0% {
504 | transform: scale(0.95);
505 | box-shadow: 0 0 0 0 var(--highlight-color);
506 | }
507 |
508 | 70% {
509 | transform: scale(1);
510 | box-shadow: 0 0 0 10px rgba(0, 0, 0, 0);
511 | }
512 |
513 | 100% {
514 | transform: scale(0.95);
515 | box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
516 | }
517 | }
518 |
519 | .icon {
520 | font-size: 1.5rem;
521 | }
522 |
523 | .error-page {
524 | display: flex;
525 | flex-direction: column;
526 | justify-content: center;
527 | align-items: center;
528 | }
529 |
530 | .error-title {
531 | margin-bottom: 0;
532 | }
533 |
534 | .error-img {
535 | width: 20rem;
536 | height: auto;
537 | }
538 |
539 | .error-description {
540 | margin-bottom: 2rem;
541 | color: var(--secondary-color);
542 | }
543 |
544 | @media only screen and (max-width: 960px) {
545 | .content {
546 | width: inherit;
547 | padding: 1rem;
548 | }
549 |
550 | .top-menu {
551 | padding: 2rem 1rem;
552 | }
553 |
554 | .writing-row {
555 | width: 80vw;
556 | }
557 |
558 | .text-transition {
559 | display: block !important;
560 | }
561 |
562 | .filters {
563 | margin: 0;
564 | }
565 |
566 | .search-bar {
567 | width: 100%;
568 | transform: translateX(0);
569 | }
570 |
571 | footer {
572 | flex-direction: column;
573 | }
574 | }
575 |
--------------------------------------------------------------------------------