├── src ├── components │ ├── Code │ │ ├── themes │ │ │ ├── index.ts │ │ │ └── vs.ts │ │ └── index.tsx │ ├── index.ts │ ├── Title │ │ └── index.tsx │ ├── NewThought │ │ └── index.tsx │ ├── Nav │ │ └── index.tsx │ ├── Separator │ │ └── index.tsx │ ├── Container │ │ └── index.tsx │ ├── Sidenote │ │ └── index.tsx │ ├── Header │ │ └── index.tsx │ ├── Link │ │ └── index.tsx │ ├── Spinner │ │ └── index.tsx │ ├── Image │ │ └── index.tsx │ ├── Paragraph │ │ └── index.tsx │ └── Markdown │ │ └── index.tsx ├── react-app-env.d.ts ├── constants.ts ├── models │ └── Post.ts ├── index.tsx ├── App.tsx ├── views │ ├── 404.tsx │ ├── Home.tsx │ └── Post.tsx ├── fonts │ ├── Charter.ts │ └── FiraCode.ts ├── contexts │ └── Database.tsx ├── types │ └── index.d.ts └── admonition.css ├── .vscode └── settings.json ├── public ├── fonts │ ├── CharterBold.ttf │ ├── CharterItalic.ttf │ ├── CharterRegular.ttf │ ├── FiraCode-Bold.ttf │ ├── FiraCode-Light.ttf │ ├── FiraCode-Medium.ttf │ ├── CharterBoldItalic.ttf │ └── FiraCode-Regular.ttf └── index.html ├── .prettierrc ├── .gitignore ├── tsconfig.json ├── package.json └── README.md /src/components/Code/themes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './vs' 2 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const DB_URL = process.env.REACT_APP_DB_URL 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /public/fonts/CharterBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARE/blog.iama.re/master/public/fonts/CharterBold.ttf -------------------------------------------------------------------------------- /public/fonts/CharterItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARE/blog.iama.re/master/public/fonts/CharterItalic.ttf -------------------------------------------------------------------------------- /public/fonts/CharterRegular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARE/blog.iama.re/master/public/fonts/CharterRegular.ttf -------------------------------------------------------------------------------- /public/fonts/FiraCode-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARE/blog.iama.re/master/public/fonts/FiraCode-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/FiraCode-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARE/blog.iama.re/master/public/fonts/FiraCode-Light.ttf -------------------------------------------------------------------------------- /public/fonts/FiraCode-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARE/blog.iama.re/master/public/fonts/FiraCode-Medium.ttf -------------------------------------------------------------------------------- /public/fonts/CharterBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARE/blog.iama.re/master/public/fonts/CharterBoldItalic.ttf -------------------------------------------------------------------------------- /public/fonts/FiraCode-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ARE/blog.iama.re/master/public/fonts/FiraCode-Regular.ttf -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "tabWidth": 4, 4 | "trailingComma": "es5", 5 | "singleQuote": true, 6 | "printWidth": 120 7 | } 8 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Title' 2 | export * from './Sidenote' 3 | export * from './Paragraph' 4 | export * from './NewThought' 5 | export * from './Container' 6 | export * from './Nav' 7 | export * from './Link' 8 | export * from './Code' 9 | export * from './Spinner' 10 | export * from './Separator' 11 | export * from './Image' 12 | export * from './Header' 13 | -------------------------------------------------------------------------------- /src/models/Post.ts: -------------------------------------------------------------------------------- 1 | export type PostModel = { 2 | title: string 3 | author: string 4 | body: string 5 | createdAt: string 6 | updatedAt: string 7 | slug: string 8 | bookId: string 9 | } 10 | 11 | export const postCreatedAt = (post: PostModel): Date => new Date(post.createdAt) 12 | export const postUpdatedAt = (post: PostModel): Date => new Date(post.updatedAt) 13 | -------------------------------------------------------------------------------- /src/components/Title/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, FunctionComponent } from 'react' 2 | import { css } from 'emotion' 3 | 4 | const styles = { 5 | title: css` 6 | font-size: 1.5em; 7 | `, 8 | } 9 | 10 | export type TitleProps = { 11 | children: ReactNode 12 | } 13 | 14 | export const Title: FunctionComponent = ({ children }) => { 15 | return

{children}

16 | } 17 | -------------------------------------------------------------------------------- /.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 | /build-server 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | _couchdb 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | .env 28 | -------------------------------------------------------------------------------- /src/components/NewThought/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, FunctionComponent } from 'react' 2 | import { css } from 'emotion' 3 | 4 | const styles = { 5 | newThought: css` 6 | font-variant: small-caps; 7 | font-size: 1.17em; 8 | `, 9 | } 10 | 11 | export type NewThoughtProps = { 12 | children: ReactNode 13 | } 14 | 15 | export const NewThought: FunctionComponent = ({ 16 | children, 17 | }) => { 18 | return {children} 19 | } 20 | -------------------------------------------------------------------------------- /src/components/Nav/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, FunctionComponent } from 'react' 2 | import { css } from 'emotion' 3 | 4 | const styles = { 5 | root: css` 6 | display: flex; 7 | justify-content: space-between; 8 | 9 | color: #545e6b; 10 | 11 | font-size: 0.75em; 12 | `, 13 | } 14 | 15 | export type NavProps = { 16 | children: ReactNode 17 | } 18 | 19 | export const Nav: FunctionComponent = ({ children }) => { 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | blog - iama.re 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom' 3 | import { injectGlobal } from 'emotion' 4 | 5 | import { DatabaseProvider } from './contexts/Database' 6 | import { App } from './App' 7 | 8 | import 'normalize.css' 9 | import './fonts/Charter' 10 | import './fonts/FiraCode' 11 | 12 | injectGlobal` 13 | html, 14 | body { 15 | background: #fffff3; 16 | color: #2e343b; 17 | } 18 | ` 19 | 20 | render( 21 | 22 | 23 | , 24 | document.getElementById('root') 25 | ) 26 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Router } from '@reach/router' 3 | 4 | import { Container } from './components' 5 | 6 | import { Home } from './views/Home' 7 | import { Post } from './views/Post' 8 | import { NotFound } from './views/404' 9 | 10 | import './admonition.css' 11 | 12 | export const App = () => { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Separator/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { css } from 'emotion' 3 | 4 | const styles = { 5 | separator: css` 6 | border: 0; 7 | height: 1px; 8 | background-image: -webkit-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); 9 | background-image: -moz-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); 10 | background-image: -ms-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); 11 | background-image: -o-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0); 12 | `, 13 | } 14 | 15 | export const Separator = () => { 16 | return
17 | } 18 | -------------------------------------------------------------------------------- /src/components/Container/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, FunctionComponent } from 'react' 2 | import { css } from 'emotion' 3 | 4 | const styles = { 5 | root: css` 6 | position: relative; 7 | font-family: Charter; 8 | font-size: 21px; 9 | line-height: 1.4; 10 | font-variant-ligatures: normal; 11 | 12 | margin: 100px; 13 | 14 | width: 960px; 15 | `, 16 | } 17 | 18 | export type ContainerProps = { 19 | children: ReactNode 20 | } 21 | 22 | export const Container: FunctionComponent = ({ children }) => { 23 | return
{children}
24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "preserve", 17 | "checkJs": true 18 | }, 19 | "include": ["src", "src/types/**/*.d.ts"] 20 | } 21 | -------------------------------------------------------------------------------- /src/views/404.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent } from 'react' 2 | import { RouteComponentProps } from '@reach/router' 3 | import { Paragraph, Link } from '../components' 4 | 5 | export type NotFoundProps = RouteComponentProps 6 | 7 | export const NotFound: FunctionComponent = () => { 8 | return ( 9 | <> 10 | I couldn't find what you were looking for. 11 | 12 | You can go{' '} 13 | 14 | home 15 | 16 | . 17 | 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /src/components/Sidenote/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, FunctionComponent } from 'react' 2 | import { css } from 'emotion' 3 | 4 | const styles = { 5 | sidenote: css` 6 | position: absolute; 7 | top: auto; 8 | right: -100px; 9 | transform: translateX(100%); 10 | margin-top: 2px; 11 | max-width: 400px; 12 | 13 | text-align: justify; 14 | 15 | font-size: 0.8em; 16 | `, 17 | } 18 | 19 | export type SidenoteProps = { 20 | label?: string 21 | children: ReactNode 22 | } 23 | 24 | export const Sidenote: FunctionComponent = ({ 25 | children, 26 | label, 27 | }) => { 28 | return ( 29 | 30 | {label} 31 | 32 | {label} {children} 33 | 34 | 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /src/fonts/Charter.ts: -------------------------------------------------------------------------------- 1 | import { injectGlobal } from 'emotion' 2 | 3 | injectGlobal` 4 | @font-face { 5 | font-family: 'Charter'; 6 | src: url('/fonts/CharterRegular.ttf') format('truetype'); 7 | font-weight: 400; 8 | font-style: normal; 9 | } 10 | 11 | @font-face { 12 | font-family: 'Charter'; 13 | src: url('/fonts/CharterBold.ttf') format('truetype'); 14 | font-weight: 700; 15 | font-style: normal; 16 | } 17 | 18 | @font-face { 19 | font-family: 'Charter'; 20 | src: url('/fonts/CharterItalic.ttf') format('truetype'); 21 | font-weight: 400; 22 | font-style: italic; 23 | } 24 | 25 | @font-face { 26 | font-family: 'Charter'; 27 | src: url('/fonts/CharterBoldItalic.ttf') format('truetype'); 28 | font-weight: 700; 29 | font-style: italic; 30 | } 31 | ` 32 | -------------------------------------------------------------------------------- /src/components/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent, ReactNode } from 'react' 2 | import { css } from 'emotion' 3 | 4 | const styles = { 5 | h1: css` 6 | font-size: 1.5em; 7 | `, 8 | h2: css` 9 | font-size: 1.2em; 10 | `, 11 | h3: css` 12 | font-size: 1.05em; 13 | `, 14 | h4: css` 15 | font-size: 0.9em; 16 | `, 17 | } 18 | 19 | type HeaderProps = { 20 | children: ReactNode 21 | } 22 | 23 | export const H1: FunctionComponent = ({ children }) =>

{children}

24 | export const H2: FunctionComponent = ({ children }) =>

{children}

25 | export const H3: FunctionComponent = ({ children }) =>

{children}

26 | export const H4: FunctionComponent = ({ children }) =>

{children}

27 | -------------------------------------------------------------------------------- /src/fonts/FiraCode.ts: -------------------------------------------------------------------------------- 1 | import { injectGlobal } from 'emotion' 2 | 3 | injectGlobal` 4 | @font-face { 5 | font-family: 'Fira Code'; 6 | src: url('/fonts/FiraCode-Light.ttf') format('truetype'); 7 | font-weight: 200; 8 | font-style: normal; 9 | } 10 | 11 | @font-face { 12 | font-family: 'Fira Code'; 13 | src: url('/fonts/FiraCode-Regular.ttf') format('truetype'); 14 | font-weight: 400; 15 | font-style: normal; 16 | } 17 | 18 | @font-face { 19 | font-family: 'Fira Code'; 20 | src: url('/fonts/FiraCode-Medium.ttf') format('truetype'); 21 | font-weight: 600; 22 | font-style: normal; 23 | } 24 | 25 | @font-face { 26 | font-family: 'Fira Code'; 27 | src: url('/fonts/FiraCode-Bold.ttf') format('truetype'); 28 | font-weight: 700; 29 | font-style: normal; 30 | } 31 | 32 | 33 | ` 34 | -------------------------------------------------------------------------------- /src/components/Link/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent, ReactNode } from 'react' 2 | import { Link as RouterLink } from '@reach/router' 3 | import { css } from 'emotion' 4 | import Microlink from '@microlink/react' 5 | 6 | const styles = { 7 | link: css` 8 | transition: all 0.4s ease; 9 | color: inherit; 10 | line-height: 1; 11 | color: #001f3f; 12 | 13 | &:hover { 14 | transition: all 0.4s ease; 15 | filter: brightness(200%); 16 | } 17 | `, 18 | } 19 | 20 | export type LinkProps = { 21 | children: ReactNode 22 | internal?: boolean 23 | [key: string]: any 24 | } 25 | 26 | export const Link: FunctionComponent = ({ children, internal, ...rest }) => { 27 | if (rest.className && rest.className === 'link-previews') { 28 | return 29 | } 30 | 31 | if (internal) { 32 | return ( 33 | 34 | {children} 35 | 36 | ) 37 | } 38 | 39 | return ( 40 | 41 | {children} 42 | 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /src/components/Spinner/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent } from 'react' 2 | import { css } from 'emotion' 3 | 4 | const styles = { 5 | root: css` 6 | display: block; 7 | margin: 0 auto; 8 | position: relative; 9 | width: 64px; 10 | height: 64px; 11 | 12 | & > span { 13 | display: block; 14 | position: absolute; 15 | border: 4px solid #2e343b; 16 | opacity: 1; 17 | border-radius: 50%; 18 | animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite; 19 | } 20 | 21 | & > span:nth-of-type(2) { 22 | animation-delay: -0.5s; 23 | } 24 | 25 | @keyframes lds-ripple { 26 | 0% { 27 | top: 28px; 28 | left: 28px; 29 | width: 0; 30 | height: 0; 31 | opacity: 1; 32 | } 33 | 100% { 34 | top: -1px; 35 | left: -1px; 36 | width: 58px; 37 | height: 58px; 38 | opacity: 0; 39 | } 40 | } 41 | `, 42 | } 43 | 44 | export const Spinner: FunctionComponent<{}> = () => { 45 | return ( 46 | 47 | 48 | 49 | 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /src/components/Image/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent, useContext, useState } from 'react' 2 | import { css, cx } from 'emotion' 3 | import { DBContext } from '../../contexts/Database' 4 | import { useAsyncEffect } from 'use-async-effect' 5 | import { Spinner } from '../Spinner' 6 | 7 | const styles = { 8 | image: css` 9 | width: 100%; 10 | `, 11 | } 12 | 13 | export type ImageProps = { 14 | alt: string 15 | src: string 16 | style?: string 17 | } 18 | 19 | export const Image: FunctionComponent = ({ src: initialSrc, alt, style = '' }) => { 20 | const urlObj = new URL(initialSrc) 21 | 22 | const { getAttachment } = useContext(DBContext) 23 | const [src, setSrc] = useState(initialSrc) 24 | const [isLoading, setLoading] = useState(urlObj.protocol === 'inkdrop:') 25 | 26 | useAsyncEffect( 27 | async () => { 28 | if (urlObj.protocol === 'inkdrop:') { 29 | setLoading(true) 30 | const blob = await getAttachment(urlObj.pathname.split('//')[1], 'index') 31 | const imgUrl = URL.createObjectURL(blob) 32 | 33 | setSrc(imgUrl) 34 | setLoading(false) 35 | } 36 | }, 37 | undefined, 38 | [] 39 | ) 40 | 41 | if (isLoading) { 42 | return ( 43 | <> 44 | 45 | 46 | ) 47 | } 48 | 49 | return ( 50 | {alt} 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /src/components/Code/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent, ReactNode } from 'react' 2 | import { PrismLight } from 'react-syntax-highlighter' 3 | import { css, cx } from 'emotion' 4 | 5 | import ts from 'react-syntax-highlighter/dist/languages/prism/typescript' 6 | import js from 'react-syntax-highlighter/dist/languages/prism/jsx' 7 | import yaml from 'react-syntax-highlighter/dist/languages/prism/yaml' 8 | import twig from 'react-syntax-highlighter/dist/languages/prism/twig' 9 | 10 | import { vs } from './themes' 11 | 12 | const styles = { 13 | pre: css` 14 | padding-left: 20px; 15 | `, 16 | code: css` 17 | font-family: 'Fira Code'; 18 | font-size: 0.9em; 19 | font-feature-settings: 'calt' 1; 20 | font-variant-ligatures: contextual; 21 | `, 22 | } 23 | 24 | PrismLight.registerLanguage('ts', ts) 25 | PrismLight.registerLanguage('js', js) 26 | PrismLight.registerLanguage('yml', yaml) 27 | PrismLight.registerLanguage('twig', twig) 28 | 29 | export const PreTag: FunctionComponent<{ children: ReactNode }> = ({ children }) => { 30 | return
{children}
31 | } 32 | 33 | export const CodeTag: FunctionComponent<{ children: ReactNode }> = ({ children }) => { 34 | return {children} 35 | } 36 | 37 | export type CodeProps = { 38 | children: string 39 | className?: string 40 | } 41 | 42 | export const Code: FunctionComponent = ({ children, className }) => { 43 | const [, lang] = (className || 'language-plaintext').split('-') 44 | 45 | return ( 46 | 47 | {children} 48 | 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /src/components/Paragraph/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent, ReactNode, ReactNodeArray } from 'react' 2 | import { css, cx } from 'emotion' 3 | 4 | import createHyphenator from 'hyphen' 5 | import englishHyphenationPatterns from 'hyphen/patterns/en-us' 6 | import polishHyphenationPatterns from 'hyphen/patterns/pl' 7 | 8 | const styles = { 9 | paragraph: css` 10 | position: relative; 11 | hyphens: manual; 12 | text-align: justify; 13 | `, 14 | indent: css` 15 | text-indent: 2em; 16 | `, 17 | } 18 | 19 | export type ParagraphProps = { 20 | children: ReactNode 21 | indent?: boolean 22 | hyphenate?: boolean 23 | language?: 'en' | 'pl' 24 | } 25 | 26 | const englishHyphenate = createHyphenator(englishHyphenationPatterns) 27 | const polishHyphenate = createHyphenator(polishHyphenationPatterns) 28 | 29 | export const Paragraph: FunctionComponent = ({ 30 | children, 31 | hyphenate = false, 32 | language = 'en', 33 | indent = false, 34 | }) => { 35 | let childrenArray: ReactNodeArray 36 | 37 | if (!Array.isArray(children)) { 38 | childrenArray = [children] 39 | } else { 40 | childrenArray = children 41 | } 42 | 43 | if (hyphenate) { 44 | childrenArray = childrenArray.map(node => { 45 | if (typeof node === 'string') { 46 | switch (language) { 47 | case 'pl': 48 | return polishHyphenate(node) 49 | case 'en': 50 | default: 51 | return englishHyphenate(node) 52 | } 53 | } 54 | 55 | return node 56 | }) 57 | } 58 | 59 | return ( 60 |

61 | {childrenArray} 62 |

63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog.iama.re", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@mdx-js/runtime": "^1.0.20", 7 | "@microlink/react": "^4.0.1", 8 | "@reach/router": "^1.2.1", 9 | "@types/classnames": "^2.2.8", 10 | "@types/jest": "24.0.14", 11 | "@types/node": "12.0.8", 12 | "@types/pouchdb-browser": "^6.1.3", 13 | "@types/pouchdb-find": "^6.3.4", 14 | "@types/ramda": "^0.26.9", 15 | "@types/reach__router": "^1.2.4", 16 | "@types/react": "^16.8.20", 17 | "@types/react-dom": "16.8.4", 18 | "@types/react-syntax-highlighter": "^10.2.1", 19 | "classnames": "^2.2.6", 20 | "date-fns": "^2.0.0-alpha.34", 21 | "emotion": "^10.0.9", 22 | "hyphen": "^1.1.1", 23 | "normalize.css": "^8.0.1", 24 | "pouchdb-browser": "^7.1.1", 25 | "pouchdb-find": "^7.1.1", 26 | "ramda": "^0.26.1", 27 | "react": "^16.8.6", 28 | "react-dom": "^16.8.6", 29 | "react-scripts": "3.0.1", 30 | "react-syntax-highlighter": "^10.3.0", 31 | "remark-custom-blocks": "^2.3.3", 32 | "remark-macro": "^1.0.7", 33 | "styled-components": "^4.3.2", 34 | "typescript": "3.5.2", 35 | "use-async-effect": "^2.0.1" 36 | }, 37 | "scripts": { 38 | "start": "react-scripts start", 39 | "build": "react-scripts build", 40 | "test": "react-scripts test", 41 | "eject": "react-scripts eject" 42 | }, 43 | "eslintConfig": { 44 | "extends": "react-app" 45 | }, 46 | "browserslist": { 47 | "production": [ 48 | ">0.2%", 49 | "not dead", 50 | "not op_mini all" 51 | ], 52 | "development": [ 53 | "last 1 chrome version", 54 | "last 1 firefox version", 55 | "last 1 safari version" 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/contexts/Database.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, ReactNode, FunctionComponent, useCallback } from 'react' 2 | import PouchDB from 'pouchdb-browser' 3 | import PouchDBFind from 'pouchdb-find' 4 | import prop from 'ramda/es/prop' 5 | 6 | import { DB_URL } from '../constants' 7 | import { PostModel } from '../models/Post' 8 | 9 | PouchDB.plugin(PouchDBFind) 10 | 11 | const db = new PouchDB(DB_URL, { skip_setup: true }) 12 | 13 | export type Result = PouchDB.Core.ExistingDocument 14 | 15 | export type DBContext = { 16 | getDatabaseInfo: () => Promise 17 | getRecentPosts: (limit?: number, skip?: number) => Promise[]> 18 | getPostById: (id: string) => Promise> 19 | getAttachment: (id: string, filename: string) => Promise 20 | } 21 | 22 | export const DBContext = createContext({} as DBContext) 23 | 24 | export type DatabaseProviderProps = { 25 | children: ReactNode 26 | } 27 | 28 | export const DatabaseProvider: FunctionComponent = ({ children }) => { 29 | const getDatabaseInfo = useCallback(async () => { 30 | return db.info() 31 | }, []) 32 | 33 | const getRecentPosts = useCallback(async (limit = 10, skip = 0) => { 34 | const result = await db.query('posts/all', { 35 | descending: true, 36 | endkey: skip, 37 | limit: limit, 38 | }) 39 | 40 | return result.rows.map(prop('value')) 41 | }, []) 42 | 43 | const getPostById = useCallback(async (id: string) => { 44 | return db.get(id, { attachments: true }) 45 | }, []) 46 | 47 | const getAttachment = useCallback(async (id: string, filename: string) => { 48 | return db.getAttachment(id, filename) 49 | }, []) 50 | 51 | const contextValue: DBContext = { 52 | getDatabaseInfo, 53 | getRecentPosts, 54 | getPostById, 55 | getAttachment, 56 | } 57 | 58 | return {children} 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | -------------------------------------------------------------------------------- /src/components/Markdown/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent } from 'react' 2 | import MDX from '@mdx-js/runtime' 3 | import remarkCustomBlocks from 'remark-custom-blocks' 4 | import macro from 'remark-macro' 5 | 6 | import { Paragraph, Sidenote, Code, Link, Image, H1, H2, H3, H4, CodeTag } from '../index' 7 | 8 | const macroPlugin = macro() 9 | 10 | macroPlugin.addMacro( 11 | 'preview', 12 | props => { 13 | return { 14 | type: 'NoteNode', 15 | data: { 16 | hName: 'a', 17 | hProperties: { className: 'link-previews', href: props.src }, 18 | hChildren: [{ type: 'text', value: props.src }], 19 | }, 20 | } 21 | }, 22 | true 23 | ) 24 | const admonitionConfig: Record = {} 25 | const admonitionTypes = ['abstract', 'note', 'danger', 'warning', 'info', 'success', 'fail', 'question', 'example'] 26 | 27 | admonitionTypes.forEach(type => { 28 | admonitionConfig[type] = { 29 | classes: `admonition-${type}`, 30 | title: 'optional', 31 | } 32 | admonitionConfig[`${type}-spoiler`] = { 33 | classes: `admonition-${type}`, 34 | title: 'required', 35 | details: true, 36 | } 37 | }) 38 | 39 | admonitionConfig['spoiler'] = { 40 | classes: 'admonition-spoiler', 41 | title: 'required', 42 | details: true, 43 | } 44 | 45 | export type MarkdownProps = { 46 | scope?: object 47 | text: string 48 | } 49 | 50 | export const Markdown: FunctionComponent = ({ text, scope = {} }) => { 51 | return ( 52 | 70 | {text} 71 | 72 | ) 73 | } 74 | -------------------------------------------------------------------------------- /src/views/Home.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent, useContext, useState } from 'react' 2 | import { RouteComponentProps } from '@reach/router' 3 | import { useAsyncEffect } from 'use-async-effect' 4 | import distanceToNow from 'date-fns/formatDistanceToNow' 5 | 6 | import { DBContext, Result } from '../contexts/Database' 7 | import { PostModel, postCreatedAt } from '../models/Post' 8 | import { Spinner, Paragraph, Link, Separator } from '../components' 9 | 10 | export type HomeProps = RouteComponentProps 11 | 12 | export const Home: FunctionComponent = () => { 13 | const { getRecentPosts } = useContext(DBContext) 14 | const [posts, setPosts] = useState[]>([]) 15 | const [isLoading, setLoading] = useState(true) 16 | 17 | useAsyncEffect( 18 | async () => { 19 | const posts = await getRecentPosts() 20 | 21 | setPosts(posts) 22 | setLoading(false) 23 | }, 24 | undefined, 25 | [] 26 | ) 27 | 28 | if (isLoading) { 29 | return 30 | } 31 | 32 | if (!isLoading && posts.length === 0) { 33 | return <>There are no posts to display. 34 | } 35 | 36 | return ( 37 | <> 38 | 39 | Hi there! I'm Arè. This is a collection of my articles and thoughts. This blog has been written 40 | as a serverless CouchDB-powered app. Please excuse the size of the bundle, this is mostly a playground 41 | for me. Code powering this blog is available on{' '} 42 | Github. 43 | 44 | 45 | {posts.map(post => { 46 | const createdAt = postCreatedAt(post) 47 | return ( 48 | 49 | 50 | {post.title} 51 | {' '} 52 | 53 | by @{post.author} —   54 | {distanceToNow(createdAt)} ago 55 | 56 | 57 | ) 58 | })} 59 | 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /src/views/Post.tsx: -------------------------------------------------------------------------------- 1 | import React, { FunctionComponent, useState, useContext } from 'react' 2 | import { RouteComponentProps, Redirect } from '@reach/router' 3 | import distanceToNow from 'date-fns/formatDistanceToNow' 4 | import { useAsyncEffect } from 'use-async-effect' 5 | 6 | import { PostModel, postCreatedAt, postUpdatedAt } from '../models/Post' 7 | 8 | import { Title, Nav, Link, Spinner } from '../components' 9 | import { Markdown } from '../components/Markdown' 10 | import { DBContext } from '../contexts/Database' 11 | 12 | export type PostProps = RouteComponentProps<{ 13 | id: string 14 | }> 15 | 16 | export const Post: FunctionComponent = ({ id }) => { 17 | const { getPostById } = useContext(DBContext) 18 | const [post, setPost] = useState() 19 | const [error, setError] = useState(null) 20 | 21 | useAsyncEffect( 22 | async () => { 23 | if (!id) { 24 | return setError('404') 25 | } 26 | 27 | try { 28 | const post = await getPostById(id) 29 | 30 | if (!post) { 31 | return setError('404') 32 | } 33 | 34 | setPost(post) 35 | } catch (e) { 36 | if (e.error === 'not_found') { 37 | return setError('404') 38 | } 39 | 40 | return setError('404') 41 | } 42 | }, 43 | undefined, 44 | [id] 45 | ) 46 | 47 | if (error === '404') { 48 | return 49 | } 50 | 51 | if (!post) { 52 | return 53 | } 54 | 55 | const createdAt = postCreatedAt(post) 56 | const updatedAt = postUpdatedAt(post) 57 | 58 | return ( 59 | <> 60 | 71 | {post.title} 72 | 73 | 74 | ) 75 | } 76 | -------------------------------------------------------------------------------- /src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'hyphen' { 2 | export type HyphenationPatterns = { 3 | patterns: string[] 4 | exceptions: string[] 5 | } 6 | 7 | export default function createHyphenator(patterns: HyphenationPatterns): (text: string) => string 8 | } 9 | 10 | declare module 'hyphen/patterns/en-us' { 11 | import { HyphenationPatterns } from 'hyphen' 12 | 13 | var englishHyphenationPatterns: HyphenationPatterns 14 | 15 | export default englishHyphenationPatterns 16 | } 17 | 18 | declare module 'hyphen/patterns/pl' { 19 | import { HyphenationPatterns } from 'hyphen' 20 | 21 | var polishHyphenationPatterns: HyphenationPatterns 22 | 23 | export default polishHyphenationPatterns 24 | } 25 | 26 | declare module 'react-syntax-highlighter/dist/languages/prism/jsx' { 27 | var language: string 28 | export default language 29 | } 30 | 31 | declare module 'react-syntax-highlighter/dist/languages/prism/typescript' { 32 | var language: string 33 | export default language 34 | } 35 | 36 | declare module 'react-syntax-highlighter/dist/languages/prism/yaml' { 37 | var language: string 38 | export default language 39 | } 40 | 41 | declare module 'react-syntax-highlighter/dist/languages/prism/twig' { 42 | var language: string 43 | export default language 44 | } 45 | 46 | declare module 'remark-macro' { 47 | type Transformer = (content: string, props: any) => any 48 | type InlineTransformer = (props: any) => any 49 | interface Macro { 50 | addMacro(name: string, fn: Transformer, inline: false): void 51 | addMacro(name: string, fn: InlineTransformer, inline: true): void 52 | 53 | transformer: any 54 | } 55 | 56 | var macro: () => Macro 57 | export default macro 58 | } 59 | 60 | declare module '**.ttf' { 61 | var fontUrl: string 62 | export default fontUrl 63 | } 64 | 65 | declare module 'react-markdown/with-html' { 66 | import ReactMarkdown from 'react-markdown' 67 | 68 | export default ReactMarkdown 69 | } 70 | 71 | declare module '@mdx-js/runtime' { 72 | import { FunctionComponent, Component } from 'react' 73 | 74 | type MDXProps = { 75 | scope?: object 76 | components?: Record> 77 | remarkPlugins?: Array 78 | rehypePlugins?: Array 79 | children: string 80 | } 81 | 82 | var MDX: FunctionComponent 83 | 84 | export default MDX 85 | } 86 | 87 | declare module 'remark-custom-blocks' { 88 | var plugin: any 89 | 90 | export default plugin 91 | } 92 | 93 | declare module '@microlink/react' { 94 | var microlink: FuncitonComponent<{ 95 | url: string 96 | [key: string]: string 97 | }> 98 | 99 | export default microlink 100 | } 101 | -------------------------------------------------------------------------------- /src/components/Code/themes/vs.ts: -------------------------------------------------------------------------------- 1 | import { css } from 'emotion' 2 | 3 | export const vs = css` 4 | /** 5 | * VS theme by Andrew Lock (https://andrewlock.net) 6 | * Inspired by Visual Studio syntax coloring 7 | */ 8 | 9 | & > .token.comment, 10 | & > .token.prolog, 11 | & > .token.doctype, 12 | & > .token.cdata { 13 | color: #008000; 14 | font-style: italic; 15 | } 16 | 17 | & > .token.namespace { 18 | opacity: 0.7; 19 | } 20 | 21 | & > .token.string { 22 | color: #a31515; 23 | } 24 | 25 | & > .token.punctuation, 26 | & > .token.operator { 27 | color: #393a34; /* no highlight */ 28 | } 29 | 30 | & > .token.url, 31 | & > .token.symbol, 32 | & > .token.number, 33 | & > .token.boolean, 34 | & > .token.variable, 35 | & > .token.constant, 36 | & > .token.inserted { 37 | color: #36acaa; 38 | } 39 | 40 | & > .token.atrule, 41 | & > .token.keyword, 42 | & > .token.attr-value, 43 | & > .language-autohotkey .token.selector, 44 | & > .language-json .token.boolean, 45 | & > .language-json .token.number { 46 | color: #0000ff; 47 | } 48 | 49 | & > .token.function { 50 | color: #393a34; 51 | } 52 | & > .token.deleted, 53 | & > .language-autohotkey .token.tag { 54 | color: #9a050f; 55 | } 56 | 57 | & > .token.selector, 58 | & > .language-autohotkey .token.keyword { 59 | color: #00009f; 60 | } 61 | 62 | & > .token.important, 63 | & > .token.bold { 64 | font-weight: bold; 65 | } 66 | 67 | & > .token.italic { 68 | font-style: italic; 69 | } 70 | 71 | & > .token.class-name, 72 | & > .language-json .token.property { 73 | color: #2b91af; 74 | } 75 | 76 | & > .token.tag, 77 | & > .token.selector { 78 | color: #800000; 79 | } 80 | 81 | & > .token.attr-name, 82 | & > .token.property, 83 | & > .token.regex, 84 | & > .token.entity { 85 | color: #ff0000; 86 | } 87 | 88 | & > .token.directive.tag .tag { 89 | background: #ffff00; 90 | color: #393a34; 91 | } 92 | 93 | /* overrides color-values for the Line Numbers plugin 94 | * http://prismjs.com/plugins/line-numbers/ 95 | */ 96 | & > .line-numbers .line-numbers-rows { 97 | border-right-color: #a5a5a5; 98 | } 99 | 100 | & > .line-numbers-rows > span:before { 101 | color: #2b91af; 102 | } 103 | 104 | /* overrides color-values for the Line Highlight plugin 105 | * http://prismjs.com/plugins/line-highlight/ 106 | */ 107 | & > .line-highlight { 108 | background: rgba(193, 222, 241, 0.2); 109 | background: -webkit-linear-gradient( 110 | left, 111 | rgba(193, 222, 241, 0.2) 70%, 112 | rgba(221, 222, 241, 0) 113 | ); 114 | background: linear-gradient( 115 | to right, 116 | rgba(193, 222, 241, 0.2) 70%, 117 | rgba(221, 222, 241, 0) 118 | ); 119 | } 120 | ` 121 | -------------------------------------------------------------------------------- /src/admonition.css: -------------------------------------------------------------------------------- 1 | .admonition { 2 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); 3 | position: relative; 4 | margin: 1.5625em 0; 5 | padding: 0 1.2rem; 6 | border-left: 0.4rem solid #448aff; 7 | border-radius: 0.2rem; 8 | } 9 | .admonition > summary { 10 | cursor: pointer; 11 | } 12 | .admonition > .custom-block-body { 13 | margin: 1em 0; 14 | } 15 | .admonition-title, 16 | .admonition-note > .custom-block-heading, 17 | .admonition-info > .custom-block-heading, 18 | .admonition-danger > .custom-block-heading, 19 | .admonition-warning > .custom-block-heading, 20 | .admonition-success > .custom-block-heading, 21 | .admonition-fail > .custom-block-heading, 22 | .admonition-question > .custom-block-heading, 23 | .admonition-abstract > .custom-block-heading, 24 | .admonition-example > .custom-block-heading, 25 | .admonition-spoiler > .custom-block-heading { 26 | margin: 0 -1.2rem; 27 | padding: 0.8rem 1.2rem 0.8rem 1.2rem; 28 | border-bottom: 0.1rem solid rgba(68, 138, 255, 0.1); 29 | background-color: rgba(68, 138, 255, 0.1); 30 | font-weight: 700; 31 | } 32 | .admonition-note { 33 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); 34 | position: relative; 35 | margin: 1.5625em 0; 36 | padding: 0 1.2rem; 37 | border-left: 0.4rem solid #448aff; 38 | border-radius: 0.2rem; 39 | border-left-color: #448aff; 40 | } 41 | .admonition-note > summary { 42 | cursor: pointer; 43 | } 44 | .admonition-note > .custom-block-body { 45 | margin: 1em 0; 46 | } 47 | .admonition-note > .custom-block-heading { 48 | background-color: rgba(68, 138, 255, 0.1); 49 | } 50 | .admonition-info { 51 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); 52 | position: relative; 53 | margin: 1.5625em 0; 54 | padding: 0 1.2rem; 55 | border-left: 0.4rem solid #448aff; 56 | border-radius: 0.2rem; 57 | border-left-color: #009688; 58 | } 59 | .admonition-info > summary { 60 | cursor: pointer; 61 | } 62 | .admonition-info > .custom-block-body { 63 | margin: 1em 0; 64 | } 65 | .admonition-info > .custom-block-heading { 66 | background-color: rgba(0, 150, 136, 0.1); 67 | } 68 | .admonition-danger { 69 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); 70 | position: relative; 71 | margin: 1.5625em 0; 72 | padding: 0 1.2rem; 73 | border-left: 0.4rem solid #448aff; 74 | border-radius: 0.2rem; 75 | border-left-color: #c2185b; 76 | } 77 | .admonition-danger > summary { 78 | cursor: pointer; 79 | } 80 | .admonition-danger > .custom-block-body { 81 | margin: 1em 0; 82 | } 83 | .admonition-danger > .custom-block-heading { 84 | background-color: rgba(194, 24, 91, 0.1); 85 | } 86 | .admonition-warning { 87 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); 88 | position: relative; 89 | margin: 1.5625em 0; 90 | padding: 0 1.2rem; 91 | border-left: 0.4rem solid #448aff; 92 | border-radius: 0.2rem; 93 | border-left-color: #ff9100; 94 | } 95 | .admonition-warning > summary { 96 | cursor: pointer; 97 | } 98 | .admonition-warning > .custom-block-body { 99 | margin: 1em 0; 100 | } 101 | .admonition-warning > .custom-block-heading { 102 | background-color: rgba(255, 145, 0, 0.1); 103 | } 104 | .admonition-success { 105 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); 106 | position: relative; 107 | margin: 1.5625em 0; 108 | padding: 0 1.2rem; 109 | border-left: 0.4rem solid #448aff; 110 | border-radius: 0.2rem; 111 | border-left-color: #00c853; 112 | } 113 | .admonition-success > summary { 114 | cursor: pointer; 115 | } 116 | .admonition-success > .custom-block-body { 117 | margin: 1em 0; 118 | } 119 | .admonition-success > .custom-block-heading { 120 | background-color: rgba(0, 200, 83, 0.1); 121 | } 122 | .admonition-fail { 123 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); 124 | position: relative; 125 | margin: 1.5625em 0; 126 | padding: 0 1.2rem; 127 | border-left: 0.4rem solid #448aff; 128 | border-radius: 0.2rem; 129 | border-left-color: #d32f2f; 130 | } 131 | .admonition-fail > summary { 132 | cursor: pointer; 133 | } 134 | .admonition-fail > .custom-block-body { 135 | margin: 1em 0; 136 | } 137 | .admonition-fail > .custom-block-heading { 138 | background-color: rgba(211, 47, 47, 0.1); 139 | } 140 | .admonition-question { 141 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); 142 | position: relative; 143 | margin: 1.5625em 0; 144 | padding: 0 1.2rem; 145 | border-left: 0.4rem solid #448aff; 146 | border-radius: 0.2rem; 147 | border-left-color: #64dd17; 148 | } 149 | .admonition-question > summary { 150 | cursor: pointer; 151 | } 152 | .admonition-question > .custom-block-body { 153 | margin: 1em 0; 154 | } 155 | .admonition-question > .custom-block-heading { 156 | background-color: rgba(100, 221, 23, 0.1); 157 | } 158 | .admonition-abstract { 159 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); 160 | position: relative; 161 | margin: 1.5625em 0; 162 | padding: 0 1.2rem; 163 | border-left: 0.4rem solid #448aff; 164 | border-radius: 0.2rem; 165 | border-left-color: #00b0ff; 166 | } 167 | .admonition-abstract > summary { 168 | cursor: pointer; 169 | } 170 | .admonition-abstract > .custom-block-body { 171 | margin: 1em 0; 172 | } 173 | .admonition-abstract > .custom-block-heading { 174 | background-color: rgba(0, 176, 255, 0.1); 175 | } 176 | .admonition-example { 177 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); 178 | position: relative; 179 | margin: 1.5625em 0; 180 | padding: 0 1.2rem; 181 | border-left: 0.4rem solid #448aff; 182 | border-radius: 0.2rem; 183 | border-left-color: #651fff; 184 | } 185 | .admonition-example > summary { 186 | cursor: pointer; 187 | } 188 | .admonition-example > .custom-block-body { 189 | margin: 1em 0; 190 | } 191 | .admonition-example > .custom-block-heading { 192 | background-color: rgba(101, 31, 255, 0.1); 193 | } 194 | .admonition-spoiler { 195 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); 196 | position: relative; 197 | margin: 1.5625em 0; 198 | padding: 0 1.2rem; 199 | border-left: 0.4rem solid #448aff; 200 | border-radius: 0.2rem; 201 | border-left-color: #455a64; 202 | } 203 | .admonition-spoiler > summary { 204 | cursor: pointer; 205 | } 206 | .admonition-spoiler > .custom-block-body { 207 | margin: 1em 0; 208 | } 209 | .admonition-spoiler > .custom-block-heading { 210 | padding-left: 1rem; 211 | background-color: rgba(69, 90, 100, 0.1); 212 | } 213 | 214 | .custom-block-body { 215 | overflow: auto; 216 | } 217 | --------------------------------------------------------------------------------