├── plugins ├── gatsby-plugin-draft │ ├── index.js │ ├── package.json │ └── gatsby-node.js ├── gatsby-plugin-disable-localsearch │ ├── index.js │ ├── package.json │ └── gatsby-node.js └── gatsby-remark-sectionize-toc │ ├── index.js │ ├── package.json │ └── sectionize-toc.js ├── docker ├── Dockerfile.runtime ├── Dockerfile ├── Dockerfile.builder └── Dockerfile.develop ├── src ├── components │ ├── Link │ │ ├── index.js │ │ └── Link.js │ ├── Navigation │ │ ├── index.js │ │ └── Navigation.js │ ├── Seo │ │ ├── index.js │ │ └── Seo.js │ ├── Header │ │ ├── index.js │ │ ├── fullscreen.js │ │ ├── social.js │ │ ├── logo.js │ │ └── navigation.js │ ├── Layout │ │ ├── index.js │ │ └── Layout.js │ ├── Sidebar │ │ ├── index.js │ │ ├── contentTree.js │ │ ├── links.js │ │ ├── poweredBy.js │ │ ├── contentTreeGroup.js │ │ └── contentTreeNode.js │ ├── EditOnRepo │ │ ├── index.js │ │ └── EditOnRepo.js │ ├── ScrollTop │ │ ├── index.js │ │ └── ScrollTop.js │ ├── PreviousNext │ │ └── index.js │ ├── DarkModeSwitch │ │ ├── index.js │ │ └── DarkModeSwitch.js │ ├── TableOfContents │ │ └── index.js │ ├── ThemeProvider │ │ ├── index.js │ │ └── ThemeProvider.js │ ├── Buttons │ │ ├── index.js │ │ ├── ButtonIcon.js │ │ └── Social.js │ ├── Search │ │ ├── localsearch │ │ │ ├── stats.js │ │ │ ├── pagination.js │ │ │ ├── input.js │ │ │ ├── hitComps.js │ │ │ └── index.js │ │ ├── index.js │ │ ├── algolia │ │ │ ├── stats.js │ │ │ ├── pagination.js │ │ │ ├── input.js │ │ │ ├── hitComps.js │ │ │ └── index.js │ │ ├── styles.js │ │ ├── Stats.js │ │ ├── Status.js │ │ ├── Hits.js │ │ ├── Sidebar.js │ │ ├── Pagination.js │ │ └── Input.js │ ├── MdxComponents │ │ ├── loading.js │ │ ├── LiveProvider.js │ │ ├── anchor.js │ │ ├── card.js │ │ ├── layout.js │ │ ├── icon.js │ │ ├── badge.js │ │ ├── imageCard.js │ │ ├── linkCard.js │ │ ├── fileDownloadCard.js │ │ ├── highlights.js │ │ ├── jargon.js │ │ ├── accordion.js │ │ ├── index.js │ │ └── codeBlock.js │ ├── index.js │ └── Loader │ │ └── index.js ├── pwa-512.png ├── custom-sw-code.js ├── pages │ ├── 404.js │ └── gitlab.svg ├── images │ ├── closed.js │ ├── opened.js │ ├── help.svg │ ├── github.svg │ ├── up-arrow.inline.svg │ ├── bitbucket.svg │ ├── twitter.svg │ └── gitlab.svg ├── utils │ ├── emoji.js │ ├── useDebounce.js │ ├── utils.js │ ├── colors.js │ ├── algolia.js │ ├── jargon-config.js │ ├── fileUtils.js │ ├── config-pwa.js │ └── search.js ├── theme │ ├── dark.js │ ├── light.js │ ├── colors.js │ └── index.js ├── styles │ ├── responsive.js │ └── main.scss └── html.js ├── netlify.toml ├── snippets ├── hello-js.js └── hello.java ├── static └── assets │ ├── gatsby.png │ ├── example.pdf │ ├── favicon.png │ ├── code.svg │ └── settings.svg ├── content ├── developing │ ├── building.md │ └── running_locally.mdx ├── images │ ├── simple_sidebar.png │ ├── structure_content.png │ ├── structure_general.png │ ├── structure_header.png │ ├── structure_search.png │ └── structure_sidebar.png ├── gettingstarted │ ├── page_structure.md │ ├── concepts.md │ ├── quickstart.md │ └── cli.md ├── configuration │ ├── introduction.md │ ├── setting-up │ │ ├── header.md │ │ ├── full_example.md │ │ ├── base.md │ │ └── sidebar.md │ ├── setting-up.md │ └── navigation.md ├── editing │ ├── rich_content.md │ ├── images.md │ ├── rich_content │ │ ├── abbreviations.md │ │ └── embed.md │ └── page_config.md └── index.mdx ├── renovate.json ├── .prettierrc ├── .gitignore ├── .editorconfig ├── .codacy.yml ├── .dockerignore ├── gatsby-browser.js ├── scripts ├── builder.sh └── run_local.sh ├── .eslintrc.json ├── .github └── workflows │ ├── cd.yml │ ├── cleaner.yml │ ├── release.yml │ └── ci.yml ├── LICENSE ├── config ├── jargon.yml ├── default.js └── config.yml ├── gatsby-node.js ├── package.json └── README.md /plugins/gatsby-plugin-draft/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/gatsby-plugin-disable-localsearch/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docker/Dockerfile.runtime: -------------------------------------------------------------------------------- 1 | FROM gatsbyjs/gatsby:latest 2 | -------------------------------------------------------------------------------- /src/components/Link/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Link'; 2 | -------------------------------------------------------------------------------- /src/components/Navigation/index.js: -------------------------------------------------------------------------------- 1 | export * from './Navigation'; 2 | -------------------------------------------------------------------------------- /src/components/Seo/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Seo'; 2 | -------------------------------------------------------------------------------- /src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Header'; 2 | -------------------------------------------------------------------------------- /src/components/Layout/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Layout'; 2 | -------------------------------------------------------------------------------- /src/components/Sidebar/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Sidebar'; 2 | -------------------------------------------------------------------------------- /src/components/EditOnRepo/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './EditOnRepo'; 2 | -------------------------------------------------------------------------------- /src/components/ScrollTop/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './ScrollTop'; 2 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "public" 3 | command = "npm run build" 4 | -------------------------------------------------------------------------------- /src/components/PreviousNext/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './PreviousNext'; 2 | -------------------------------------------------------------------------------- /src/pwa-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipowm/BooGi/HEAD/src/pwa-512.png -------------------------------------------------------------------------------- /snippets/hello-js.js: -------------------------------------------------------------------------------- 1 | const hello = () => console.log('Hello World'); 2 | hello(); 3 | -------------------------------------------------------------------------------- /src/components/DarkModeSwitch/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './DarkModeSwitch'; 2 | -------------------------------------------------------------------------------- /src/components/TableOfContents/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './TableOfContents'; 2 | -------------------------------------------------------------------------------- /src/components/ThemeProvider/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './ThemeProvider'; 2 | -------------------------------------------------------------------------------- /static/assets/gatsby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipowm/BooGi/HEAD/static/assets/gatsby.png -------------------------------------------------------------------------------- /static/assets/example.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipowm/BooGi/HEAD/static/assets/example.pdf -------------------------------------------------------------------------------- /static/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipowm/BooGi/HEAD/static/assets/favicon.png -------------------------------------------------------------------------------- /snippets/hello.java: -------------------------------------------------------------------------------- 1 | public static void main(String[] args) { 2 | System.out.println("Hello World"); 3 | } -------------------------------------------------------------------------------- /content/developing/building.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Building" 3 | order: 2 4 | --- 5 | 6 | There is nothing so far :-( 7 | -------------------------------------------------------------------------------- /content/images/simple_sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipowm/BooGi/HEAD/content/images/simple_sidebar.png -------------------------------------------------------------------------------- /content/images/structure_content.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipowm/BooGi/HEAD/content/images/structure_content.png -------------------------------------------------------------------------------- /content/images/structure_general.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipowm/BooGi/HEAD/content/images/structure_general.png -------------------------------------------------------------------------------- /content/images/structure_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipowm/BooGi/HEAD/content/images/structure_header.png -------------------------------------------------------------------------------- /content/images/structure_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipowm/BooGi/HEAD/content/images/structure_search.png -------------------------------------------------------------------------------- /content/images/structure_sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filipowm/BooGi/HEAD/content/images/structure_sidebar.png -------------------------------------------------------------------------------- /src/components/Buttons/index.js: -------------------------------------------------------------------------------- 1 | export { default as ButtonIcon } from './ButtonIcon'; 2 | export * from './Social'; 3 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "ignoreDeps": ["gatsby-react-router-scroll"] 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "jsxBracketSameLine": false, 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "trailingComma": "es5" 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | public 2 | .cache 3 | node_modules 4 | *DS_Store 5 | *.env 6 | 7 | .idea/ 8 | .vscode/ 9 | *.iml 10 | reduxcache* 11 | *.log 12 | .generated.config.js -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /src/components/Search/localsearch/stats.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SearchStats } from '../'; 3 | export default ({ nbHits, ...rest }) => ; 4 | -------------------------------------------------------------------------------- /src/components/Search/localsearch/pagination.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Pagination } from '../'; 3 | export default ({ currentPage, ...rest }) => ( 4 | 5 | ); 6 | -------------------------------------------------------------------------------- /src/custom-sw-code.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | workbox.routing.registerRoute( 3 | new RegExp('https:.*min.(css|js)'), 4 | new workbox.strategies.StaleWhileRevalidate({ 5 | cacheName: 'cdn-cache', 6 | }) 7 | ); 8 | -------------------------------------------------------------------------------- /.codacy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | exclude_paths: 3 | - 'config/**' 4 | - 'content/**/*' 5 | - 'snippets/**/*' 6 | - 'static/**/*' 7 | - '**/package.json' 8 | - '**/*.md' 9 | - '**/*.mdx' 10 | - 'CHANGELOG.md' 11 | - 'README.md' -------------------------------------------------------------------------------- /src/components/Search/index.js: -------------------------------------------------------------------------------- 1 | export { SearchInput, SidebarSearchInput } from './Input'; 2 | export { default as Pagination } from './Pagination'; 3 | export { default as SearchSidebar } from './Sidebar'; 4 | export { default as SearchStats } from './Stats'; 5 | -------------------------------------------------------------------------------- /src/components/Search/algolia/stats.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connectStats } from 'react-instantsearch-dom'; 3 | import { SearchStats } from '../'; 4 | export default connectStats(({ nbHits, ...rest }) => ); 5 | -------------------------------------------------------------------------------- /src/components/Search/localsearch/input.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SidebarSearchInput } from '../'; 3 | export default (({ refine, ...rest }) => ( 4 | refine(value)} {...rest} showClean={true} /> 5 | )); 6 | -------------------------------------------------------------------------------- /src/pages/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const NotFoundPage = () => ( 4 |
5 |

NOT FOUND

6 |

You just hit a route that doesn't exist... the sadness.

7 |
8 | ); 9 | 10 | export default NotFoundPage; 11 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public 3 | .cache 4 | .idea 5 | .vscode 6 | *.iml 7 | reduxcache* 8 | Dockerfile* 9 | *.crt 10 | *.key 11 | .dockerignore 12 | package-lock.json 13 | yarn.lock 14 | .github 15 | .gitlab-ci.yml 16 | *DS_Store 17 | *.env 18 | *.log 19 | *.zip -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | export const onServiceWorkerUpdateReady = () => { 2 | const answer = window.confirm( 3 | `This tutorial has been updated. ` + `Reload to display the latest version?` 4 | ); 5 | if (answer === true) { 6 | window.location.reload(); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/components/MdxComponents/loading.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const LoadingProvider = ({ isLoading, ...props }) => { 4 | if (isLoading) { 5 | return
Loading...
; 6 | } 7 | return
; 8 | }; 9 | 10 | export default LoadingProvider; 11 | -------------------------------------------------------------------------------- /src/components/Search/algolia/pagination.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connectPagination } from 'react-instantsearch-dom'; 3 | import { Pagination } from '../'; 4 | export default connectPagination(({ currentRefinement, ...rest }) => ( 5 | 6 | )); 7 | -------------------------------------------------------------------------------- /scripts/builder.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | theme_file_name=theme.js 3 | theme_path=src/theme/main.js 4 | cd /app 5 | 6 | if [[ -f "$theme_file_name" ]]; then 7 | echo "Copying Theme configuration" 8 | mv $theme_file_name $theme_path 9 | fi 10 | 11 | echo "Executing gatsby" 12 | gatsby build 13 | 14 | exit $? 15 | -------------------------------------------------------------------------------- /src/images/closed.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ClosedSvg = () => ( 4 | 5 | 6 | 7 | ); 8 | 9 | export default ClosedSvg; 10 | -------------------------------------------------------------------------------- /src/images/opened.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const OpenedSvg = () => ( 4 | 5 | 6 | 7 | ); 8 | 9 | export default OpenedSvg; 10 | -------------------------------------------------------------------------------- /src/components/Search/algolia/input.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connectSearchBox } from 'react-instantsearch-dom'; 3 | import { SidebarSearchInput } from '../'; 4 | export default connectSearchBox(({ refine, ...rest }) => ( 5 | refine(value)} {...rest} showClean={true} /> 6 | )); 7 | -------------------------------------------------------------------------------- /plugins/gatsby-plugin-draft/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-plugin-draft", 3 | "description": "Plugin to manage drafts", 4 | "author": "Mateusz Filipowicz ", 5 | "version": "0.1.0", 6 | "dependencies": { 7 | "gatsby": "2.24.43" 8 | }, 9 | "license": "MIT", 10 | "main": "index.js" 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/emoji.js: -------------------------------------------------------------------------------- 1 | const emoji = require('node-emoji'); 2 | 3 | const emojify = (text) => { 4 | return emoji.emojify(text, (name) => name); 5 | }; 6 | 7 | const clean = (text) => { 8 | const emojified = emojify(text); 9 | return emoji.strip(emojified); 10 | }; 11 | 12 | module.exports = { 13 | emojify, 14 | clean, 15 | }; 16 | -------------------------------------------------------------------------------- /plugins/gatsby-plugin-disable-localsearch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-plugin-disable-localsearch", 3 | "description": "Plugin to to disable local search", 4 | "author": "Mateusz Filipowicz ", 5 | "version": "0.1.0", 6 | "dependencies": { 7 | "gatsby": "2.24.43" 8 | }, 9 | "license": "MIT", 10 | "main": "index.js" 11 | } 12 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:buster-slim as builder 2 | 3 | WORKDIR /app 4 | 5 | RUN apt-get update \ 6 | && apt-get install --no-install-recommends -y git=1:2.20.1-2+deb10u3 \ 7 | && npm -g install gatsby-cli 8 | 9 | COPY package*.json ./ 10 | 11 | RUN yarn 12 | 13 | COPY . . 14 | 15 | RUN npm run build 16 | 17 | 18 | FROM gatsbyjs/gatsby:latest 19 | COPY --from=builder /app/public /pub 20 | -------------------------------------------------------------------------------- /plugins/gatsby-remark-sectionize-toc/index.js: -------------------------------------------------------------------------------- 1 | const sectionizeToc = require('./sectionize-toc'); 2 | 3 | const transform = sectionizeToc(); 4 | 5 | module.exports = function ({ markdownAST, markdownNode }, pluginOptions) { 6 | const maxDepth = markdownNode.frontmatter.tocDepth 7 | ? markdownNode.frontmatter.tocDepth 8 | : pluginOptions.maxDepth; 9 | transform(markdownAST, maxDepth); 10 | }; 11 | -------------------------------------------------------------------------------- /src/components/Search/styles.js: -------------------------------------------------------------------------------- 1 | import { css } from '@emotion/core'; 2 | 3 | const defaults = { 4 | leftRight: '24px', 5 | }; 6 | 7 | export const paddingLeftRight = css` 8 | padding-left: ${defaults.leftRight}; 9 | padding-right: ${defaults.leftRight}; 10 | `; 11 | 12 | export const marginLeftRight = css` 13 | margin-left: ${defaults.leftRight}; 14 | margin-right: ${defaults.leftRight}; 15 | `; 16 | -------------------------------------------------------------------------------- /src/components/MdxComponents/LiveProvider.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { LiveProvider, LiveEditor, LiveError, LivePreview } from 'react-live'; 3 | 4 | const ReactLiveProvider = ({ code }) => { 5 | return ( 6 | 7 | 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | export default ReactLiveProvider; 15 | -------------------------------------------------------------------------------- /docker/Dockerfile.builder: -------------------------------------------------------------------------------- 1 | FROM node:buster-slim as builder 2 | 3 | WORKDIR /app 4 | 5 | RUN apt-get update \ 6 | && apt-get install -y git \ 7 | && npm -g install gatsby-cli 8 | 9 | COPY package*.json ./ 10 | 11 | RUN yarn 12 | 13 | COPY . . 14 | RUN mv scripts/* /usr/local/bin \ 15 | && rm -rf scripts \ 16 | && chmod +x /usr/local/bin/* \ 17 | && rm -rf content/* 18 | 19 | ONBUILD COPY . /app 20 | ONBUILD RUN builder.sh 21 | -------------------------------------------------------------------------------- /docker/Dockerfile.develop: -------------------------------------------------------------------------------- 1 | FROM node:buster-slim as builder 2 | 3 | WORKDIR /app 4 | 5 | RUN apt-get update \ 6 | && apt-get install -y git net-tools \ 7 | && npm install -g gatsby-cli 8 | 9 | COPY package*.json ./ 10 | 11 | RUN yarn 12 | 13 | COPY . . 14 | RUN mv scripts/* /usr/local/bin \ 15 | && rm -rf scripts \ 16 | && chmod +x /usr/local/bin/* \ 17 | && rm -rf content/* 18 | 19 | EXPOSE 8000 20 | 21 | CMD ["run_local.sh"] 22 | -------------------------------------------------------------------------------- /src/utils/useDebounce.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | 3 | export default function useDebounce(value, delay) { 4 | const [debouncedValue, setDebouncedValue] = useState(value); 5 | 6 | useEffect(() => { 7 | const handler = setTimeout(() => { 8 | setDebouncedValue(value); 9 | }, delay); 10 | 11 | return () => { 12 | clearTimeout(handler); 13 | }; 14 | }, [value]); 15 | 16 | return debouncedValue; 17 | } 18 | -------------------------------------------------------------------------------- /src/theme/dark.js: -------------------------------------------------------------------------------- 1 | import defaultColors from './colors'; 2 | 3 | const colors = { 4 | ...defaultColors, 5 | 6 | primary: defaultColors.red, 7 | primaryDark: defaultColors.blueDark, 8 | font: '#dddddd', 9 | fontDark: '#8a8a8a', 10 | background: '#29282A', 11 | mainBackground: '#1E1E1F', 12 | border: '#323234', 13 | hover: defaultColors.red, 14 | shadow: defaultColors.gray + '33', 15 | }; 16 | 17 | export default { 18 | colors: colors, 19 | }; 20 | -------------------------------------------------------------------------------- /src/theme/light.js: -------------------------------------------------------------------------------- 1 | import defaultColors from './colors'; 2 | 3 | const colors = { 4 | ...defaultColors, 5 | 6 | primary: defaultColors.blue, 7 | primaryDark: defaultColors.blueDark, 8 | font: '#333334', 9 | fontDark: '#121213', 10 | background: '#F5F7F9', 11 | mainBackground: '#fefefe', 12 | border: '#DBDDDF', 13 | hover: defaultColors.blue, 14 | shadow: defaultColors.gray + '33', 15 | }; 16 | 17 | export default { 18 | colors: colors, 19 | }; 20 | -------------------------------------------------------------------------------- /src/utils/utils.js: -------------------------------------------------------------------------------- 1 | const sleep = (milliseconds) => { 2 | return new Promise((resolve) => setTimeout(resolve, milliseconds)); 3 | }; 4 | 5 | const arraysEqual = (a, b) => { 6 | if (a === b) return true; 7 | if (a == null || b == null) return false; 8 | if (a.length != b.length) return false; 9 | 10 | for (var i = 0; i < a.length; ++i) { 11 | if (a[i] !== b[i]) return false; 12 | } 13 | return true; 14 | }; 15 | 16 | export { sleep, arraysEqual }; 17 | -------------------------------------------------------------------------------- /plugins/gatsby-remark-sectionize-toc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-remark-sectionize-toc", 3 | "description": "Forked sectionize plugin used to generate sections to make react-scrollspy work with Table of Contents", 4 | "author": "Mateusz Filipowicz ", 5 | "version": "0.1.0", 6 | "dependencies": { 7 | "remark": "12.0.1", 8 | "unist-util-find-after": "^3.0.0" 9 | }, 10 | "license": "MIT", 11 | "main": "index.js" 12 | } 13 | -------------------------------------------------------------------------------- /src/components/MdxComponents/anchor.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { anchor } from '../../styles'; 3 | import { useTheme } from 'emotion-theming'; 4 | import { Link } from '../'; 5 | 6 | const AnchorTag = ({ children: link, ...props }) => { 7 | if (link) { 8 | return ( 9 | 10 | {link} 11 | 12 | ); 13 | } else { 14 | return null; 15 | } 16 | }; 17 | 18 | export default AnchorTag; 19 | -------------------------------------------------------------------------------- /src/theme/colors.js: -------------------------------------------------------------------------------- 1 | export default { 2 | blue: '#0066CC', 3 | blueLight: '#CDDFF5', 4 | blueDark: '#264D99', 5 | red: '#E40046', 6 | // redLight: "#FCE7EE", 7 | redLight: '#FAD0DD', 8 | violet: '#A05EB5', 9 | green: '#00965E', 10 | greenLight: '#D0EBE1', 11 | yellow: '#FFC72C', 12 | orange: '#ED8B00', 13 | orangeLight: '#FBE9D0', 14 | white: '#FFFFFF', 15 | black: '#000000', 16 | gray: '#5C6975', 17 | grayLight: '#AEAEAE', 18 | grayDark: '#3B4656', 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/Link/Link.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link as GatsbyLink } from 'gatsby'; 3 | import isAbsoluteUrl from 'is-absolute-url'; 4 | 5 | const Link = ({ to, ...props }) => 6 | isAbsoluteUrl(to) ? ( 7 | 13 | {props.children} 14 | 15 | ) : ( 16 | 17 | ); 18 | 19 | export default Link; 20 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | export * from './Buttons'; 2 | export DarkModeSwitch from './DarkModeSwitch'; 3 | export EditOnRepo from './EditOnRepo'; 4 | export Header from './Header'; 5 | export Layout from './Layout'; 6 | export Link from './Link'; 7 | export MdxComponents from './MdxComponents'; 8 | export * from './Navigation'; 9 | export PreviousNext from './PreviousNext'; 10 | export ScrollTop from './ScrollTop'; 11 | export * from './Search'; 12 | export Seo from './Seo'; 13 | export Sidebar from './Sidebar'; 14 | export TableOfContents from './TableOfContents'; 15 | export ThemeProvider from './ThemeProvider'; 16 | -------------------------------------------------------------------------------- /content/developing/running_locally.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Running Locally" 3 | order: 1 4 | --- 5 | 6 | If you want to develop BooGi or use plain Gatsby for your BooGi app 7 | development, then you can follow this guide. 8 | 9 | ## Download dependencies 10 | 11 | ``` 12 | yarn 13 | ``` 14 | 15 | ## Run Gatsby in development mode 16 | 17 | ``` 18 | gatsby develop 19 | ``` 20 | This will run BooGi on port 8000 on localhost. 21 | 22 | ## Build and run docker image locally 23 | 24 | ``` 25 | docker build -t boogi -f docker/Dockerfile . 26 | docker run -dp 80:80 boogi 27 | ``` 28 | This will run BooGi on port 80 on localhost. 29 | -------------------------------------------------------------------------------- /src/images/help.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/Search/Stats.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { paddingLeftRight } from './styles'; 3 | 4 | const Token = styled.span` 5 | font-weight: bold; 6 | `; 7 | 8 | const StatsWrapper = styled.p` 9 | color: ${(props) => props.theme.search.font.base}; 10 | font-size: 11px; 11 | text-align: right; 12 | margin-top: 10px; 13 | `; 14 | 15 | const Stats = ({ hits, processingTimeMS }) => ( 16 | 17 | Found 18 | {hits} results in 19 | {processingTimeMS} ms 20 | 21 | ); 22 | 23 | export default Stats; 24 | -------------------------------------------------------------------------------- /src/components/MdxComponents/card.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { useTheme } from 'emotion-theming'; 4 | import { ArrowRight } from 'react-feather'; 5 | import { shadowAround } from '../../styles'; 6 | 7 | const Card = styled.div` 8 | display: flex; 9 | flex-direction: column; 10 | width: 100%; 11 | padding: 16px; 12 | margin: 10px 0; 13 | border-radius: 4px; 14 | border: 1px solid transparent; 15 | transition: ${(props) => props.theme.transitions.hover}; 16 | `; 17 | 18 | export default ({ ...props }) => { 19 | return ; 20 | }; 21 | -------------------------------------------------------------------------------- /plugins/gatsby-plugin-draft/gatsby-node.js: -------------------------------------------------------------------------------- 1 | const defaultOptions = { 2 | publishDraft: false, 3 | }; 4 | 5 | exports.onCreateNode = ({ node, actions }, pluginOptions) => { 6 | const { createNodeField } = actions; 7 | 8 | const options = { 9 | ...defaultOptions, 10 | ...pluginOptions, 11 | }; 12 | 13 | if (node.internal.type !== 'MarkdownRemark' && node.internal.type !== 'Mdx') { 14 | return; 15 | } 16 | 17 | const isDraft = options.publishDraft === false && node.frontmatter && node.frontmatter.draft === true; 18 | createNodeField({ 19 | node, 20 | name: "draft", 21 | value: isDraft 22 | }); 23 | 24 | }; -------------------------------------------------------------------------------- /content/gettingstarted/page_structure.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Page structure' 3 | order: 2 4 | --- 5 | 6 | Check BooGi page layout and how different components relate to each other 7 | and where they are placed. 8 | 9 | ### General page structure 10 | 11 | ![](../images/structure_general.png) 12 | 13 | --- 14 | 15 | ### Header 16 | 17 | ![](../images/structure_header.png) 18 | 19 | --- 20 | 21 | ### Navigation sidebar 22 | 23 | ![](../images/structure_sidebar.png) 24 | 25 | --- 26 | 27 | ### Page content 28 | 29 | ![](../images/structure_content.png) 30 | 31 | --- 32 | 33 | ### Search sidebar 34 | 35 | ![](../images/structure_search.png#) 36 | 37 | --- -------------------------------------------------------------------------------- /src/components/Search/algolia/hitComps.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Highlight, Snippet } from 'react-instantsearch-dom'; 3 | import { Hit } from '../Hits'; 4 | import emoji from '../../../utils/emoji'; 5 | 6 | export const PageHit = ({ hit }) => { 7 | hit._highlightResult.title.value = emoji.emojify(hit._highlightResult.title.value); 8 | hit._snippetResult.excerpt.value = emoji.emojify(hit._snippetResult.excerpt.value); 9 | return ( 10 | } 13 | details={} 14 | /> 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /src/utils/colors.js: -------------------------------------------------------------------------------- 1 | import colorfn from 'color'; 2 | 3 | export const increaseIntensivity = (color, factor) => { 4 | const clr = colorfn(color); 5 | const intensified = clr.isDark() ? clr.darken(factor) : clr.lighten(factor); 6 | return intensified.hex(); 7 | }; 8 | 9 | export const decreaseIntensivity = (color, factor) => { 10 | const clr = colorfn(color); 11 | const luminStd = 1 / clr.luminosity(); 12 | const fc = luminStd > 6 ? factor * 6 : factor * luminStd; 13 | const intensified = clr.isDark() ? clr.lighten(fc) : clr.darken(fc); 14 | return intensified.hex(); 15 | }; 16 | 17 | export const grayscaleCompatible = (color) => { 18 | return increaseIntensivity(colorfn(color).negate().grayscale().hex(), 0.7); 19 | }; -------------------------------------------------------------------------------- /content/configuration/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Introduction" 3 | order: 0 4 | --- 5 | 6 | BooGi gives you tons of customization and configuration options. You can not 7 | only turn on/off and adjust nearly any feature available in BooGi, but 8 | also adjust its color theme (or two if dark mode is enabled). 9 | 10 | Before starting to configure your own BooGi page, we recommend going through 11 | basic information about [page structure](/gettingstarted/page_structure) 12 | and [navigation](/configuration/navigation) details. 13 | 14 | Then, start with [Setting Up](/configuration/setting-up) to configure your BooGi page 15 | to match your desired features and metadata. After that you can apply your own 16 | branding or tune theme to your needs. To do this follow 17 | [Themes / styling](/configuration/themes) guide. -------------------------------------------------------------------------------- /src/components/DarkModeSwitch/DarkModeSwitch.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | import { Sun as DayImage, Moon as NightImage } from 'react-feather'; 4 | import { ButtonIcon } from '../'; 5 | 6 | const DarkModeSwitch = ({ isDarkThemeActive, toggleActiveTheme, ...props }) => { 7 | const img = isDarkThemeActive ? NightImage : DayImage; 8 | return ; 9 | }; 10 | 11 | DarkModeSwitch.propTypes = { 12 | isDarkThemeActive: PropTypes.bool.isRequired, 13 | toggleActiveTheme: PropTypes.func.isRequired, 14 | background: PropTypes.string, 15 | hoverFill: PropTypes.string, 16 | hoverStroke: PropTypes.string, 17 | fill: PropTypes.string, 18 | stroke: PropTypes.string, 19 | }; 20 | 21 | export default DarkModeSwitch; 22 | -------------------------------------------------------------------------------- /src/components/Sidebar/contentTree.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import ContentTreeGroup from './contentTreeGroup'; 3 | import { calculateNavigation } from '../'; 4 | 5 | const ContentTree = ({ edges, location }) => { 6 | const [treeData] = useState(() => calculateNavigation(edges)); 7 | const [collapsed, setCollapsed] = useState({}); 8 | return ( 9 | <> 10 | {treeData.children.map((group) => { 11 | const key = group.path ? group.path : Math.random().toString(36).substring(2); 12 | return ( 13 | 19 | ); 20 | })} 21 | 22 | ); 23 | }; 24 | 25 | export default ContentTree; 26 | -------------------------------------------------------------------------------- /content/gettingstarted/concepts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Concepts' 3 | order: 1 4 | --- 5 | 6 | # Docs as Code 7 | 8 | Docs as Code means applying software development processes and tools to 9 | documentation you create. Teams write content in plain text with 10 | [Markdown](https://www.markdownguide.org/) using text editor. Then content is 11 | transformed to documentation page using a static site generator rather than 12 | a publishing system. 13 | 14 | # Markdown 15 | 16 | Markdown is a plain text formatting syntax aimed at making writing for the Internet easier. 17 | The philosophy behind Markdown is that plain text documents should be readable without 18 | tags mussing everything up, but there should still be ways to add text modifiers 19 | like lists, bold, italics, etc. It is an alternative to _WYSIWYG_ (what you see is what 20 | you get) editors, which use rich text that later gets converted to proper HTML. 21 | 22 | -------------------------------------------------------------------------------- /scripts/run_local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echoerr() { echo "$@" 1>&2; } 4 | 5 | link_data() { 6 | echo "Linking $1" 7 | [[ -n "$2" ]] && subpath=$2 || subpath="" 8 | [[ -n "$3" ]] && optional=$3 || optional=false 9 | if [[ -d "/develop/$1" ]]; then 10 | echo "skip" 11 | ln -sfn "/develop/$1/*" "/app/$subpath" 12 | elif [[ -f "/develop/$1" ]]; then 13 | ln -sfn "/develop/$1" "/app/$subpath" 14 | elif [ "$optional" = true ]; then 15 | echo "Optional path $1 does not exist" 16 | else 17 | echoerr "Error: Path $1 does not exist" 18 | exit -1 19 | fi 20 | return 0 21 | } 22 | 23 | link_data content content 24 | link_data config.js 25 | link_data assets static/assets/ true 26 | link_data theme.js src/theme/main.js true 27 | 28 | echo "Starting gatsby in development mode. It will run on your localhost!" 29 | cd /app 30 | gatsby develop --host $(ifconfig eth0 | grep 'inet' | awk '{print $2}') 31 | exit $? 32 | -------------------------------------------------------------------------------- /src/components/MdxComponents/layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | 4 | const LayoutEl = styled.div` 5 | display: flex; 6 | flex-direction: ${(props) => props.direction || 'row'}; 7 | align-items: ${(props) => props.align}; 8 | &.spacing-xlarge > * { 9 | margin: 64px 64px 64px 0; 10 | } 11 | &.spacing-large > * { 12 | margin: 32px 32px 32px 0; 13 | } 14 | &.spacing-medium > * { 15 | margin: 16px 16px 16px 0; 16 | } 17 | &.spacing-small > * { 18 | margin: 8px 8px 8px 0; 19 | } 20 | &.spacing-xsmall > * { 21 | margin: 4px 4px 4px 0; 22 | } 23 | & > * { 24 | flex: 1 1 0px; 25 | } 26 | `; 27 | const Layout = ({ children, spacing, ...props }) => { 28 | let space = spacing || 'medium'; 29 | return ( 30 | 31 | {children} 32 | 33 | ); 34 | }; 35 | 36 | export default Layout; 37 | -------------------------------------------------------------------------------- /src/components/MdxComponents/icon.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as Icons from 'react-feather'; 3 | import { useTheme } from 'emotion-theming'; 4 | 5 | const capitalize = (text) => text.charAt(0).toUpperCase() + text.slice(1); 6 | 7 | const Icon = ({ ...props }) => { 8 | const theme = useTheme(); 9 | let name = props.name 10 | .split('-') 11 | .map(capitalize) 12 | .reduce((acc, value) => (acc += value), ''); 13 | const icon = Icons[name]; 14 | if (!icon) { 15 | return ''; 16 | } 17 | 18 | const config = { 19 | size: props.size || 22, 20 | color: props.color || theme.colors.font, 21 | }; 22 | const margin = props.margin || '5px'; 23 | return ( 24 | 32 | {icon.render(config)} 33 | 34 | ); 35 | }; 36 | 37 | export default Icon; 38 | -------------------------------------------------------------------------------- /src/images/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/styles/responsive.js: -------------------------------------------------------------------------------- 1 | const breakpointsInt = { 2 | small: 768, 3 | large: 1170, 4 | }; 5 | 6 | const breakpoints = {}; 7 | 8 | Object.keys(breakpointsInt).map(function (key, index) { 9 | breakpoints[key] = breakpointsInt[key] + 'px'; 10 | }); 11 | 12 | const mq = Object.values(breakpoints).map((bp) => `@media (max-width: ${bp})`); 13 | 14 | const checkViewport = (maxValue) => { 15 | if (typeof document !== `undefined`) { 16 | const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0); 17 | return vw <= maxValue; 18 | } 19 | return false; 20 | }; 21 | 22 | export const isMobile = () => { 23 | return checkViewport(breakpointsInt.small); 24 | }; 25 | 26 | export const isTablet = () => { 27 | return checkViewport(breakpointsInt.large); 28 | }; 29 | 30 | export const isNormal = () => { 31 | return !(isMobile() || isTablet()); 32 | }; 33 | 34 | export const onMobile = mq[0]; 35 | 36 | export const onTablet = mq[1]; 37 | -------------------------------------------------------------------------------- /src/images/up-arrow.inline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/images/bitbucket.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Bitbucket-blue 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/utils/algolia.js: -------------------------------------------------------------------------------- 1 | module.exports = (indexName, excerptSize) => { 2 | const pageQuery = `{ 3 | pages: allMdx(filter: {fields: {draft: {ne: true}}}) { 4 | edges { 5 | node { 6 | objectID: id 7 | fields { 8 | slug 9 | } 10 | headings { 11 | value 12 | } 13 | frontmatter { 14 | title 15 | description 16 | } 17 | excerpt(pruneLength: ${excerptSize}) 18 | } 19 | } 20 | } 21 | }`; 22 | 23 | const flatten = (arr) => 24 | arr.map(({ node: { frontmatter, fields, ...rest } }) => ({ 25 | ...frontmatter, 26 | ...fields, 27 | ...rest, 28 | })); 29 | 30 | const settings = { attributesToSnippet: [`excerpt:20`] }; 31 | 32 | return [ 33 | { 34 | query: pageQuery, 35 | transformer: ({ data }) => flatten(data.pages.edges), 36 | indexName: indexName, 37 | settings, 38 | }, 39 | ]; 40 | }; 41 | -------------------------------------------------------------------------------- /src/components/MdxComponents/badge.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import colorFn from 'color'; 4 | import { useTheme } from 'emotion-theming'; 5 | 6 | const BadgeWrapper = styled.span` 7 | padding: 4px 8px; 8 | background: ${(props) => props.background}; 9 | color: ${(props) => props.foreground}; 10 | border-radius: 16px; 11 | min-width: 14px; 12 | font-weight: bold; 13 | font-size: 12px; 14 | align-self: flex-start; 15 | margin-left: 4px; 16 | margin-right: 4px; 17 | `; 18 | 19 | const Badge = ({ children, color, ...props }) => { 20 | const theme = useTheme(); 21 | 22 | const background = color || theme.colors.primary; 23 | 24 | const foreground = 25 | colorFn(background).luminosity() < 0.5 ? 'rgba(255,255,255,0.95)' : 'rgba(0,0,0,0.95)'; 26 | return ( 27 | 28 | {children} 29 | 30 | ); 31 | }; 32 | 33 | export default Badge; 34 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:import/errors", 5 | "plugin:react/recommended", 6 | "plugin:jsx-a11y/recommended", 7 | "prettier", 8 | "prettier/react" 9 | ], 10 | "plugins": ["react", "import", "jsx-a11y"], 11 | "settings": { 12 | "react": { 13 | "version": "detect" 14 | } 15 | }, 16 | "rules": { 17 | "react/prop-types": 0, 18 | "react/react-in-jsx-scope": "off", 19 | "lines-between-class-members": ["error", "always"], 20 | "padding-line-between-statements": "off", 21 | "import/no-unresolved": "off", 22 | "jsx-a11y/click-events-have-key-events": "off" 23 | }, 24 | "parser": "babel-eslint", 25 | "parserOptions": { 26 | "ecmaVersion": 10, 27 | "sourceType": "module", 28 | "ecmaFeatures": { 29 | "jsx": true 30 | } 31 | }, 32 | "env": { 33 | "es6": true, 34 | "browser": true, 35 | "node": true 36 | }, 37 | "globals": { 38 | "graphql": false 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/utils/jargon-config.js: -------------------------------------------------------------------------------- 1 | const { readYamlOrJson } = require('./fileUtils'); 2 | const jargonData = readYamlOrJson(__dirname + '/../../config/jargon.yml'); 3 | 4 | const validateProperty = (entry, property, key) => { 5 | const value = entry[property]; 6 | if (typeof value === 'undefined' || value === null || value.length === 0) { 7 | throw "Property '" + property + "' is not defined for jargon entry '" + key + "'!"; 8 | } 9 | }; 10 | 11 | const jargon = {}; 12 | for (const key in jargonData) { 13 | const entry = jargonData[key]; 14 | validateProperty(entry, 'name', key); 15 | validateProperty(entry, 'description', key); 16 | let long_description = '' + entry.name + ''; 17 | 18 | let long_name = entry.long_name; 19 | if (typeof long_name !== 'undefined' && long_name !== null && long_name.length > 0) { 20 | long_description += ' - ' + long_name; 21 | } 22 | long_description += ' ' + entry.description; 23 | jargon[key] = long_description; 24 | } 25 | 26 | module.exports = jargon; 27 | -------------------------------------------------------------------------------- /content/editing/rich_content.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Rich content" 3 | order: 2 4 | --- 5 | 6 | BooGi, in addition to standard Markdown elements, offers additinal components 7 | which support you in building beautiful, rich pages. You can embed these 8 | components in the content of each page. It's simple -- really anyone can do it :smiley: 9 | 10 | Available components: 11 | - columnar layout 12 | - accordion 13 | - jargon / abbreviations 14 | - emojis 15 | - highlights 16 | - badges 17 | - cards 18 | - link, cards 19 | - file download cards 20 | - image cards 21 | - graphs and diagrams 22 | - code snippets / code highlighting 23 | - [feather icons](https://feathericons.com/) 24 | - embeds from popular services: Instagram, Giphy, Codepen, 25 | Codesandbox, Pinterest, Spotify, Twitch, Twitter, Youtube 26 | 27 | Components which are planned: 28 | - tabs 29 | - math formulas 30 | - embedded code snippets from public repositories on Github, Gitlab, Bitbucket 31 | - embed Open API definitions 32 | 33 | To learn more visit [Custom Components](/rich_content/custom_components) page. -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: CD 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - develop 7 | tags: 8 | - 'v*' 9 | 10 | jobs: 11 | 12 | docker: 13 | name: Build and push Docker images 14 | runs-on: ubuntu-latest 15 | strategy: 16 | fail-fast: true 17 | matrix: 18 | dockerfile: ['builder', 'runtime', 'develop'] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | 23 | - name: Build Docker image 24 | uses: docker/build-push-action@v1.1.0 25 | with: 26 | username: filipowm 27 | password: ${{ secrets.DOCKER_REGISTRY_PASS }} 28 | repository: filipowm/boogi-${{ matrix.dockerfile }} 29 | tags: latest 30 | tag_with_ref: true 31 | tag_with_sha: true 32 | # Path to the Dockerfile (Default is '{path}/Dockerfile') 33 | dockerfile: 'docker/Dockerfile.${{ matrix.dockerfile }}' 34 | always_pull: false 35 | cache_froms: node:buster-slim 36 | add_git_labels: true 37 | push: true # optional, default is true 38 | -------------------------------------------------------------------------------- /src/components/MdxComponents/imageCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { useTheme } from 'emotion-theming'; 4 | import Card from './card'; 5 | import { onMobile } from '../../styles/responsive'; 6 | 7 | const ImageCard = styled(Card)` 8 | width: ${(props) => props.width}; 9 | height: ${(props) => props.height}; 10 | ${onMobile} { 11 | width: 100%; 12 | height: 100%; 13 | } 14 | `; 15 | 16 | const Image = styled.img` 17 | align-self: center; 18 | border-radius: 4px; 19 | `; 20 | 21 | 22 | const Text = styled.p` 23 | margin-top: 15px; 24 | & > p:first-child { 25 | margin-top: 0; 26 | } 27 | & > p:last-child { 28 | margin-bottom: 0; 29 | } 30 | `; 31 | 32 | export default ({ children, width, height, src}) => { 33 | const theme = useTheme(); 34 | const imgWidth = width ? width : '50%'; 35 | const imgHeight = width ? width : '50%'; 36 | return ( 37 | 38 | 39 | {children} 40 | 41 | ); 42 | }; 43 | -------------------------------------------------------------------------------- /src/components/Search/Status.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { css } from '@emotion/core'; 4 | import { marginLeftRight } from './styles'; 5 | import Loading from '../Loader'; 6 | 7 | const queryToken = css` 8 | font-weight: bold; 9 | font-style: italic; 10 | display: block; 11 | `; 12 | 13 | const StatusWrapper = styled.div` 14 | margin: 30px auto; 15 | text-align: center; 16 | line-height: 30px; 17 | span { 18 | font-size: 1.2em; 19 | } 20 | `; 21 | 22 | const SearchStatus = ({ searching, noHits, query }) => { 23 | let text = ''; 24 | if (searching) { 25 | text = 'Searching...'; 26 | } else if (noHits) { 27 | text = `No results found for `; 28 | } 29 | return text !== '' ? ( 30 |
31 | 32 | {text} 33 | {noHits ? {query} : ''} 34 | {searching ? : ''} 35 | 36 |
37 | ) : ( 38 | '' 39 | ); 40 | }; 41 | 42 | export default SearchStatus; 43 | -------------------------------------------------------------------------------- /.github/workflows/cleaner.yml: -------------------------------------------------------------------------------- 1 | name: Clean repository 2 | on: 3 | schedule: 4 | - cron: "0 4 * * *" 5 | 6 | jobs: 7 | 8 | cleanup: 9 | name: Cleanup repository from stale draft, PRs and issues 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Delete draft releases 14 | uses: hugo19941994/delete-draft-releases@v0.1.0 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | 18 | - name: Cleanup stale issues and PRs 19 | uses: actions/stale@v3 20 | with: 21 | repo-token: ${{ secrets.GITHUB_TOKEN }} 22 | stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days' 23 | stale-pr-message: 'This PR is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days' 24 | days-before-stale: 30 25 | days-before-close: 5 26 | stale-issue-label: 'no-activity' 27 | exempt-issue-labels: 'work-in-progress' 28 | stale-pr-label: 'no-activity' 29 | exempt-pr-labels: 'work-in-progress' 30 | -------------------------------------------------------------------------------- /src/utils/fileUtils.js: -------------------------------------------------------------------------------- 1 | const { readFileSync, close, open, utimes } = require('fs'); 2 | const yaml = require('js-yaml'); 3 | const path = require('path'); 4 | 5 | const touch = (path) => { 6 | return new Promise((resolve, reject) => { 7 | const time = new Date(); 8 | utimes(path, time, time, (err) => { 9 | if (err) { 10 | return open(path, 'w', (err, fd) => { 11 | if (err) return reject(err); 12 | close(fd, (err) => (err ? reject(err) : resolve(fd))); 13 | }); 14 | } 15 | resolve(); 16 | }); 17 | }); 18 | }; 19 | 20 | const readYamlOrJson = (path) => { 21 | try { 22 | if (path.endsWith('.yml') || path.endsWith('.yaml')) { 23 | const fileContents = readFileSync(path, 'utf8'); 24 | return yaml.safeLoad(fileContents); 25 | } else if (path.endsWith('.json')) { 26 | return require(path); 27 | } 28 | throw 'Config file must be either YAML or JSON'; 29 | } catch (err) { 30 | console.error(err); 31 | return {}; 32 | } 33 | }; 34 | 35 | const rootDir = () => path.dirname(require.main.filename); 36 | 37 | module.exports = { touch, readYamlOrJson, rootDir }; 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mateusz Filipowicz 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 | -------------------------------------------------------------------------------- /src/images/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /src/theme/index.js: -------------------------------------------------------------------------------- 1 | import base from './base'; 2 | import lightTheme from './light'; 3 | import darkTheme from './dark'; 4 | import _ from 'lodash'; 5 | 6 | class ThemeBuilder { 7 | constructor(base) { 8 | this.result = _.cloneDeep(base); 9 | } 10 | 11 | applyColors(colors) { 12 | this.result['colors'] = _.merge(this.result.colors, colors); 13 | return this; 14 | } 15 | 16 | initialize() { 17 | for (let [key, value] of Object.entries(this.result)) { 18 | if (typeof value === 'function') { 19 | this.result[key] = value(this.result.colors); 20 | } else { 21 | this.result[key] = value; 22 | } 23 | } 24 | return this; 25 | } 26 | 27 | applyTheme(theme) { 28 | this.result = _.merge(this.result, theme); 29 | return this; 30 | } 31 | 32 | get() { 33 | return this.result; 34 | } 35 | } 36 | 37 | export const dark = new ThemeBuilder(base) 38 | .applyColors(darkTheme.colors) 39 | .initialize() 40 | .applyTheme(darkTheme) 41 | .get(); 42 | 43 | export const light = new ThemeBuilder(base) 44 | .applyColors(lightTheme.colors) 45 | .initialize() 46 | .applyTheme(lightTheme) 47 | .get(); 48 | -------------------------------------------------------------------------------- /src/components/Buttons/ButtonIcon.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | import styled from '@emotion/styled'; 4 | 5 | const ButtonIconWrapper = styled('div')` 6 | display: flex; 7 | justify-content: flex-end; 8 | padding: 4px; 9 | outline: none; 10 | 11 | background-color: ${(props) => props.background}; 12 | border-radius: 50%; 13 | cursor: pointer; 14 | &:hover { 15 | svg { 16 | fill: ${(props) => props.hoverFill}; 17 | stroke: ${(props) => props.hoverStroke}; 18 | } 19 | } 20 | svg { 21 | transition: ${(props) => props.theme.transitions.hover}; 22 | fill: ${(props) => props.fill}; 23 | stroke: ${(props) => props.stroke}; 24 | } 25 | `; 26 | 27 | const ButtonIcon = ({ icon, ...props }) => { 28 | return ( 29 | 30 | {/* not defining color as a workaround to use css styling instead */} 31 | {icon.render({ color: '' })} 32 | 33 | ); 34 | }; 35 | 36 | ButtonIcon.propTypes = { 37 | background: PropTypes.string, 38 | hoverFill: PropTypes.string, 39 | hoverStroke: PropTypes.string, 40 | fill: PropTypes.string, 41 | stroke: PropTypes.string, 42 | icon: PropTypes.object.isRequired, 43 | }; 44 | 45 | export default ButtonIcon; 46 | -------------------------------------------------------------------------------- /src/components/Loader/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { css } from '@emotion/core'; 3 | 4 | const spinner = css` 5 | width: 40px; 6 | height: 40px; 7 | 8 | position: relative; 9 | margin: 0 auto; 10 | 11 | .double-bounce1, 12 | .double-bounce2 { 13 | width: 100%; 14 | height: 100%; 15 | border-radius: 50%; 16 | background-color: #333; 17 | opacity: 0.6; 18 | position: absolute; 19 | top: 0; 20 | left: 0; 21 | 22 | -webkit-animation: sk-bounce 2s infinite ease-in-out; 23 | animation: sk-bounce 2s infinite ease-in-out; 24 | } 25 | 26 | .double-bounce2 { 27 | -webkit-animation-delay: -1s; 28 | animation-delay: -1s; 29 | } 30 | 31 | @-webkit-keyframes sk-bounce { 32 | 0%, 33 | 100% { 34 | -webkit-transform: scale(0); 35 | } 36 | 50% { 37 | -webkit-transform: scale(1); 38 | } 39 | } 40 | 41 | @keyframes sk-bounce { 42 | 0%, 43 | 100% { 44 | transform: scale(0); 45 | -webkit-transform: scale(0); 46 | } 47 | 50% { 48 | transform: scale(1); 49 | -webkit-transform: scale(1); 50 | } 51 | } 52 | `; 53 | 54 | export default ({ ...props }) => ( 55 |
56 |
57 |
58 |
59 | ); -------------------------------------------------------------------------------- /content/editing/images.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Adding images" 3 | order: 3 4 | --- 5 | 6 | You can add and embed images into content, as this is standard Markdown feature. 7 | First, add images under `content` directory -- it can be a single `images` directory 8 | or images can be added to any directory within `content` dir. 9 | 10 | Then, once you have added images, reference them using Markdown image 11 | syntax: `![some image caption](myImage.png)`. You can use any image format, 12 | however images in `png` and `jpeg` formats will be additionally processed to improve 13 | overal site performance and user experience. 14 | 15 | You can leave caption empty, e.g. `![](../images/myimg.png)`. 16 | You can also reference external images by providing full URL. 17 | 18 | **Example** 19 | 20 | Assuming, that all images will be embed in `page.md`: 21 | 22 | 23 | 24 | ``` 25 | project 26 | │ 27 | └───content 28 | │── index.md 29 | │── img1.png 30 | │ 31 | └───images 32 | │ │── img2.png 33 | │ └───another 34 | │ │── img3.png 35 | │ 36 | └───group 37 | │── page.md 38 | │── img4.jpeg 39 | ``` 40 | 41 | ```markdown 42 | ![first image](../img1.png) 43 | 44 | ![second image](../images/img2.png) 45 | 46 | ![third image](../images/another/img3.png) 47 | 48 | ![fourth image](img4.png) 49 | 50 | ``` 51 | 52 | -------------------------------------------------------------------------------- /src/components/MdxComponents/linkCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { useTheme } from 'emotion-theming'; 4 | import { ArrowRight } from 'react-feather'; 5 | import emoji from '../../utils/emoji'; 6 | import Link from '../Link'; 7 | import { decreaseIntensivity } from '../../utils/colors'; 8 | import Card from './card'; 9 | 10 | const LinkCard = styled(Card)` 11 | cursor: pointer; 12 | flex-direction: row; 13 | align-items: center; 14 | &:hover { 15 | border: 1px solid ${(props) => props.theme.colors.primary}; 16 | } 17 | `; 18 | 19 | const LinkPath = styled.div` 20 | color: ${(props) => decreaseIntensivity(props.theme.colors.fontLight, 0.25)}; 21 | font-size: 9pt; 22 | padding-left: 16px; 23 | text-align: right; 24 | `; 25 | 26 | const Title = styled.div` 27 | padding: 0 14px; 28 | color: ${(props) => props.theme.colors.primary}; 29 | font-size: 12pt; 30 | font-weight: 500; 31 | flex: 1; 32 | `; 33 | 34 | export default ({ title, url }) => { 35 | const theme = useTheme(); 36 | const path = url.replace(/(https:\/\/)|(http:\/\/)/, ''); 37 | return ( 38 | 39 | 40 | 41 | {emoji.emojify(title)} 42 | {path} 43 | 44 | 45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /src/components/MdxComponents/fileDownloadCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { useTheme } from 'emotion-theming'; 4 | import { Download } from 'react-feather'; 5 | import emoji from '../../utils/emoji'; 6 | import { decreaseIntensivity } from '../../utils/colors'; 7 | import Card from './card'; 8 | 9 | const DownloadCard = styled(Card)` 10 | cursor: pointer; 11 | flex-direction: row; 12 | align-items: center; 13 | &:hover { 14 | border: 1px solid ${(props) => props.theme.colors.primary}; 15 | } 16 | `; 17 | 18 | const DownloadPath = styled.div` 19 | color: ${(props) => decreaseIntensivity(props.theme.colors.fontLight, 0.25)}; 20 | font-size: 9pt; 21 | padding-left: 16px; 22 | text-align: right; 23 | `; 24 | 25 | const Title = styled.div` 26 | padding: 0 14px; 27 | color: ${(props) => props.theme.colors.primary}; 28 | font-size: 12pt; 29 | font-weight: 500; 30 | flex: 1; 31 | `; 32 | 33 | export default ({ title, url }) => { 34 | const theme = useTheme(); 35 | const splitted = url.split('/') 36 | const filename = splitted[splitted.length - 1] 37 | return ( 38 | 39 | 40 | 41 | {emoji.emojify(title)} 42 | {filename} 43 | 44 | 45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /config/jargon.yml: -------------------------------------------------------------------------------- 1 | --- 2 | markdown: 3 | name: Markdown 4 | description: Very simple and lightweight markup language with plain-text-formatting 5 | syntax. 6 | seo: 7 | name: SEO 8 | long_name: Search Engine Optimization 9 | description: The process of growing the quality and quantity of website traffic 10 | by increasing the visibility of a website or a web page to users of a web search 11 | engine 12 | git: 13 | name: Git 14 | description: Distributed version-control system for tracking changes in source code.It 15 | is designed for coordinating work among programmers, but it can be used to track 16 | changes in any set of files. Its goals include speed, data integrity and support 17 | for distributed, non-linear workflows 18 | api: 19 | name: API 20 | long_name: Application Programming Interface 21 | description: Computing interface which defines interactions between multiple software 22 | intermediaries 23 | saas: 24 | name: SaaS 25 | long_name: Software as a Service 26 | description: Software licensing and delivery model in which software is licensed 27 | on a subscription basis and is centrally hosted and managed 28 | pwa: 29 | name: PWA 30 | long_name: Progressive Web App 31 | description: Web app that uses modern web capabilities to deliver an app-like experience 32 | to users 33 | boogi: 34 | name: BooGi 35 | description: T lulzhe best documentation tool in the space! Check it here. -------------------------------------------------------------------------------- /src/components/ScrollTop/ScrollTop.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import Arrow from 'images/up-arrow.inline.svg'; 4 | import { onMobile, onTablet } from '../../styles/responsive'; 5 | 6 | const scrollToTop = () => window.scrollTo(0, 0); 7 | 8 | const ScrollTop = styled(({ className }) => { 9 | return ( 10 |
11 | 12 |
13 | ); 14 | })` 15 | background-color: ${(props) => props.theme.scrollTop.background}; 16 | width: 35px; 17 | height: 35px; 18 | position: fixed; 19 | border-radius: 50px; 20 | bottom: 50px; 21 | right: 50px; 22 | cursor: pointer; 23 | z-index: 10; 24 | transition: ${(props) => props.theme.transitions.hover}; 25 | ${onTablet} { 26 | background-color: ${(props) => props.theme.scrollTop.background + 'bc'}; 27 | } 28 | ${onMobile} { 29 | bottom: 25px; 30 | right: 25px; 31 | width: 30px; 32 | height: 30px; 33 | background-color: ${(props) => props.theme.scrollTop.background + '9b'}; 34 | } 35 | svg { 36 | width: 50%; 37 | position: absolute; 38 | margin: auto; 39 | top: 0; 40 | left: 0; 41 | right: 0; 42 | bottom: 0; 43 | path { 44 | fill: ${(props) => props.theme.scrollTop.arrow}; 45 | } 46 | } 47 | &:hover { 48 | background: ${(props) => props.theme.scrollTop.hover}; 49 | } 50 | `; 51 | 52 | export default ScrollTop; 53 | -------------------------------------------------------------------------------- /src/components/Sidebar/links.js: -------------------------------------------------------------------------------- 1 | import { ExternalLink } from 'react-feather'; 2 | import React from 'react'; 3 | import styled from '@emotion/styled'; 4 | import { flex, transparent } from '../../styles'; 5 | 6 | const Link = styled(({ className, to, text }) => { 7 | return ( 8 |
  • 9 | 10 | {text} 11 | 14 | 15 |
  • 16 | ); 17 | })` 18 | list-style: none; 19 | a { 20 | font-size: 14px; 21 | color: ${(props) => props.theme.navigationSidebar.font.base}; 22 | text-decoration: none; 23 | font-weight: ${({ level }) => (level === 0 ? 700 : 400)}; 24 | padding: 0.45rem 0 0.45rem 16px; 25 | button svg * { 26 | color: ${(props) => props.theme.navigationSidebar.font.base}; 27 | } 28 | &:hover { 29 | color: ${(props) => props.theme.navigationSidebar.font.hover}; 30 | button svg * { 31 | color: ${(props) => props.theme.navigationSidebar.font.hover}; 32 | } 33 | } 34 | button { 35 | padding: 0 25px 0 10px; 36 | cursor: pointer; 37 | } 38 | } 39 | `; 40 | 41 | const Links = ({ links }) => ( 42 |
      43 | {links.map((link, key) => { 44 | if (link.link !== '' && link.text !== '') { 45 | return ; 46 | } 47 | })} 48 |
    49 | ); 50 | 51 | export default Links; 52 | -------------------------------------------------------------------------------- /plugins/gatsby-plugin-disable-localsearch/gatsby-node.js: -------------------------------------------------------------------------------- 1 | exports.createPages = async (gatsbyContext, pluginOptions) => { 2 | const { 3 | actions, 4 | createNodeId, 5 | createContentDigest, 6 | } = gatsbyContext 7 | const { createNode } = actions 8 | const { 9 | name 10 | } = pluginOptions 11 | 12 | const nodeType = `localSearch${name}`; 13 | const nodeId = createNodeId(name); 14 | const node = { 15 | id: nodeId, 16 | name: name, 17 | index: '', 18 | store: {}, 19 | internal: { 20 | type: nodeType, 21 | contentDigest: createContentDigest({ index: [], store: [] }), 22 | }, 23 | } 24 | 25 | createNode(node) 26 | } 27 | 28 | exports.createSchemaCustomization = async ( 29 | gatsbyContext, 30 | pluginOptions, 31 | ) => { 32 | const { actions, schema } = gatsbyContext 33 | const { createTypes } = actions 34 | const { name } = pluginOptions 35 | 36 | const nodeType = `localSearch${name}`; 37 | 38 | createTypes([ 39 | schema.buildObjectType({ 40 | name: nodeType, 41 | fields: { 42 | name: { 43 | type: 'String!', 44 | description: 'The name of the index.', 45 | }, 46 | index: { 47 | type: 'String!', 48 | description: 'The search index created using the selected engine.', 49 | }, 50 | store: { 51 | type: 'JSON!', 52 | description: 53 | 'A JSON object used to map search results to their data.', 54 | }, 55 | }, 56 | interfaces: ['Node'], 57 | }), 58 | ]) 59 | } -------------------------------------------------------------------------------- /content/editing/rich_content/abbreviations.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Abbreviations" 3 | order: 2 4 | --- 5 | 6 | BooGi offers creating inline definitions of 7 | popular jargon used. This is a good way to define abbreviations / terms 8 | used in your company / area / profession. 9 | 10 | First define your jargon / definitions in `jargon.yml` in `config` 11 | directory. 12 | 13 | ## Configuration 14 | 15 | Format of yaml file is following: 16 | 17 | ```yaml 18 | : # key by which term can be referenced in the page content to embed jargon 19 | name: String # required name of term 20 | long_name: String # optional, long name of term 21 | description: String # optional, term description 22 | ``` 23 | 24 | **Example:** 25 | 26 | `jargon.yml` 27 | ```yaml 28 | saas: 29 | name: SaaS 30 | long_name: Software as a Service 31 | description: Software licensing and delivery model in which software is licensed 32 | on a subscription basis and is centrally hosted and managed 33 | boogi: 34 | name: BooGi 35 | description: The best documentation tool in the space! Check it here. 36 | ``` 37 | 38 | When under development mode, you must restart BooGi to apply new jargon configuration. 39 | 40 | ## Usage 41 | 42 | Then in markdown wrap word defined as key (`term_key`) above in `_` to use Jargon 43 | (key is case-insensitive, thus `term_key` or `TERM_key` reference same term). 44 | 45 | **Example** 46 | 47 | ```markdown 48 | You can use any _saas_ application. 49 | 50 | Try _BooGi_ -- you'll love it :heart: ! 51 | ``` 52 | 53 | You can use any _saas_ application. 54 | 55 | Try _BooGi_ -- you'll love it :heart: ! -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | 9 | release: 10 | name: Update changelog and create release 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Conventional Changelog 17 | id: changelog 18 | uses: TriPSs/conventional-changelog-action@v2 19 | with: 20 | github-token: ${{ secrets.github_token }} 21 | output-file: 'CHANGELOG.md' 22 | tag-prefix: 'v' 23 | release-count: 0 24 | package-json: './package.json' 25 | 26 | - name: Create Release 27 | uses: actions/create-release@v1 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.github_token }} 30 | with: 31 | tag_name: ${{ steps.changelog.outputs.tag }} 32 | release_name: ${{ steps.changelog.outputs.tag }} 33 | body: ${{ steps.changelog.outputs.clean_changelog }} 34 | 35 | - name: Sync Github info from release 36 | uses: jaid/action-sync-node-meta@v1.4.0 37 | with: 38 | direction: overwrite-github 39 | githubToken: ${{ secrets.priv_github_token }} 40 | 41 | - name: Sync develop with master 42 | uses: repo-sync/pull-request@v2 43 | with: 44 | source_branch: "master" 45 | destination_branch: "develop" 46 | pr_title: "Synchronize develop with master after release ${{ steps.changelog.outputs.tag }}" 47 | pr_body: ":crown:" 48 | pr_reviewer: "filipowm" 49 | pr_assignee: "filipowm" 50 | pr_label: "auto-pr" 51 | github_token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /src/components/Seo/Seo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Helmet } from 'react-helmet'; 4 | import config from 'config'; 5 | 6 | const Seo = ({ frontmatter, title, url }) => { 7 | const description = frontmatter.description 8 | ? frontmatter.description 9 | : config.metadata.description; 10 | const image = frontmatter.cover ? frontmatter.cover : config.metadata.siteImage; 11 | 12 | return ( 13 | 19 | {/* General tags */} 20 | {title} 21 | 22 | 23 | {/* OpenGraph tags */} 24 | 25 | 26 | 27 | 28 | 29 | {/* Twitter Card tags */} 30 | 31 | 32 | 33 | {/* */} 37 | 38 | ); 39 | }; 40 | 41 | Seo.propTypes = { 42 | frontmatter: PropTypes.object.isRequired, 43 | title: PropTypes.string.isRequired, 44 | url: PropTypes.string.isRequired, 45 | }; 46 | 47 | export default Seo; 48 | -------------------------------------------------------------------------------- /src/components/Buttons/Social.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import ButtonIcon from './ButtonIcon'; 3 | import { Link } from '../'; 4 | import { 5 | Facebook as FB, 6 | GitHub as GH, 7 | Gitlab as GL, 8 | Instagram as IN, 9 | Linkedin as LI, 10 | Mail as MA, 11 | Rss as RS, 12 | Slack as SL, 13 | Twitch as TC, 14 | Twitter as TW, 15 | Youtube as YT, 16 | } from 'react-feather'; 17 | 18 | const SocialButton = ({ link, ...props }) => { 19 | // const theme = useTheme(); 20 | return ( 21 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | SocialButton.propTypes = { 28 | background: PropTypes.string, 29 | hoverFill: PropTypes.string, 30 | hoverStroke: PropTypes.string, 31 | fill: PropTypes.string, 32 | stroke: PropTypes.string, 33 | }; 34 | 35 | export const Facebook = (props) => ; 36 | export const Github = (props) => ; 37 | export const Gitlab = (props) => ; 38 | export const Instagram = (props) => ; 39 | export const Linkedin = (props) => ; 40 | export const Mail = (props) => ; 41 | export const Rss = (props) => ; 42 | export const Slack = (props) => ; 43 | export const Twitch = (props) => ; 44 | export const Twitter = (props) => ; 45 | export const Youtube = (props) => ; 46 | -------------------------------------------------------------------------------- /src/html.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import config from 'config'; 4 | import { scrollbar } from './styles'; 5 | 6 | export default class HTML extends React.Component { 7 | render() { 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | {config.metadata.ogImage ? ( 15 | 16 | ) : null} 17 | 18 | {config.metadata.ogImage ? ( 19 | 20 | ) : null} 21 | {config.metadata.favicon ? ( 22 | 23 | ) : null} 24 | 25 | {this.props.headComponents} 26 | 27 | 28 | {this.props.preBodyComponents} 29 |
    30 | {this.props.postBodyComponents} 31 | 32 | 33 | ); 34 | } 35 | } 36 | 37 | HTML.propTypes = { 38 | htmlAttributes: PropTypes.object, 39 | headComponents: PropTypes.array, 40 | bodyAttributes: PropTypes.object, 41 | preBodyComponents: PropTypes.array, 42 | body: PropTypes.string, 43 | postBodyComponents: PropTypes.array, 44 | }; 45 | -------------------------------------------------------------------------------- /static/assets/code.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /plugins/gatsby-remark-sectionize-toc/sectionize-toc.js: -------------------------------------------------------------------------------- 1 | const findAfter = require('unist-util-find-after'); 2 | const visit = require('unist-util-visit-parents'); 3 | 4 | const MAX_HEADING_DEPTH = 6; 5 | 6 | module.exports = () => transform; 7 | 8 | const transform = (tree, maxDepth) => { 9 | const maxTocDepth = maxDepth ? maxDepth : MAX_HEADING_DEPTH; 10 | const visitFunction = sectionize(maxTocDepth); 11 | for (let depth = MAX_HEADING_DEPTH; depth > 0; depth--) { 12 | visit(tree, (node) => node.type === 'heading' && node.depth === depth, visitFunction); 13 | } 14 | }; 15 | const sectionize = (maxTocDepth) => { 16 | let minDepth = MAX_HEADING_DEPTH; 17 | return (node, ancestors) => { 18 | const start = node; 19 | const depth = start.depth; 20 | const parent = ancestors[ancestors.length - 1]; 21 | minDepth = depth < minDepth ? depth : minDepth; 22 | const isEnd = (node) => 23 | (node.type === 'heading' && node.depth <= depth) || 24 | // node.depth - (minDepth - 1) was added to fix case, when headers 25 | // do not start from h1 or h2 (etc..) 26 | (node.type === 'section' && 27 | node.depth > depth && 28 | node.depth - (minDepth - 1) <= maxTocDepth) || 29 | node.type === 'export'; 30 | const end = findAfter(parent, start, isEnd); 31 | const startIndex = parent.children.indexOf(start); 32 | const endIndex = parent.children.indexOf(end); 33 | 34 | const between = parent.children.slice(startIndex, endIndex > 0 ? endIndex : undefined); 35 | 36 | const section = { 37 | type: 'section', 38 | depth: depth, 39 | children: between, 40 | data: { 41 | hName: 'section', 42 | }, 43 | }; 44 | parent.children.splice(startIndex, section.children.length, section); 45 | }; 46 | }; 47 | -------------------------------------------------------------------------------- /src/components/ThemeProvider/ThemeProvider.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ThemeProvider as EmotionThemeProvider } from 'emotion-theming'; 3 | import { light, dark } from '../../theme'; 4 | 5 | class ThemeProvider extends React.Component { 6 | state = { 7 | isDarkThemeActive: false, 8 | }; 9 | 10 | constructor({ darkModeConfig }) { 11 | super(); 12 | this.darkModeConfig = darkModeConfig; 13 | } 14 | 15 | componentDidMount() { 16 | this.retrieveActiveTheme(); 17 | } 18 | 19 | retrieveActiveTheme = () => { 20 | if (!this.darkModeConfig.enabled) { 21 | return false; 22 | } 23 | let isDarkThemeActive = JSON.parse(window.localStorage.getItem('isDarkThemeActive')); 24 | if (isDarkThemeActive == null) { 25 | isDarkThemeActive = 26 | window.matchMedia('(prefers-color-scheme: dark)').matches || this.darkModeConfig.default; 27 | } 28 | this.setState({ isDarkThemeActive }); 29 | return isDarkThemeActive; 30 | }; 31 | 32 | toggleActiveTheme = () => { 33 | if (!this.darkModeConfig.enabled) { 34 | console.warn('Dark mode is disabled, but trying to activate it.'); 35 | return false; 36 | } 37 | this.setState((prevState) => ({ isDarkThemeActive: !prevState.isDarkThemeActive })); 38 | 39 | window.localStorage.setItem('isDarkThemeActive', JSON.stringify(!this.state.isDarkThemeActive)); 40 | return !this.state.isDarkThemeActive; 41 | }; 42 | 43 | render() { 44 | const { children } = this.props; 45 | const { isDarkThemeActive } = this.state; 46 | const currentActiveTheme = isDarkThemeActive ? dark : light; 47 | 48 | return ( 49 |
    50 | {children} 51 |
    52 | ); 53 | } 54 | } 55 | 56 | export default ThemeProvider; 57 | -------------------------------------------------------------------------------- /src/components/Search/localsearch/hitComps.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Hit } from '../Hits'; 3 | import emoji from '../../../utils/emoji'; 4 | import styled from '@emotion/styled'; 5 | 6 | const trim_words = (str, numWords) => { 7 | const expString = str.split(/\s+/,numWords); 8 | return expString.join(" "); 9 | } 10 | 11 | const reverse = (str) => str.split("").reverse().join(""); 12 | 13 | const onReversed = (str, func) => { 14 | const reversed = reverse(str); 15 | const transformed = func(reversed); 16 | return reverse(transformed); 17 | } 18 | 19 | const Highlight = styled.span` 20 | background-color: ${(props) => props.theme.search.mark.background}; 21 | color: ${(props) => props.theme.search.mark.font}; 22 | `; 23 | 24 | const highlight = (query, text, maxWords) => { 25 | const regex = new RegExp(query, "i") 26 | const startPos = text.search(regex); 27 | if (startPos < 0) { 28 | return trim_words(text, maxWords); 29 | } 30 | const qLength = query.length; 31 | const boundary = Math.ceil((maxWords - 1) / 2); 32 | let beforeText = text.substring(0, startPos); 33 | beforeText = onReversed(beforeText, (reversed) => trim_words(reversed, boundary)); 34 | beforeText = emoji.emojify(beforeText); 35 | let afterText = text.substring(startPos + qLength); 36 | afterText = trim_words(afterText, boundary); 37 | afterText = emoji.emojify(afterText); 38 | return <> 39 | {beforeText} 40 | {emoji.emojify(query)} 41 | {afterText} 42 | 43 | } 44 | 45 | export const PageHit = ({ hit, q, maxWords }) => { 46 | return ( 47 |
  • 48 | 53 |
  • 54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /content/configuration/setting-up/header.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Header' 3 | order: 2 4 | --- 5 | 6 | Metadata base configuration can be set in `config.yaml` under `header` key. 7 | 8 | | Property | Description | Default value | 9 | |---------------------------|------------------------------------------------------------------|---------------| 10 | | `header.enabled` | Set to `true` to enable (show) header. | `true` | 11 | | `header.logo` | URL (relative or absolute) to logo. Example: `/assets/logo.png` | | 12 | | `header.logoLink` | URL to page showed when clicked on logo. | `/` | 13 | | `header.helpUrl` | URL to help page. When set, help icon will be visible in header. | | 14 | | `header.links` | List of links visible in header. | | 15 | | `header.links[].text` | Text of a header link. | | 16 | | `header.links[].link` | URL associated with header link. | | 17 | | `header.links[].external` | Set to `true` if link should be open in a separate browser tab. | | 18 | 19 | Other configuration, that has impact on header look are: 20 | * [search](/configuration/settingup/search) 21 | * [dark / light mode](/configuration/settingup/features#darkmode) 22 | 23 | **Full example:** 24 | 25 | ```yaml 26 | header: 27 | enabled: true 28 | logo: "/assets/mylogo.png" 29 | logoLink: "/" 30 | helpUrl: "https://somehelp.mycompany.com" 31 | links: 32 | - text: BooGi 33 | link: https://github.com/filipowm/boogi 34 | external: true 35 | - text: Another Link 36 | link: /somethinghere 37 | ``` -------------------------------------------------------------------------------- /src/components/Sidebar/poweredBy.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { onMobile } from '../../styles/responsive'; 4 | 5 | const Trademark = styled(({ className, trademark }) => { 6 | return ( 7 |
    8 | powered by logo 9 |
    10 | ); 11 | })` 12 | display: flex; 13 | img { 14 | svg * { 15 | color: ${(props) => props.theme.navigationSidebar.poweredBy.hover}; 16 | } 17 | width: 25px; 18 | } 19 | `; 20 | const PoweredText = styled(({ className, text }) => ( 21 |
    22 | 23 | Powered By {text} 24 | 25 |
    26 | ))` 27 | padding-left: 20px; 28 | span { 29 | font-size: 12px; 30 | font-family: 'Roboto', sans-serif; 31 | font-weight: 400; 32 | line-height: 1.625; 33 | } 34 | `; 35 | 36 | const PoweredBy = styled(({ className, trademark, name, link }) => ( 37 | 43 | ))` 44 | color: ${(props) => props.theme.navigationSidebar.poweredBy.font}; 45 | margin: 12px; 46 | display: flex; 47 | align-items: center; 48 | margin-left: 0px; 49 | padding: 12px 18px; 50 | border-radius: 4px; 51 | text-decoration: none; 52 | background-color: ${(props) => props.theme.navigationSidebar.poweredBy.background}; 53 | transition: ${(props) => props.theme.transitions.hoverColor}; 54 | &:hover { 55 | border: 1px solid ${(props) => props.theme.navigationSidebar.poweredBy.hover}; 56 | margin-top: 11px; 57 | color: ${(props) => props.theme.navigationSidebar.poweredBy.hover}; 58 | } 59 | ${onMobile} { 60 | display: none; 61 | } 62 | `; 63 | 64 | export default PoweredBy; 65 | -------------------------------------------------------------------------------- /src/components/Header/fullscreen.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { Maximize, Minimize } from 'react-feather'; 4 | import { useTheme } from 'emotion-theming'; 5 | import { ButtonIcon } from '../'; 6 | 7 | export const FullScreenEnter = styled(({ toggle, ...props }) => { 8 | const theme = useTheme(); 9 | return ( 10 | 20 | ); 21 | })``; 22 | 23 | export const FullScreenHeader = styled.div` 24 | display: ${(props) => (props.show ? 'flex' : 'none')}; 25 | position: fixed; 26 | justify-content: flex-end; 27 | align-items: center; 28 | height: 32px; 29 | padding: 6px; 30 | width: 100%; 31 | z-index: 10; 32 | top: 0; 33 | background-color: ${(props) => props.theme.header.background}; 34 | `; 35 | 36 | export const FullScreenClose = styled(({ className, toggle }) => { 37 | const theme = useTheme(); 38 | return ( 39 |
    40 | Close full mode 41 | 49 |
    50 | ); 51 | })` 52 | display: flex; 53 | transition: ${(props) => props.theme.transitions.hover}; 54 | cursor: pointer; 55 | align-items: center; 56 | font-size: 10pt; 57 | margin-right: 10px; 58 | &:hover{ 59 | color: ${(props) => props.theme.colors.hover}; 60 | } 61 | `; 62 | -------------------------------------------------------------------------------- /src/components/Search/Hits.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'gatsby'; 3 | import styled from '@emotion/styled'; 4 | import { css } from '@emotion/core'; 5 | import { paddingLeftRight } from './styles'; 6 | 7 | const topBottomPadding = css` 8 | padding-top: 20px; 9 | padding-bottom: 20px; 10 | `; 11 | 12 | export const HitsWrapper = styled.div` 13 | left: 0; 14 | display: flex; 15 | flex-direction: column; 16 | overflow: auto; 17 | z-index: 2; 18 | -webkit-overflow-scrolling: touch; 19 | width: 100%; 20 | margin-top: 10px; 21 | // @media only screen and (max-width: 991px) { 22 | // width: 400px; 23 | // max-width: 400px; 24 | // } 25 | // @media only screen and (max-width: 767px) { 26 | // width: 100%; 27 | // max-width: 500px; 28 | // } 29 | > * + * { 30 | border-top: 2px solid ${(props) => props.theme.search.border}; 31 | } 32 | li { 33 | ${topBottomPadding}; 34 | ${paddingLeftRight}; 35 | &:hover { 36 | background-color: ${(props) => props.theme.search.hover}; 37 | color: ${(props) => props.theme.search.font.hover}; 38 | } 39 | } 40 | li + li { 41 | border-top: 1px solid ${(props) => props.theme.search.border}; 42 | } 43 | * { 44 | margin-top: 0; 45 | color: ${(props) => props.theme.search.font.base}; 46 | } 47 | ul { 48 | list-style: none; 49 | } 50 | mark { 51 | color: ${(props) => props.theme.search.mark.font}; 52 | background: ${(props) => props.theme.search.mark.background}; 53 | } 54 | `; 55 | 56 | const HitTitle = styled.div` 57 | font-weight: bold; 58 | padding-top: 4px; 59 | padding-bottom: 4px; 60 | font-size: 15px; 61 | `; 62 | 63 | const HitDetails = styled.div` 64 | padding-top: 4px; 65 | padding-bottom: 4px; 66 | font-size: 14px; 67 | `; 68 | 69 | export const Hit = ({ slug, title, details }) => ( 70 | 71 | {title} 72 | {details} 73 | 74 | ); 75 | -------------------------------------------------------------------------------- /src/components/MdxComponents/highlights.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { useTheme } from 'emotion-theming'; 4 | import { AlertCircle, AlertOctagon, AlertTriangle } from 'react-feather'; 5 | import { css } from '@emotion/core'; 6 | 7 | const skipParagraph = css` 8 | .paragraph { 9 | &:first-child { 10 | margin-top: 0; 11 | } 12 | &:last-child { 13 | margin-bottom: 0; 14 | } 15 | } 16 | `; 17 | 18 | const HighlightWrapper = styled(({ className, children }) => ( 19 |
    {children}
    20 | ))` 21 | margin: 16px 0; 22 | padding: 14px; 23 | border: 1px solid ${(props) => props.border}; 24 | background-color: ${(props) => props.background}; 25 | color: ${(props) => props.font}; 26 | align-items: center; 27 | display: flex; 28 | border-radius: 4px; 29 | `; 30 | 31 | const Highlight = ({ children, color, icon, ...props }) => { 32 | const theme = useTheme(); 33 | const highlightColor = theme.highlights[color]; 34 | return ( 35 | 41 |
    42 | {icon.render({ color: highlightColor.border, size: 24 })} 43 |
    44 |
    {children}
    45 |
    46 | ); 47 | }; 48 | 49 | export default { 50 | Warning: (props) => 51 | Highlight({ 52 | color: 'warning', 53 | icon: AlertTriangle, 54 | ...props, 55 | }), 56 | Error: (props) => 57 | Highlight({ 58 | color: 'error', 59 | icon: AlertOctagon, 60 | ...props, 61 | }), 62 | Info: (props) => 63 | Highlight({ 64 | color: 'info', 65 | icon: AlertCircle, 66 | ...props, 67 | }), 68 | Tip: (props) => 69 | Highlight({ 70 | color: 'tip', 71 | icon: AlertCircle, 72 | ...props, 73 | }), 74 | }; 75 | -------------------------------------------------------------------------------- /content/gettingstarted/quickstart.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Quick Start" 3 | order: 3 4 | --- 5 | 6 | ## Prerequisites 7 | 8 | 1. Install NodeJS (newest 14+ recommended, minimal 12.18). 9 | 2. Install Yarn: `npm install -g yarn` 10 | 3. Install Boogi CLI: `npm install -g boogi-cli` 11 | 12 | These commands may require root rights, depending on your operating 13 | system and configuration. 14 | 15 | ## Quick start 16 | 17 | 1. Initialize BooGi project in current directory: 18 | ```bash 19 | boogi init 20 | ``` 21 | Now wizard will guide you through core BooGi 22 | configuration. 23 | 24 | 2. Run your app in development mode with live reload 25 | ```bash 26 | boogi develop 27 | ``` 28 | You can access your app on `localhost:8000`. Any changes 29 | applied will be automatically applied on running 30 | development server. 31 | 32 | 3. Build you app package ready for deployment 33 | ```bash 34 | boogi build 35 | ``` 36 | Built package will be available in `public` directory. 37 | 38 | ## BooGi directory structure 39 | 40 | Below is defined BooGi app directory structure. 41 | **Important** This is applicable only for apps initialized and 42 | using BooGi CLI. 43 | 44 | ```bash 45 | +-- .boogi.yml # BooGi CLI configuration file 46 | +-- package.json # 47 | +-- README.md # Your BooGi app readme 48 | │ 49 | +-- assets/ # Directory with static assets not used inside content (e.g. logo) 50 | │ 51 | +-- config/ # Directory with BooGi app configuration 52 | │ +-- config.yml # BooGi configuration file 53 | │ +-- jargon.yml # Jargon (abbrevations / definitions) configuration file 54 | │ +-- theme/ # Directory with BooGi app theme (look-and-feel) configuration 55 | │ +-- colors.js # Base colors configuration file 56 | │ +-- dark.js # Dark theme configuration file 57 | │ +-- light.js # Light theme configuration file 58 | │ 59 | +-- content/ # Directory with your app content 60 | │ +-- index.md # Root page content file (do not remove!) 61 | │ 62 | +-- snippets/ # Directory with external code snippets, which can be embedded in content 63 | ``` -------------------------------------------------------------------------------- /src/images/gitlab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/pages/gitlab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /content/editing/page_config.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Page config' 3 | order: 1 4 | --- 5 | 6 | ## Frontmatter 7 | 8 | Frontmatter can be used to define page-specific metadata and configuration. 9 | The front matter must be the first thing in the file and must take the form 10 | of valid YAML set between triple-dashed lines. Here is a basic example: 11 | 12 | ```yaml 13 | --- 14 | title: Coding like a Pro! 15 | --- 16 | ``` 17 | 18 | Between these triple-dashed lines, you can set predefined variables 19 | (see below for a reference) which allow you to customize the page. 20 | 21 | ```yaml 22 | title: String # title of the page, visible in navigation sidebar, page content title, browser tab title and used for SEO and search 23 | metaTitle: String # used for SEO if provided, otherwise title is used 24 | description: String # page description used for search and SEO 25 | order: Int # page order in navigation sidebar, the lower the higher it will appear 26 | draft: Boolean # set to true to mark page as draft and not publish it (unless overriden by features.publishDraft property) 27 | editable: Boolean # set to true to show Edit on Repo button, set to false to hide it 28 | showMetadata: Boolean # set to true to show page metadata, set to false to hide it 29 | showToc: Boolean # set to true to show Table of Contents, set to false to hide it 30 | tocDepth: Int # Table of Contents depth (depth of headers used to calculate ToC) 31 | showPreviousNext: Boolean # set to true to show previous/next buttons, set to false to hide it 32 | ``` 33 | 34 | **Examples** 35 | 36 | 37 | 38 | ```yaml 39 | --- 40 | title: 'Navigation' 41 | --- 42 | ``` 43 | 44 | ```yaml 45 | --- 46 | title: 'Navigation' 47 | order: 4 48 | draft: true 49 | editable: true 50 | tocDepth: 1 51 | --- 52 | ``` 53 | 54 | 55 | 56 | ## Page link 57 | 58 | Page link is defined page file name and directories path relative to `content` 59 | directory. Check [navigation](/content/configuration/navigation) details 60 | to better understand how page link is created. 61 | 62 | Further on, when writing content, page link must be used to create valid 63 | links within page content, e.g. `[go to this page](/group/page)`. 64 | -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap'); 2 | 3 | * { 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: border-box; 7 | font-display: swap; 8 | } 9 | 10 | html, 11 | body { 12 | font-family: -apple-system, BlinkMacSystemFont, 'Roboto', 'Roboto Light', 'Segoe UI','Oxygen', 13 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif, 14 | 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; 15 | font-size: 16px; 16 | scroll-behavior: smooth; 17 | } 18 | 19 | body { 20 | font-family: 'Roboto'; 21 | } 22 | 23 | a { 24 | text-decoration: none; 25 | 26 | &:hover { 27 | text-decoration: none; 28 | } 29 | } 30 | 31 | /* Header section starts here */ 32 | pre { 33 | border: 0 !important; 34 | background-color: rgb(245, 247, 249); 35 | } 36 | 37 | /* Image styling */ 38 | 39 | img { 40 | max-width: 100%; 41 | } 42 | 43 | /* end image */ 44 | 45 | .heading1 { 46 | font-size: 26px; 47 | font-weight: 800; 48 | line-height: 1.5; 49 | margin-bottom: 16px; 50 | margin-top: 32px; 51 | } 52 | 53 | .heading2 { 54 | font-size: 24px; 55 | font-weight: 700; 56 | line-height: 1.5; 57 | margin-bottom: 16px; 58 | margin-top: 32px; 59 | } 60 | 61 | .heading3 { 62 | font-size: 20px; 63 | font-weight: 600; 64 | line-height: 1.5; 65 | margin-bottom: 16px; 66 | margin-top: 32px; 67 | } 68 | 69 | .heading4 { 70 | font-size: 18px; 71 | font-weight: 500; 72 | line-height: 1.5; 73 | margin-bottom: 16px; 74 | margin-top: 32px; 75 | } 76 | 77 | .heading5 { 78 | font-size: 16px; 79 | font-weight: 400; 80 | line-height: 1.5; 81 | margin-bottom: 16px; 82 | margin-top: 32px; 83 | } 84 | 85 | .heading6 { 86 | font-size: 14px; 87 | font-weight: 300; 88 | line-height: 1.5; 89 | margin-bottom: 16px; 90 | margin-top: 32px; 91 | } 92 | 93 | .paragraph { 94 | margin: 16px 0; 95 | line-height: 1.625; 96 | } 97 | 98 | .gatsby-resp-image-figcaption { 99 | text-align: center; 100 | font-style: italic; 101 | margin-top: 5px; 102 | font-size: 14px; 103 | } 104 | -------------------------------------------------------------------------------- /src/components/MdxComponents/jargon.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useTheme } from 'emotion-theming'; 3 | import styled from '@emotion/styled'; 4 | 5 | const JargonWrapper = styled.em` 6 | display: inline-block; 7 | .jargon-term { 8 | text-decoration: underline dotted ${(props) => props.theme.colors.primary}; 9 | &::after { 10 | content: '?'; 11 | font-weight: bold; 12 | display: inline-block; 13 | transform: translate(0, -0.5em); 14 | font-size: 75%; 15 | color: ${(props) => props.theme.colors.primary}; 16 | margin-left: 3px; 17 | } 18 | &:hover { 19 | position: relative; 20 | text-decoration: none; 21 | cursor: help; 22 | 23 | .jargon-info { 24 | color: ${(props) => props.theme.jargon.font}; 25 | display: block; 26 | position: absolute; 27 | top: 1.5em; 28 | left: 0; 29 | background: ${(props) => props.theme.jargon.background}; 30 | border: 1px solid ${(props) => props.theme.jargon.border}; 31 | border-left: 4px solid ${(props) => props.theme.colors.primary}; 32 | padding: 1rem; 33 | border-radius: 4px; 34 | font-size: 90%; 35 | min-width: 300px; 36 | max-width: 400px; 37 | z-index: 1; 38 | box-shadow: 0 0 4px 2px ${(props) => props.theme.jargon.shadow}; 39 | span:first-child { 40 | width: 100%; 41 | padding-bottom: 10px; 42 | display: inline-block; 43 | position: relative; 44 | &::after { 45 | content: ''; 46 | position: absolute; 47 | bottom: 0; 48 | left: 0; 49 | right: 0; 50 | height: 0.5em; 51 | border-top: 1px solid ${(props) => props.theme.colors.primary}; 52 | z-index: -1; 53 | } 54 | } 55 | } 56 | } 57 | 58 | .jargon-info { 59 | display: none; 60 | } 61 | } 62 | `; 63 | 64 | const Jargon = ({ children, ...props }) => { 65 | const theme = useTheme(); 66 | return ( 67 | 68 | {children} 69 | 70 | ); 71 | }; 72 | 73 | export default Jargon; 74 | -------------------------------------------------------------------------------- /content/configuration/setting-up.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Setting up" 3 | order: 1 4 | tocDepth: 1 5 | --- 6 | 7 | Primary configuration is set in `config.yml` within `config` directory 8 | in you main project directory. Configuration details are described 9 | in subsequent articles. 10 | 11 | ## Adding images 12 | 13 | 14 | 15 | For adding images (or any other assets) to content, please follow [this](/editing/images) guide instead. 16 | Using assets does not bring performance optimization 17 | 18 | 19 | You can use your own logo, favicon, page icons (used by _PWA_). Add images 20 | to `static/assets` directory. Any assets (like images) can be then accessed 21 | under `/assets/` path. 22 | 23 | Let's assume you add `mylogo.png` image to `static/assets`. Then you can 24 | set a path in `config.yml` logo configuration as 25 | 26 | ```yaml 27 | header: 28 | logo: /assets/mylogo.png 29 | ``` 30 | 31 | ## Using environment variables 32 | 33 | Some configuration may be either sensitive (like Algolia API keys) or is per-environment. 34 | To set such properties, you can make use of environment variables 35 | to pass configuration to BooGi / Gatsby while building your project. 36 | 37 | Each and every configuration property in `config.yml` can also be 38 | passed in environment variable. 39 | 40 | **Important:** List variables are not supported! 41 | 42 | **General rule:** 43 | 44 | in `config.yml`: 45 | ```yaml 46 | property1: 47 | childProperty: MyApp 48 | ``` 49 | 50 | then env variable should be: `PROPERTY1_CHILD_PROPERTY`. 51 | 52 | ## Examples 53 | 54 | **Example 1:** 55 | 56 | 57 | 58 | ```yaml 59 | metadata: 60 | name: MyApp 61 | 62 | features: 63 | search: 64 | startComponent: 'input' 65 | ``` 66 | 67 | ```bash 68 | METADATA_NAME=MyApp Test 69 | FEATURES_SEARCH_START_COMPONENT=icon 70 | ``` 71 | 72 | 73 | 74 | **Example 2:** 75 | 76 | 77 | 78 | ```yaml 79 | features: 80 | search: 81 | enabled: false 82 | ``` 83 | 84 | ```bash 85 | FEATURES_SEARCH_ENABLED=true 86 | FEATURES_SEARCH_ALGOLIA_APP_ID=XXXYYYZZZ9 87 | FEATURES_SEARCH_ALGOLIA_SEARCH_KEY=aabbccddeeffgghhiijjkk 88 | FEATURES_SEARCH_ALGOLIA_ADMIN_KEY=xxxyyyzzz 89 | FEATURES_SEARCH_INDEX_NAME=myapp-test-idx 90 | ``` 91 | 92 | -------------------------------------------------------------------------------- /src/components/MdxComponents/accordion.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { useTheme } from 'emotion-theming'; 4 | import Collapsible from 'react-collapsible'; 5 | import { ChevronUp, ChevronDown } from 'react-feather'; 6 | import { renderToStaticMarkup } from 'react-dom/server'; 7 | import emoji from '../../utils/emoji'; 8 | import { shadowAround } from '../../styles'; 9 | 10 | const AccordionWrapper = styled.div` 11 | margin: 10px 0; 12 | & > div { 13 | ${(props) => shadowAround(props.theme)}; 14 | border-radius: 4px; 15 | 16 | & > span { 17 | &.is-open { 18 | border-bottom: 1px solid ${(props) => props.theme.colors.border}; 19 | &:after { 20 | content: url('data:image/svg+xml; utf8, ${(props) => props.openImg}'); 21 | } 22 | } 23 | &:hover { 24 | border: 1px solid ${(props) => props.theme.colors.primary}; 25 | } 26 | &:after { 27 | content: url('data:image/svg+xml; utf8, ${(props) => props.closedImg}'); 28 | float: right; 29 | } 30 | transition: ${(props) => props.theme.transitions.hover}; 31 | border: 1px solid transparent; 32 | font-weight: 500; 33 | padding: 16px; 34 | cursor: pointer; 35 | display: block; 36 | width: 100%; 37 | } 38 | 39 | & > div > div { 40 | padding: 8px 16px; 41 | } 42 | } 43 | `; 44 | 45 | export default ({ title, titleWhenOpen, expanded, children, ...props }) => { 46 | const theme = useTheme(); 47 | const color = encodeURIComponent(theme.colors.primary); // replace # to not follow uri as usual 48 | const closed = renderToStaticMarkup(); 49 | const open = renderToStaticMarkup(); 50 | const triggerWhenOpen = titleWhenOpen ? titleWhenOpen : title; 51 | return ( 52 | 53 | 59 | {children} 60 | 61 | 62 | ); 63 | }; 64 | -------------------------------------------------------------------------------- /src/components/Header/social.js: -------------------------------------------------------------------------------- 1 | import * as SocialButtons from '../Buttons/Social'; 2 | 3 | const SocialButtonsBuilder = (baseProps) => { 4 | const iconBaseProps = baseProps; 5 | const buttons = []; 6 | const create = (config, name, optConfig) => { 7 | if (config && config.length > 0) { 8 | const btn = SocialButtons[name]; 9 | if (btn) { 10 | const link = optConfig && optConfig.linkFn ? optConfig.linkFn(config) : config; 11 | const title = 12 | optConfig && optConfig.titleFn ? optConfig.titleFn(name) : `Follow on ${name}`; 13 | const additionalProps = 14 | optConfig && optConfig.additionalProps ? optConfig.additionalProps : {}; 15 | buttons.push( 16 | btn({ 17 | link: link, 18 | key: `${name}-social`, 19 | title: title, 20 | ...iconBaseProps, 21 | ...additionalProps, 22 | }) 23 | ); 24 | } 25 | } 26 | }; 27 | const get = () => buttons; 28 | return { 29 | get: get, 30 | create: create, 31 | }; 32 | }; 33 | 34 | export default (iconBaseProps, socialConfig) => { 35 | const buttons = SocialButtonsBuilder(iconBaseProps); 36 | buttons.create(socialConfig.facebook, 'Facebook'); 37 | buttons.create(socialConfig.github, 'Github'); 38 | buttons.create(socialConfig.gitlab, 'Gitlab'); 39 | buttons.create(socialConfig.instagram, 'Instagram'); 40 | buttons.create(socialConfig.linkedin, 'Linkedin', { 41 | additionalProps: { 42 | fill: iconBaseProps.stroke, 43 | hoverFill: iconBaseProps.hoverStroke, 44 | }, 45 | }); 46 | buttons.create(socialConfig.mail, 'Mail', { 47 | linkFn: (address) => `mailto:${address}`, 48 | titleFn: () => `Send email to owner`, 49 | }); 50 | buttons.create(socialConfig.gmail, 'Mail', { 51 | linkFn: (address) => `https://mail.google.com/mail/?view=cm&fs=1&tf=1&to=${address}`, 52 | titleFn: () => `Send email to owner`, 53 | }); 54 | buttons.create(socialConfig.slack, 'Slack'); 55 | buttons.create(socialConfig.twitch, 'Twitch'); 56 | buttons.create(socialConfig.twitter, 'Twitter', { 57 | additionalProps: { 58 | fill: iconBaseProps.stroke, 59 | hoverFill: iconBaseProps.hoverStroke, 60 | }, 61 | }); 62 | buttons.create(socialConfig.youtube, 'Youtube'); 63 | return buttons.get(); 64 | }; 65 | -------------------------------------------------------------------------------- /src/components/Header/logo.js: -------------------------------------------------------------------------------- 1 | // import Link from "../Link"; 2 | import React from 'react'; 3 | import styled from '@emotion/styled'; 4 | import { Link } from '../'; 5 | import { css } from '@emotion/core'; 6 | import { useTheme } from 'emotion-theming'; 7 | import { onMobile, onTablet } from '../../styles/responsive'; 8 | 9 | const logoStyle = (theme) => css` 10 | padding: 0 0; 11 | display: flex; 12 | align-items: center; 13 | ${onMobile} { 14 | min-height: 40px; 15 | } 16 | img { 17 | width: 55px; 18 | margin-right: 16px; 19 | display: inline-block; 20 | ${onTablet} { 21 | width: 50px; 22 | } 23 | ${onMobile} { 24 | margin-right: 8px; 25 | width: 45px; 26 | } 27 | } 28 | 29 | span { 30 | height: auto; 31 | font-size: 26px; 32 | line-height: 1.5; 33 | color: ${theme.header.font.base}; 34 | ${onTablet} { 35 | font-size: 21px; 36 | } 37 | ${onMobile} { 38 | font-size: 19px; 39 | flex: initial; 40 | padding: 0 15px 0 0; 41 | } 42 | &:hover { 43 | text-decoration: none; 44 | opacity: 0.8; 45 | } 46 | } 47 | `; 48 | 49 | const LogoWrapper = styled.div` 50 | margin-left: ${(props) => props.theme.layout.leftMargin}; 51 | ${onMobile} { 52 | margin-left: 10px; 53 | } 54 | `; 55 | 56 | const Logo = styled(({ className, link, img, title }) => { 57 | const theme = useTheme(); 58 | let split = title.split(' '); 59 | split[0] = '' + split[0]; 60 | const last = split.length < 3 ? 0 : split.length - 2; 61 | split[last] = split[last] + ''; 62 | const title2 = split.join(' '); 63 | return ( 64 |
    65 | 66 | 67 | {'logo'} 68 | 69 | 70 | 71 |
    72 | ); 73 | })` 74 | min-width: ${(props) => props.theme.layout.leftWidth}; 75 | display: flex; 76 | align-items: center; 77 | border-right: 1px solid ${(props) => props.theme.header.border}; 78 | ${onMobile} { 79 | border-right: none; 80 | min-width: auto; 81 | padding-right: 0; 82 | } 83 | `; 84 | 85 | export default Logo; 86 | -------------------------------------------------------------------------------- /src/components/Sidebar/contentTreeGroup.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ContentTreeNode from './contentTreeNode'; 3 | import config from 'config'; 4 | import styled from '@emotion/styled'; 5 | import emoji from '../../utils/emoji'; 6 | 7 | const ContentTreeGroup = styled(({ className, treeState, title, icon, location, children }) => { 8 | children.forEach((item) => { 9 | const alreadyExpanded = treeState.collapsed[item.url] === false; 10 | const expanded = 11 | alreadyExpanded || 12 | location.pathname === item.url || 13 | location.pathname === item.url + '/' || 14 | item.children.some((child) => child.url === location.pathname) || 15 | (config.sidebar.expanded && config.sidebar.expanded.includes(item.url)); 16 | 17 | treeState.collapsed[item.url] = !expanded; 18 | }); 19 | const toggle = (url) => { 20 | treeState.setCollapsed({ 21 | ...treeState.collapsed, 22 | [url]: !treeState.collapsed[url], 23 | }); 24 | }; 25 | const emojified = emoji.emojify(title); 26 | return ( 27 |
    28 | {title ? ( 29 | <> 30 | 31 | {icon ? {`group : null}{' '} 32 | {emojified} 33 | 34 | 35 | ) : null} 36 |
      37 | {children.map((child) => ( 38 | 45 | ))} 46 |
    47 |
    48 | 49 | // {...item} 50 | ); 51 | })` 52 | display: block; 53 | padding: 0; 54 | position: relative; 55 | margin-bottom: 24px; 56 | span { 57 | padding: 5px 16px; 58 | font-size: 13px; 59 | font-family: Content-font, Roboto, sans-serif; 60 | font-weight: 700; 61 | line-height: 1.2; 62 | letter-spacing: 1.2px; 63 | text-transform: uppercase; 64 | position: relative; 65 | color: ${(props) => props.theme.navigationSidebar.font.group}; 66 | } 67 | > span { 68 | margin-bottom: 5px; 69 | display: flex; 70 | align-items: center; 71 | img { 72 | width: 18px; 73 | margin-right: 7px; 74 | } 75 | } 76 | `; 77 | 78 | export default ContentTreeGroup; 79 | -------------------------------------------------------------------------------- /src/components/Header/navigation.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { onMobile } from '../../styles/responsive'; 4 | 5 | const Navigation = styled(({ className, links }) => { 6 | return ( 7 | 28 | ); 29 | })` 30 | display: flex; 31 | align-items: center; 32 | -webkit-overflow-scrolling: touch; 33 | float: left; 34 | ${onMobile} { 35 | width: 100%; 36 | justify-content: space-evenly; 37 | flex-wrap: wrap; 38 | margin-top: 10px; 39 | li { 40 | height: 37px; 41 | 42 | a { 43 | font-size: 14px; 44 | padding: 10px 15px; 45 | } 46 | } 47 | } 48 | li { 49 | list-style-type: none; 50 | display: flex; 51 | & > a:before { 52 | content: ''; 53 | position: absolute; 54 | width: 100%; 55 | height: 3px; 56 | bottom: 0; 57 | left: 0; 58 | background: ${(props) => props.theme.header.font.hover}; 59 | visibility: hidden; 60 | border-radius: 4px; 61 | transform: scaleX(0); 62 | transition: 0.25s linear; 63 | } 64 | & > a:focus:before, 65 | & > a:hover:before { 66 | visibility: visible; 67 | transform: scaleX(1); 68 | } 69 | a { 70 | font-family: 'Roboto'; 71 | position: relative; 72 | color: ${(props) => props.theme.header.font.base}; 73 | font-size: 16px; 74 | font-weight: 500; 75 | line-height: 1em; 76 | opacity: 1; 77 | padding: 10px 15px; 78 | &:hover { 79 | color: ${(props) => props.theme.header.font.hover}; 80 | } 81 | } 82 | } 83 | `; 84 | 85 | export default Navigation; 86 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - develop 7 | tags: 8 | - '*' 9 | pull_request: 10 | branches: 11 | - '*' 12 | 13 | jobs: 14 | cleanup-runs: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: rokroskar/workflow-run-cleanup-action@master 18 | env: 19 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 20 | 21 | lint: 22 | name: Lint code 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: eslint 26 | uses: reviewdog/action-eslint@v1 27 | with: 28 | reporter: github-pr-review # Change reporter. 29 | eslint_flags: 'src/' 30 | - name: Dockerfile 31 | uses: reviewdog/action-hadolint@v1 32 | with: 33 | github_token: ${{ secrets.github_token }} 34 | reporter: github-pr-review # Default is github-pr-check 35 | level: warning 36 | 37 | netlify: 38 | name: Build and Deploy preview on Netlify 39 | runs-on: ubuntu-latest 40 | if: github.ref != 'refs/heads/master' && github.ref != 'refs/heads/develop' 41 | steps: 42 | - uses: actions/checkout@master 43 | - name: Waiting for 300sec from the Netlify preview 44 | uses: jakepartusch/wait-for-netlify-action@v1 45 | id: waitFor300 46 | with: 47 | site_name: "boogi" 48 | max_timeout: 300 49 | - name: Netlify deploy logs 50 | uses: bdougie/loglify@master 51 | 52 | docker: 53 | name: Build Docker images 54 | runs-on: ubuntu-latest 55 | 56 | strategy: 57 | fail-fast: true 58 | matrix: 59 | dockerfile: ['Dockerfile.builder', 'Dockerfile.runtime', 'Dockerfile.develop'] 60 | 61 | steps: 62 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 63 | - uses: actions/checkout@v2 64 | 65 | - name: Build Docker image 66 | uses: docker/build-push-action@v1.1.0 67 | with: 68 | username: filipowm 69 | password: ${{ secrets.DOCKER_REGISTRY_PASS }} 70 | repository: filipowm/boogi 71 | tags: latest 72 | tag_with_ref: true 73 | tag_with_sha: false 74 | # Path to the Dockerfile (Default is '{path}/Dockerfile') 75 | dockerfile: docker/${{ matrix.dockerfile }} 76 | always_pull: false 77 | cache_froms: node:buster-slim 78 | add_git_labels: true 79 | push: false # optional, default is true 80 | -------------------------------------------------------------------------------- /src/utils/config-pwa.js: -------------------------------------------------------------------------------- 1 | const hasValue = (value) => value && value.length > 0; 2 | 3 | const shortNameFromMetadata = (metadata) => 4 | calculateValue(metadata.short_name, metadata.name.replace(/\w+/, '')); 5 | 6 | const calculateValue = (value, fallbackValue) => { 7 | return hasValue(value) ? value : hasValue(fallbackValue) ? fallbackValue : null; 8 | }; 9 | 10 | const calculateIconLocalPath = (icon) => { 11 | if (icon && icon.startsWith('/assets/')) { 12 | return `static${icon}`; 13 | } else if (icon && icon.startsWith('assets/')) { 14 | return `static/${icon}`; 15 | } 16 | return icon; 17 | }; 18 | 19 | const formatsMap = { 20 | gif: 'image/gif', 21 | jpg: 'image/jpeg', 22 | jpeg: 'image/jpeg', 23 | pjp: 'image/jpeg', 24 | pjpeg: 'image/jpeg', 25 | png: 'image/png', 26 | tif: 'image/tiff', 27 | tiff: 'image/tiff', 28 | svg: 'image/svg+xml', 29 | webp: 'image/webp', 30 | }; 31 | 32 | const setIconsType = (icons) => { 33 | icons.forEach((icon) => { 34 | if (!icon.type) { 35 | const splitted = icon.split('.'); 36 | const extension = splitted.length > 0 ? splitted[splitted.length - 1].toLowerCase() : null; 37 | const type = formatsMap[extension]; 38 | icon.type = type; 39 | } 40 | }); 41 | }; 42 | 43 | const postProcessConfig = (config) => { 44 | const manifest = { ...config.pwa.manifest }; 45 | manifest.name = calculateValue(manifest.name, config.metadata.name); 46 | manifest.short_name = calculateValue(manifest.short_name, shortNameFromMetadata(config.metadata)); 47 | manifest.description = calculateValue(manifest.description, config.metadata.description); 48 | manifest.start_url = calculateValue(manifest.start_url, config.metadata.pathPrefix); 49 | manifest.background_color = calculateValue(manifest.background_color, config.metadata.themeColor); 50 | manifest.theme_color = calculateValue(manifest.theme_color, config.metadata.themeColor); 51 | manifest.cache_busting_mode = 'none'; // enforce, because required to work with gatsby-plugin-offline 52 | const icon = calculateValue(manifest.icon, config.metadata.siteImage); 53 | manifest.icon = calculateIconLocalPath(icon); 54 | manifest.include_favicon = !hasValue(config.metadata.favicon); 55 | manifest.lang = calculateValue(config.metadata.language, null); 56 | if (manifest.icons) { 57 | setIconsType(manifest.icons); 58 | } 59 | config.pwa = { 60 | manifest: manifest, 61 | enabled: config.pwa.enabled, 62 | }; 63 | }; 64 | 65 | module.exports = postProcessConfig; 66 | -------------------------------------------------------------------------------- /src/components/MdxComponents/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/display-name */ 2 | import React from 'react'; 3 | import Accordion from './accordion'; 4 | import AnchorTag from './anchor'; 5 | import Badge from './badge'; 6 | import Card from './card'; 7 | import CodeBlock from './codeBlock'; 8 | import DownloadCard from './fileDownloadCard'; 9 | import Highlights from './highlights'; 10 | import Icon from './icon'; 11 | import ImageCard from './imageCard'; 12 | import Jargon from './jargon'; 13 | import Layout from './layout'; 14 | import LinkCard from './linkCard'; 15 | import { blockquote, pre, table, list } from '../../styles'; 16 | import { useTheme } from 'emotion-theming'; 17 | import emoji from '../../utils/emoji'; 18 | 19 | const idFromHeader = (props) => { 20 | let name = props.children; 21 | if (Array.isArray(name)) { 22 | name = props.children[0]; 23 | } 24 | return emoji.clean(name).replace(/\s+/g, '').toLowerCase(); 25 | }; 26 | const Header = (level, props) => { 27 | let name = idFromHeader(props); 28 | return React.createElement('h' + level, { 29 | className: 'heading' + level, 30 | id: 'h-' + name, 31 | ...props, 32 | }); 33 | }; 34 | 35 | const Table = ({...props}) => ( 36 |
    37 |
    38 | 39 | 40 | 41 | ); 42 | 43 | const Section = (props) => { 44 | let header = ''; 45 | if (Array.isArray(props.children)) { 46 | header = props.children[0].props; 47 | } else { 48 | header = props.children.props; 49 | } 50 | const name = idFromHeader(header); 51 | return
    ; 52 | }; 53 | 54 | const emphasis = (props) => { 55 | const useJargon = !(typeof props.children === 'string'); 56 | if (useJargon) { 57 | return ; 58 | } 59 | return ; 60 | }; 61 | 62 | export default { 63 | h1: (props) => Header(1, props), 64 | h2: (props) => Header(2, props), 65 | h3: (props) => Header(3, props), 66 | h4: (props) => Header(4, props), 67 | h5: (props) => Header(5, props), 68 | h6: (props) => Header(6, props), 69 | section: (props) => Section(props), 70 | blockquote: (props) =>
    , 71 | p: (props) =>

    , 72 | pre: (props) =>

    ,
    73 |   table: (props) => 
    , 74 | em: emphasis, 75 | img: (props) => ( 76 | 77 | 78 | 79 | ), 80 | code: CodeBlock, 81 | ul: (props) =>
      , 82 | ol: (props) =>
        , 83 | a: AnchorTag, 84 | Badge, 85 | Layout, 86 | Icon, 87 | Accordion, 88 | Card, 89 | LinkCard, 90 | ImageCard, 91 | DownloadCard, 92 | ...Highlights, 93 | }; 94 | -------------------------------------------------------------------------------- /content/editing/rich_content/embed.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Embeds" 3 | order: 5 4 | --- 5 | 6 | You can just copy-paste the link to the gif/pen/pin/player/post/sandbox/tweet/video 7 | you want to embed right from within browser onto a separate line (surrounded by empty lines) 8 | and replace it with the proper embed-code. 9 | 10 | ## Supported services 11 | 12 | ### CodePen 13 | 14 | **Usage:** 15 | 16 | ``` 17 | https://codepen.io/team/codepen/pen/PNaGbb 18 | ``` 19 | 20 | **Result:** 21 | 22 | https://codepen.io/team/codepen/pen/PNaGbb 23 | 24 | ### CodeSandbox 25 | 26 | **Usage:** 27 | 28 | ``` 29 | https://codesandbox.io/s/ynn88nx9x?view=split 30 | ``` 31 | 32 | **Result:** 33 | 34 | https://codesandbox.io/s/ynn88nx9x?view=split 35 | 36 | ### Giphy 37 | 38 | **Usage:** 39 | 40 | ``` 41 | https://giphy.com/gifs/howtogiphygifs-how-to-XatG8bioEwwVO 42 | ``` 43 | 44 | **Result:** 45 | 46 | https://giphy.com/gifs/howtogiphygifs-how-to-XatG8bioEwwVO 47 | 48 | ### Instagram 49 | 50 | **Usage:** 51 | 52 | ``` 53 | https://instagram.com/p/B60jPE6J8U- 54 | ``` 55 | 56 | **Result:** 57 | 58 | https://instagram.com/p/B60jPE6J8U- 59 | 60 | ### Lichess 61 | 62 | **Usage:** 63 | 64 | ``` 65 | https://lichess.org/MPJcy1JW 66 | ``` 67 | 68 | **Result:** 69 | 70 | https://lichess.org/MPJcy1JW 71 | 72 | ### Pinterest 73 | 74 | **Usage:** 75 | 76 | ``` 77 | https://pinterest.com/pin/99360735500167749 78 | ``` 79 | 80 | **Result:** 81 | 82 | https://pinterest.com/pin/99360735500167749 83 | 84 | ### Slides 85 | 86 | **Usage:** 87 | 88 | ``` 89 | https://slides.com/kentcdodds/oss-we-want 90 | ``` 91 | 92 | **Result:** 93 | 94 | https://slides.com/kentcdodds/oss-we-want 95 | 96 | ### SoundCloud 97 | 98 | **Usage:** 99 | 100 | ``` 101 | https://soundcloud.com/clemenswenners/africa 102 | ``` 103 | 104 | **Result:** 105 | 106 | https://soundcloud.com/clemenswenners/africa 107 | 108 | ### Spotify 109 | 110 | **Usage:** 111 | 112 | ``` 113 | https://open.spotify.com/track/0It2bnTdLl2vyymzOkBI3L 114 | ``` 115 | 116 | **Result:** 117 | 118 | https://open.spotify.com/track/0It2bnTdLl2vyymzOkBI3L 119 | 120 | ### Streamable 121 | 122 | **Usage:** 123 | 124 | ``` 125 | https://streamable.com/moo 126 | ``` 127 | 128 | **Result:** 129 | 130 | https://streamable.com/moo 131 | 132 | ### Twitch 133 | 134 | **Usage:** 135 | 136 | ``` 137 | https://twitch.tv/videos/546761743 138 | ``` 139 | 140 | **Result:** 141 | 142 | https://twitch.tv/videos/546761743 143 | 144 | ### Twitter 145 | 146 | **Usage:** 147 | 148 | ``` 149 | https://twitter.com/i/moments/994601867987619840 150 | ``` 151 | 152 | **Result:** 153 | 154 | https://twitter.com/i/moments/994601867987619840 155 | 156 | ### Youtube 157 | 158 | **Usage:** 159 | 160 | ``` 161 | https://youtu.be/dQw4w9WgXcQ 162 | ``` 163 | 164 | **Result:** 165 | 166 | https://youtu.be/dQw4w9WgXcQ 167 | -------------------------------------------------------------------------------- /src/components/Search/Sidebar.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/display-name */ 2 | import styled from '@emotion/styled'; 3 | import React, { useRef } from 'react'; 4 | import config from 'config'; 5 | import VisibilitySensor from 'react-visibility-sensor'; 6 | import { X } from 'react-feather'; 7 | import loadable from '@loadable/component'; 8 | 9 | import { onMobile } from '../../styles/responsive'; 10 | import { visibleMobile, shadowAround } from '../../styles'; 11 | 12 | const Algolia = loadable(() => import('./algolia/')) 13 | const LocalSearch = loadable(() => import('./localsearch/')) 14 | 15 | const SearchWrapper = styled.div` 16 | display: flex; 17 | flex-direction: column; 18 | padding: 0; 19 | height: 100%; 20 | overflow: auto; 21 | -webkit-overflow-scrolling: touch; 22 | `; 23 | 24 | const SearchSidebar = styled.div` 25 | display: block; //${(props) => (props.show ? 'block' : 'none')}; 26 | z-index: 20; 27 | height: 100vh; 28 | position: fixed; 29 | right: 0; 30 | left: auto; 31 | top: 0; 32 | width: 480px; 33 | box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.3); 34 | background: ${(props) => props.theme.colors.background}; 35 | ${onMobile} { 36 | width: 100%; 37 | } 38 | `; 39 | const CloseSearch = styled.div` 40 | padding: 14px; 41 | margin-bottom: 14px; 42 | width: 100%; 43 | display: flex; 44 | align-items: center; 45 | justify-content: center; 46 | cursor: pointer; 47 | box-shadow: 0 3px 8px 0 ${(props) => props.theme.colors.shadow}; 48 | border-bottom: 1px solid ${(props) => props.theme.colors.border}; 49 | svg { 50 | width: 1.2em; 51 | } 52 | &:hover { 53 | color: ${(props) => props.theme.colors.hover}; 54 | svg { 55 | stroke: ${(props) => props.theme.colors.hover}; 56 | } 57 | } 58 | `; 59 | 60 | const SearchEngine = React.forwardRef((props, ref) => { 61 | const engine = config.features.search.engine.toLowerCase(); 62 | switch(engine) { 63 | case 'algolia': 64 | return 65 | case 'localsearch': 66 | return 67 | } 68 | console.warn(`Unsupported search engine: ${engine}`); 69 | return null; 70 | }); 71 | 72 | const Search = React.forwardRef(({ onVisibleChange, closeSelf, ...props }, ref) => { 73 | const inputRef = useRef(null); 74 | const onVisibilityChange = (isVisible) => { 75 | if (isVisible && inputRef.current) { 76 | inputRef.current.focus(); 77 | } 78 | if (onVisibleChange) { 79 | onVisibleChange(isVisible); 80 | } 81 | }; 82 | return ( 83 | 84 | 85 | 86 | 87 | Close 88 | 89 | 90 | 91 | 92 | 93 | 94 | ); 95 | }); 96 | 97 | export default Search; 98 | -------------------------------------------------------------------------------- /content/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'About BooGi' 3 | showMetadata: false 4 | editable: false 5 | showToc : false 6 | --- 7 | 8 | :wave: **Hello!** 9 | 10 | **BooGi** is an awesome [GatsbyJS](http://gatsbyjs.org/) documentation :book: 11 | starter. It generates modern documentation pages, which can serve as product 12 | docs, tutorials, _API_ docs and, especially, knowledge-bases. 13 | 14 | Goal is to give teams powerful tool which they can use to efficiently and 15 | collaboratively share their knowledge. They can easily host it on any 16 | infrastructure of choice or SaaS hosting like Netlify, Vercel or 17 | GitHub / GitLab Pages. We want to provide a product, which can be customized 18 | to (nearly) any needs, either using basic or advanced configuration options. 19 | 20 | BooGi is inspired by popular [Gitbook](https://gitbook.com) look and feel. 21 | It offers custom styling and components that enable building beautiful documentation 22 | for projects and products quickly. It follows docs-as-code principles, where 23 | you treat your documentation in the same way as your code. 24 | 25 | ## What BooGi offers? :fire: 26 | 27 | BooGi is open source and free -- forever! 28 | 29 | 30 | 31 | - writing docs using Markdown / [MDX](https://github.com/mdx-js/mdx) 32 | - customizing your page to match your branding and needs 33 | - GitBook-like style theme, inspired by https://docs.gitbook.com/ 34 | - light / dark mode themes 35 | - rich-content and rich-text features like text formatting, graphs and diagrams, 36 | quotes, columnar layout, cards, emojis, highlights, live code editor, syntax highlighting, 37 | external code snippets and many many more! 38 | - draft pages 39 | - Progressive Web App which can work offline 40 | 41 | - search integration with [Algolia](https://www.algolia.com/) 42 | - local search (search in a browser without need to integrate with Algolia) 43 | - responsive, optimized for mobile devices 44 | - integration with Google Analytics 45 | - Search Engine Optimization (_SEO_) friendliness 46 | - full screen mode 47 | - RSS feed 48 | - easy way to edit content on Gitlab, Github or Bitbucket 49 | - custom CLI helping to initialize and develop BooGi apps 50 | - easy deployment on platform of your choice 51 | 52 | 53 | 54 | ## What is planned for near future? :sparkles: 55 | 56 | Features listed here will arrive in Q2/Q3 2020! 57 | 58 | - embed files to download 59 | - sharing on social media 60 | - syntax highlighting improvements (more languages, copy code snippets, show language used) 61 | - new components: tabs, math formulas 62 | - Kubernetes Helm Chart 63 | 64 | --- 65 | 66 | What we will be working on next? 67 | 68 | - documentation versions 69 | - API documentation with OpenAPI support 70 | - authentication (OAuth2, LDAP) when using Docker / Kubernetes deployment 71 | - improved and even faster site navigation 72 | - support for multiple languages 73 | - page rating (likes :thumbsup:, mood faces :smile: or stars :star:) 74 | - built-in page tutorial (how to use) 75 | 76 | **Ready to learn more?** 77 | 78 | https://giphy.com/gifs/paulmccartney-dance-l3q2yHZbc8NDfGRBS 79 | -------------------------------------------------------------------------------- /src/utils/search.js: -------------------------------------------------------------------------------- 1 | const query = (excerptSize) => `{ 2 | pages: allMdx(filter: {fields: {draft: {ne: true}}}) { 3 | edges { 4 | node { 5 | objectID: id 6 | fields { 7 | slug 8 | } 9 | frontmatter { 10 | title 11 | description 12 | } 13 | excerpt(pruneLength: ${excerptSize}) 14 | } 15 | } 16 | } 17 | }`; 18 | 19 | const flatten = (arr) => 20 | arr.map(({ node: { frontmatter, fields, ...rest } }) => ({ 21 | ...frontmatter, 22 | ...fields, 23 | ...rest, 24 | })); 25 | 26 | const transformer = ({ data }) => flatten(data.pages.edges); 27 | 28 | const algolia = (indexName, excerptSize) => { 29 | const settings = { attributesToSnippet: [`excerpt:20`] }; 30 | return [ 31 | { 32 | query: query(excerptSize), 33 | transformer: transformer, 34 | indexName: indexName, 35 | settings, 36 | }, 37 | ]; 38 | }; 39 | 40 | const localsearch = (excerptSize) => ({ 41 | query: query(excerptSize), 42 | normalizer: transformer, 43 | name: 'Boogi', 44 | ref: 'objectID', 45 | index: ['title', 'description', 'excerpt'], 46 | store: ['slug', 'title', 'excerpt'], 47 | }); 48 | 49 | const disableLocalSearchPlugin = { 50 | resolve: require.resolve(`../../plugins/gatsby-plugin-disable-localsearch`), 51 | options: { 52 | name: 'Boogi', 53 | }, 54 | }; 55 | 56 | const buildAlgoliaPluginConfig = (searchConfig) => { 57 | if (searchConfig.algoliaAppId && searchConfig.algoliaAdminKey) { 58 | return [ 59 | { 60 | resolve: `gatsby-plugin-algolia`, 61 | options: { 62 | appId: searchConfig.algoliaAppId, // algolia application id 63 | apiKey: searchConfig.algoliaAdminKey, // algolia admin key to index 64 | queries: algolia(searchConfig.indexName, searchConfig.excerptSize), 65 | chunkSize: 10000, // default: 1000 66 | }, 67 | }, 68 | disableLocalSearchPlugin, 69 | ]; 70 | } 71 | console.warn('Algolia App ID or Admin Key are not set!'); 72 | return [disableLocalSearchPlugin]; 73 | }; 74 | 75 | const buildLocalsearchPluginConfig = (searchConfig) => { 76 | const conf = localsearch(searchConfig.excerptSize); 77 | return [ 78 | { 79 | resolve: 'gatsby-plugin-local-search', 80 | options: { 81 | engine: 'flexsearch', 82 | engineOptions: searchConfig.localSearchEngine, 83 | ...conf, 84 | }, 85 | }, 86 | ]; 87 | }; 88 | 89 | module.exports.getSearchPlugins = (searchConfig) => { 90 | if (!searchConfig || searchConfig.enabled !== true) { 91 | return [disableLocalSearchPlugin]; 92 | } 93 | switch (searchConfig.engine.toLowerCase()) { 94 | case 'localsearch': 95 | return buildLocalsearchPluginConfig(searchConfig); 96 | case 'algolia': 97 | return buildAlgoliaPluginConfig(searchConfig); 98 | default: 99 | console.warn(`Unsupported search engine: ${searchConfig.engine}`); 100 | } 101 | return [disableLocalSearchPlugin]; 102 | }; 103 | -------------------------------------------------------------------------------- /content/configuration/navigation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Navigation' 3 | order: 2 4 | --- 5 | 6 | Understanding navigation is key to setting up you content properly, 7 | especially sidebar and content links. 8 | 9 | First, lets understand how page links are built from content structure. 10 | 11 | # Page links 12 | 13 | * root `content` directory represents a root of your app 14 | * `index.md` (or `index.mdx`) **must** exist in `content` directory 15 | and it will be entrypoint (`/`) of your app. 16 | * every link is derived from file name, e.g. file `myPage` in `content` directory 17 | creates link `/myPage`, so just extension is stripped 18 | * link to the page is also derived from the relative path from `content`, thus 19 | each directory in `content` is a part of page path, e.g. file `myPage` in 20 | `content/mygroup` directory will have link `/mygroup/myPage` 21 | * directories can be organized in groups (making a sidebar group) or 22 | subgroups (groups which are expandable and have nested pages). 23 | * to create a subgroup with nested pages, you need to 24 | * create directory with nested pages, e.g. `nested_group` 25 | * in same directory as just created dir, create file `nested_group.md` 26 | which will be a "parent" of the nested group 27 | * to create group, you need to: 28 | * 29 | 30 | Let's follow this example of project file and directory structure: 31 | 32 | ``` 33 | project 34 | │ 35 | └───content 36 | │── index.md 37 | │── page1.md 38 | |── page2.md 39 | │ 40 | └───group1 41 | │ │── my_page1.md 42 | │ │── myPage2.md 43 | │ └───myPage2 44 | │ │── my_subpage.md 45 | │ 46 | └───page2 47 | │── my_page1.md 48 | │── myPage2.md 49 | └───myPage2 50 | ``` 51 | 52 | Structure defined as above, will generate following page paths (links). 53 | 54 | ``` 55 | index.md => / 56 | page1.md => /page1 57 | page2.md => /page2 58 | page2/subpage.md => /page2/subpage 59 | group1/my_page1.md => /group1/my_page1 60 | group1/myPage2.md => /group1/myPage2 61 | group1/myPage2/my_subpage.md => /group1/myPage2/my_subpage 62 | ``` 63 | 64 | # Sidebar navigation 65 | 66 | When you now understand page links, let's take a deeper look into sidebar 67 | navigation. This will help you structure you content properly, and 68 | build user-friendly page navigation. 69 | 70 | As an example, let's check following simple example: 71 | 72 | 73 | 74 |
        75 | 76 | **Sidebar layout** 77 | 78 | ![](../images/simple_sidebar.png) 79 |
        80 | 81 |
        82 | 83 | **Directory layout** 84 | ``` 85 | content 86 | │── index.md 87 | │ 88 | └───gettingstarted 89 | │ │ 90 | │ │── quickstart.md 91 | │ 92 | │ 93 | └───configuration 94 | │ 95 | │── introduction.md 96 | │── settingup.md 97 | └── settingup 98 | │── features.md 99 | │── search.md 100 | ``` 101 |
        102 | 103 |
        104 | 105 | **Configuration** 106 | ```yaml 107 | sidebar: 108 | ignoreIndex: false 109 | enabled: true 110 | groups: 111 | - order: 1 112 | path: "/gettingstarted" 113 | title: ":rocket: Getting Started" 114 | - order: 2 115 | path: "/configuration" 116 | title: ":wrench: Configuration" 117 | ``` 118 |
        119 | 120 |
        -------------------------------------------------------------------------------- /src/components/Search/algolia/index.js: -------------------------------------------------------------------------------- 1 | import React, { createRef } from 'react'; 2 | import { 3 | InstantSearch, 4 | Index, 5 | Hits, 6 | Configure, 7 | connectStateResults, 8 | } from 'react-instantsearch-dom'; 9 | import algoliasearch from 'algoliasearch/lite'; 10 | import { HitsWrapper } from '../Hits'; 11 | import config from 'config'; 12 | import Input from './input'; 13 | import { PageHit } from './hitComps'; 14 | import styled from '@emotion/styled'; 15 | import SearchStatus from '../Status'; 16 | import Pagination from './pagination'; 17 | import Stats from './stats'; 18 | 19 | const Root = styled.div` 20 | position: relative; 21 | display: grid; 22 | grid-gap: 1em; 23 | @media only screen and (max-width: 767px) { 24 | width: 100%; 25 | } 26 | `; 27 | 28 | // const Results = connectStateResults( 29 | // ({ searching, searchState: state, searchResults: res }) => 30 | // (searching && `Searching...`) || (res && res.nbHits === 0 && `No results for '${state.query}'`) 31 | // ); 32 | 33 | const Results = connectStateResults(({ searching, searchState: state, searchResults: res }) => ( 34 | 35 | )); 36 | 37 | class Algolia extends React.Component { 38 | state = { 39 | query: '', 40 | focus: false, 41 | }; 42 | 43 | constructor(props) { 44 | super(props); 45 | this.searchClient = algoliasearch( 46 | config.features.search.algoliaAppId, 47 | config.features.search.algoliaSearchKey 48 | ); 49 | this.ref = createRef(); 50 | this.inputRef = props.inputRef; 51 | this.index = props.index; 52 | } 53 | 54 | render() { 55 | const ref = this.ref; 56 | const focus = this.focus; 57 | const showResults = this.state.query.length > 1 && this.state.focus; 58 | return ( 59 | this.setState({ query: query })} 63 | root={{ Root, props: { ref } }} 64 | > 65 | this.setState({ focus: true })} 68 | {...{ focus }} 69 | /> 70 | 71 |
        72 | {showResults && config.features.search.showStats ? ( 73 |
        74 | 75 |
        76 | ) : null} 77 | 78 | 79 | 80 | {showResults ? ( 81 | <> 82 | 83 | 84 | ) : ( 85 | '' 86 | )} 87 | 88 | 89 |
        90 | {showResults && config.features.search.pagination.enabled ? ( 91 | 96 | ) : null} 97 | 102 |
        103 | ); 104 | } 105 | } 106 | 107 | export default Algolia; 108 | -------------------------------------------------------------------------------- /content/gettingstarted/cli.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "BooGi CLI" 3 | order: 4 4 | --- 5 | 6 | 7 | 8 | **BooGi CLI is a recommended way of working with BooGi apps**. 9 | However, if you need full control over your app, what comes with 10 | a cost of significantly increased complexity, you can 11 | still use Gatsby CLI. 12 | 13 | 14 | 15 | CLI for BooGi used to speed up and simplify development 16 | of BooGi-based apps. 17 | 18 | ## :label: Requirements 19 | 20 | - NodeJS in version _12.13_ or higher 21 | - Yarn (`npm install -g yarn`) 22 | - BooGi CLI `npm install -g boogi-cli` 23 | 24 | ## :book: Guide 25 | 26 | 27 | ### App structure 28 | 29 | BooGi CLI is creating following directory structure 30 | 31 | ```bash 32 | +-- .boogi.yml # BooGi CLI configuration file 33 | +-- package.json # 34 | +-- README.md # Your BooGi app readme 35 | │ 36 | +-- assets/ # Directory with static assets not used inside content (e.g. logo) 37 | │ 38 | +-- config/ # Directory with BooGi app configuration 39 | │ +-- config.yml # BooGi configuration file 40 | │ +-- jargon.yml # Jargon (abbrevations / definitions) configuration file 41 | │ +-- theme/ # Directory with BooGi app theme (look-and-feel) configuration 42 | │ +-- colors.js # Base colors configuration file 43 | │ +-- dark.js # Dark theme configuration file 44 | │ +-- light.js # Light theme configuration file 45 | │ 46 | +-- content/ # Directory with your app content 47 | │ +-- index.md # Root page content file (do not remove!) 48 | │ 49 | +-- snippets/ # Directory with external code snippets, which can be embedded in content 50 | ``` 51 | 52 | ### boogi init 53 | 54 | Initialize BooGi app in a given path. This gives a way to easily and quickly 55 | start a BooGi project. 56 | 57 | ``` 58 | boogi init [path] [-f|--full] [--skip|--skip-config] [-d|--debug] 59 | ``` 60 | 61 | `path` - path where BooGi project will be initialized. Defaults to current directory. 62 | 63 | `-f`, `--full` - use full (advanced) configuration wizard. Guides you through most of available configuration options. 64 | 65 | `--skip`, `--skip-config` - skip configuration wizard. Default values will be applied. 66 | 67 | `-d`, `--debug` - enable debugging mode. 68 | 69 | ### boogi develop 70 | 71 | Start BooGi development server on specified port (default 8000). 72 | The development server supports live (hot) reload on any changes. 73 | 74 | ``` 75 | boogi develop [-p|--port] [-d|--debug] 76 | ``` 77 | `-p`, `--port` - port on which development server will run. Defaults to `8000`. 78 | 79 | `-d`, `--debug` - enable debugging mode. 80 | 81 | **Note** Changes done to `config/jargon.yml` will not be reloaded. 82 | To apply changes to jargon you must restart server. 83 | 84 | ### boogi build 85 | 86 | Build BooGi project. Deployment-ready package will be created 87 | in `public` directory. 88 | 89 | ``` 90 | boogi build [-a|--archive] [-d|--debug] 91 | ``` 92 | 93 | `-a`, `--archive` - archive (zip) result directory. `public.zip` file will be created 94 | with your built app. 95 | 96 | `-d`, `--debug` - enable debugging mode. 97 | 98 | ### boogi clean 99 | 100 | Wipe the local BooGi environment including built assets and cache. 101 | Useful in case of issues while running `build` or `develop` commands. 102 | 103 | ``` 104 | boogi clean 105 | ``` 106 | 107 | ## :construction_worker: Roadmap 108 | 109 | - add feature to manage navigation sidebar (create, edit, remove groups etc..) 110 | - add feature to manage pages (create, edit, remove etc..) 111 | - add feature to manage theme -------------------------------------------------------------------------------- /config/default.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | metadata: { 3 | name: 'BooGi', 4 | short_name: 'BooGi', 5 | description: '', 6 | language: 'en', 7 | url: 'http://localhost', 8 | pathPrefix: '/', 9 | gaTrackingId: null, 10 | siteImage: null, 11 | favicon: '/assets/favicon.png', 12 | themeColor: '#', 13 | }, 14 | header: { 15 | logo: '', 16 | logoLink: '/', 17 | helpUrl: '', 18 | links: [], 19 | }, 20 | sidebar: { 21 | enabled: true, 22 | ignoreIndex: true, 23 | forcedNavOrder: [], 24 | expanded: [], 25 | groups: [], 26 | links: [], 27 | poweredBy: {}, 28 | }, 29 | 30 | pwa: { 31 | enabled: true, // disabling this will also remove the existing service worker. 32 | manifest: { 33 | name: 'BooGi', 34 | short_name: 'BooGi', 35 | start_url: '/', 36 | background_color: '#6b37bf', 37 | theme_color: '#6b37bf', 38 | display: 'minimal-ui', 39 | crossOrigin: 'anonymous', 40 | }, 41 | }, 42 | social: { 43 | facebook: '', 44 | github: '', 45 | gitlab: '', 46 | instagram: '', 47 | linkedin: '', 48 | mail: '', 49 | gmail: '', 50 | slack: '', 51 | twich: '', 52 | twitter: '', 53 | youtube: '', 54 | }, 55 | features: { 56 | editOnRepo: { 57 | editable: true, 58 | location: 'https://github.com/filipowm/boogi', 59 | type: 'github', 60 | }, 61 | search: { 62 | enabled: true, 63 | indexName: 'docs', 64 | algoliaAppId: null, 65 | algoliaSearchKey: null, 66 | algoliaAdminKey: null, 67 | excerptSize: 20000, 68 | engine: 'localsearch', 69 | placeholder: 'Search', 70 | startComponent: 'icon', // 'input', 71 | debounceTime: 380, 72 | snippetLength: 23, 73 | hitsPerPage: 10, 74 | showStats: true, 75 | localSearchEngine: { 76 | encode: "advanced", 77 | tokenize: "full", 78 | threshold: 2, 79 | resolution: 30, 80 | depth: 20 81 | }, 82 | pagination: { 83 | enabled: true, 84 | totalPages: 10, 85 | showNext: true, 86 | showPrevious: true, 87 | }, 88 | }, 89 | toc: { 90 | show: true, 91 | depth: 3, 92 | }, 93 | previousNext: { 94 | enabled: true, 95 | arrowKeyNavigation: true, 96 | }, 97 | scrollTop: true, 98 | showMetadata: true, 99 | propagateNetlifyEnv: true, 100 | pageProgress: { 101 | enabled: false, 102 | // includePaths: [], 103 | excludePaths: ['/'], 104 | height: 3, 105 | prependToBody: false, 106 | color: '#A05EB5', 107 | }, 108 | mermaid: { 109 | language: 'mermaid', 110 | theme: 'dark', // default, dark, forest, neutral 111 | options: {}, // https://mermaidjs.github.io/#/mermaidAPI 112 | width: 300, 113 | height: 300, 114 | }, 115 | rss: { 116 | enabled: true, 117 | showIcon: true, 118 | title: 'My RSS feed', 119 | copyright: '', 120 | webMaster: 'M', 121 | managingEditor: '', 122 | categories: ['GatsbyJS', 'Docs'], 123 | ttl: '60', 124 | matchRegex: '^/', 125 | outputPath: '/rss.xml', 126 | generator: 'gidocs', 127 | }, 128 | darkMode: { 129 | enabled: true, 130 | default: false, 131 | }, 132 | publishDraft: false, 133 | fullScreenMode: { 134 | enabled: false, 135 | hideHeader: true, 136 | hideToc: true, 137 | hideSidebar: true 138 | } 139 | }, 140 | }; 141 | -------------------------------------------------------------------------------- /config/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | metadata: 3 | name: BooGi 4 | short_name: BooGi 5 | description: BooGi - awesome GitBook-like documentation generator using Gatsby 6 | language: en 7 | pathPrefix: "/" 8 | siteImage: 9 | favicon: "/assets/favicon.png" 10 | themeColor: "#ff0000" 11 | header: 12 | enabled: true 13 | logo: "/assets/code.svg" 14 | logoLink: "/" 15 | helpUrl: "" 16 | links: 17 | - text: BooGi 18 | link: https://github.com/filipowm/boogi 19 | external: true 20 | - text: Gatsby 21 | link: https://www.gatsbyjs.org 22 | external: true 23 | - text: Docs 24 | link: "/" 25 | external: false 26 | sidebar: 27 | enabled: true 28 | # forcedNavOrder: 29 | # - "/introduction" 30 | # - "/configuration/basic" 31 | # - "/configuration/advanced" 32 | # expanded: 33 | groups: 34 | - order: 1 35 | path: "/gettingstarted" 36 | title: ":rocket: Getting Started" 37 | - order: 2 38 | path: "/configuration" 39 | title: ":wrench: Configuration" 40 | - order: 3 41 | path: "/editing" 42 | title: ":writing_hand: Editing Content" 43 | - order: 4 44 | path: "/deployment" 45 | title: ":rocket: Deployment" 46 | - order: 5 47 | path: "/developing" 48 | title: ":computer: Developing" 49 | links: 50 | - text: BooGi 51 | link: https://github.com/filipowm/boogi 52 | - text: React 53 | link: https://reactjs.org 54 | ignoreIndex: false 55 | poweredBy: 56 | trademark: "/assets/gatsby.png" 57 | name: GatsbyJS 58 | link: https://www.gatsbyjs.org 59 | pwa: 60 | enabled: true 61 | manifest: 62 | display: standalone 63 | crossOrigin: anonymous 64 | icon: "/assets/favicon.png" 65 | 66 | social: 67 | github: https://github.com/filipowm/boogi 68 | linkedin: https://www.linkedin.com/in/mateusz-filipowicz-437b4768/ 69 | twitter: twitter url 70 | 71 | features: 72 | darkMode: 73 | enabled: true 74 | default: false 75 | editOnRepo: 76 | location: https://github.com/filipowm/boogi 77 | type: github 78 | editable: true 79 | mermaid: 80 | language: "mermaid" 81 | theme: "dark" 82 | options: 83 | width: 400 84 | height: 300 85 | pageProgress: 86 | enabled: true 87 | includePaths: 88 | - "/configuration/settingup/features" 89 | excludePaths: 90 | - "/" 91 | height: 3 92 | prependToBody: false 93 | color: "#A05EB5" 94 | previousNext: 95 | enabled: true 96 | arrowKeyNavigation: true 97 | propagateNetlifyEnv: true 98 | rss: 99 | enabled: true 100 | title: BooGi Docs 101 | showIcon: false 102 | copyright: "2020, Mateusz Filipowicz" 103 | webMaster: "Mateusz Filipowicz" 104 | managingEditor: "Mateusz Filipowicz" 105 | categories: 106 | - Docs as Code 107 | - GatsbyJS 108 | ttl: 60 109 | matchRegex: "^/" 110 | outputPath: "/rss.xml" 111 | generator: "boogi" 112 | scrollTop: true 113 | search: 114 | enabled: true 115 | engine: 'algolia' 116 | placeholder: 'Search' 117 | startComponent: 'input' # icon 118 | excerptSize: 8000 119 | debounceTime: 380 120 | snippetLength: 23 121 | hitsPerPage: 10 122 | showStats: true 123 | pagination: 124 | enabled: true 125 | totalPages: 10 126 | showPrevious: true 127 | showNext: true 128 | showMetadata: true 129 | toc: 130 | show: true 131 | depth: 4 132 | fullScreenMode: 133 | enabled: true 134 | hideHeader: true 135 | hideToc: true 136 | hideSidebar: true 137 | -------------------------------------------------------------------------------- /content/configuration/setting-up/full_example.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Full example' 3 | order: 999 4 | --- 5 | 6 | ```yaml 7 | --- 8 | metadata: 9 | name: BooGi 10 | short_name: BooGi 11 | description: Awesome GitBook-like documentation generator using Gatsby 12 | language: en 13 | pathPrefix: "/" 14 | siteImage: 15 | favicon: "/assets/favicon.png" 16 | themeColor: "#ff0000" 17 | header: 18 | enabled: true 19 | logo: "" 20 | logoLink: "/" 21 | helpUrl: "" 22 | links: 23 | - text: BooGi 24 | link: https://github.com/filipowm/boogi 25 | external: true 26 | - text: Gatsby 27 | link: https://www.gatsbyjs.org 28 | external: true 29 | - text: Docs 30 | link: "/" 31 | external: false 32 | sidebar: 33 | enabled: true 34 | # forcedNavOrder: 35 | # - "/introduction" 36 | # - "/configuration/basic" 37 | # - "/configuration/advanced" 38 | # expanded: 39 | groups: 40 | - order: 1 41 | path: "/gettingstarted" 42 | title: ":rocket: Getting Started" 43 | - order: 2 44 | path: "/configuration" 45 | title: ":wrench: Configuration" 46 | - order: 3 47 | path: "/editing" 48 | title: ":writing_hand: Editing Content" 49 | - order: 4 50 | path: "/deployment" 51 | title: ":rocket: Deployment" 52 | - order: 5 53 | path: "/developing" 54 | title: ":computer: Developing" 55 | links: 56 | - text: BooGi 57 | link: https://github.com/filipowm/boogi 58 | - text: React 59 | link: https://reactjs.org 60 | ignoreIndex: false 61 | poweredBy: 62 | trademark: "/assets/gatsby.png" 63 | name: GatsbyJS 64 | link: https://www.gatsbyjs.org 65 | pwa: 66 | enabled: true 67 | manifest: 68 | display: standalone 69 | crossOrigin: anonymous 70 | icon: "/assets/favicon.png" 71 | 72 | social: 73 | github: https://github.com/filipowm/boogi 74 | linkedin: https://www.linkedin.com/in/mateusz-filipowicz-437b4768/ 75 | 76 | features: 77 | darkMode: 78 | enabled: true 79 | default: false 80 | editOnRepo: 81 | location: https://github.com/filipowm/boogi 82 | type: github 83 | editable: true 84 | mermaid: 85 | language: "mermaid" 86 | theme: "dark" 87 | options: 88 | width: 400 89 | height: 300 90 | pageProgress: 91 | enabled: true 92 | includePaths: 93 | - "/configuration/settingup/features" 94 | excludePaths: 95 | - "/" 96 | height: 3 97 | prependToBody: false 98 | color: "#A05EB5" 99 | previousNext: 100 | enabled: true 101 | arrowKeyNavigation: true 102 | propagateNetlifyEnv: true 103 | rss: 104 | enabled: true 105 | showIcon: false 106 | copyright: "2020, Mateusz Filipowicz" 107 | webMaster: "Mateusz Filipowicz" 108 | managingEditor: "Mateusz Filipowicz" 109 | categories: 110 | - Docs as Code 111 | - GatsbyJS 112 | ttl: 60 113 | matchRegex: "^/" 114 | outputPath: "/rss.xml" 115 | generator: "boogi" 116 | scrollTop: true 117 | search: 118 | enabled: true 119 | engine: 'algolia' 120 | placeholder: 'Search' 121 | startComponent: 'input' # icon 122 | debounceTime: 380 123 | snippetLength: 23 124 | hitsPerPage: 10 125 | showStats: true 126 | pagination: 127 | enabled: true 128 | totalPages: 10 129 | showPrevious: true 130 | showNext: true 131 | showMetadata: true 132 | toc: 133 | show: true 134 | depth: 3 135 | publishDraft: false 136 | fullScreenMode: 137 | enabled: true 138 | hideHeader: false 139 | hideSidebar: true 140 | hideToc: true 141 | ``` 142 | -------------------------------------------------------------------------------- /src/components/EditOnRepo/EditOnRepo.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-case-declarations */ 2 | import PropTypes from 'prop-types'; 3 | import React from 'react'; 4 | import styled from '@emotion/styled'; 5 | import { Link } from '../'; 6 | import { shadowAround } from '../../styles'; 7 | import { onTablet, onMobile } from '../../styles/responsive'; 8 | 9 | const Edit = styled('div')` 10 | text-align: right; 11 | ${onMobile} { 12 | padding-left: 0; 13 | } 14 | a { 15 | ${onTablet} { 16 | padding: 5px 12px; 17 | min-width: auto; 18 | font-size: 14px; 19 | font-weight: 400; 20 | } 21 | ${onMobile} { 22 | padding: 4px 8px; 23 | font-size: 13px; 24 | } 25 | font-weight: 500; 26 | line-height: 1em; 27 | cursor: pointer; 28 | align-items: center; 29 | min-width: 175px; 30 | outline: none; 31 | transition: ${(props) => props.theme.transitions.hover}; 32 | border: 1px solid ${(props) => props.theme.editOnRepo.border}; 33 | border-radius: 4px; 34 | color: ${(props) => props.theme.editOnRepo.font.base}; 35 | background-color: ${(props) => props.theme.editOnRepo.background}; 36 | height: 30px; 37 | padding: 5px 16px; 38 | &:hover { 39 | background-color: ${(props) => props.theme.editOnRepo.hover}; 40 | color: ${(props) => props.theme.editOnRepo.font.hover}; 41 | } 42 | } 43 | `; 44 | 45 | const EditButton = styled(({ className, icon, link, text }) => { 46 | return ( 47 | 48 | 49 | {'Git 50 | {text} 51 | 52 | 53 | ); 54 | })` 55 | height: 40px; 56 | min-height: 40px; 57 | display: flex; 58 | align-items: center; 59 | ${onTablet} { 60 | height: 37px; 61 | min-height: 37px; 62 | } 63 | ${onMobile} { 64 | height: 32px; 65 | min-height: 32px; 66 | } 67 | img { 68 | width: 20px; 69 | display: inline-block; 70 | margin-right: 10px; 71 | } 72 | `; 73 | 74 | const rootDir = 'content'; 75 | 76 | const EditOnRepo = ({ repoType, branch, location, path }) => { 77 | let icon = null; 78 | let link = `${location}/${path}`; 79 | let text = 'Edit on '; 80 | switch (repoType.toLowerCase()) { 81 | case 'gitlab': 82 | icon = require('images/gitlab.svg'); 83 | const splitted = location.split('/'); 84 | const protocol = splitted[0]; 85 | const host = splitted[2]; 86 | // it does not support contexts 87 | const repo = splitted.slice(3).join('/'); 88 | link = `${protocol}//${host}/-/ide/project/${repo}/blob/${branch}/-/${rootDir}/${path}`; 89 | text += 'GitLab'; 90 | break; 91 | case 'github': 92 | icon = require('images/github.svg'); 93 | link = `${location}/edit/${branch}/${rootDir}/${path}`; 94 | text += 'Github'; 95 | break; 96 | case 'bitbucket': 97 | icon = require('images/bitbucket.svg'); 98 | link = `${location}/src/${branch}/${rootDir}/${path}?mode=edit&spa=0&at=${branch}`; 99 | text += 'Bitbucket'; 100 | break; 101 | default: 102 | console.log(`Repository type ${repoType} is not supported by edit on repo feature`); 103 | return ''; 104 | } 105 | return ; 106 | }; 107 | 108 | EditOnRepo.propTypes = { 109 | repoType: PropTypes.string.isRequired, 110 | branch: PropTypes.string.isRequired, 111 | location: PropTypes.string.isRequired, 112 | path: PropTypes.string.isRequired, 113 | }; 114 | 115 | export default EditOnRepo; 116 | -------------------------------------------------------------------------------- /src/components/Search/Pagination.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { ChevronLeft, ChevronRight } from 'react-feather'; 4 | import { onMobile } from '../../styles/responsive'; 5 | 6 | const Button = styled(({ refine, page, show, isCurrent, children, ...props }) => { 7 | const changePage = (event) => { 8 | event.preventDefault(); 9 | refine(page); 10 | }; 11 | return ( 12 | 15 | ); 16 | })` 17 | width: 32px; 18 | height: 32px; 19 | visibility: ${(props) => (props.show || props.show === undefined ? 'visible' : 'hidden')}; 20 | vertical-align: middle; 21 | transition: ${(props) => props.theme.transitions.hover}; 22 | background-color: ${(props) => 23 | props.isCurrent 24 | ? props.theme.search.pagination.current.background 25 | : props.theme.search.pagination.background}; 26 | border: 1px solid ${(props) => props.theme.search.pagination.border}; 27 | 28 | color: ${(props) => 29 | props.isCurrent 30 | ? props.theme.search.pagination.current.font 31 | : props.theme.search.pagination.font}; 32 | border-radius: 4px; 33 | box-shadow: 0 0 4px 0 ${(props) => props.theme.colors.border}; 34 | font-size: 1em; 35 | cursor: inherit; 36 | &:hover { 37 | background-color: ${(props) => props.theme.search.pagination.hover}; 38 | color: ${(props) => props.theme.search.pagination.fontHover}; 39 | } 40 | svg { 41 | stroke: ${(props) => props.theme.search.pagination.font}; 42 | vertical-align: middle; 43 | } 44 | `; 45 | 46 | const PagesList = styled.ul` 47 | display: flex; 48 | list-style: none; 49 | margin: 0 auto; 50 | align-items: center; 51 | 52 | padding: 12px 0; 53 | ${onMobile} { 54 | padding: 8px 0; 55 | } 56 | li { 57 | margin: 0 1px; 58 | cursor: pointer; 59 | } 60 | `; 61 | 62 | const PagesListWrapper = styled.div` 63 | border-top: 1px solid ${(props) => props.theme.search.pagination.border}; 64 | background: ${(props) => props.theme.colors.background}; 65 | width: 100%; 66 | display: flex; 67 | position: sticky; 68 | bottom: 0; 69 | box-shadow: 0 -2px 4px 0 ${(props) => props.theme.colors.shadow}; 70 | `; 71 | 72 | const leftRightMargin = '12px'; 73 | 74 | const Pagination = ({ totalPages, nbPages, currentPage, refine, showPrevious, showNext }) => { 75 | const pagesToShow = totalPages && nbPages > totalPages ? totalPages : nbPages; 76 | const previousPage = currentPage > 1 ? currentPage - 1 : 1; 77 | const nextPage = currentPage === pagesToShow ? currentPage : currentPage + 1; 78 | return ( 79 | 80 | 81 | {showPrevious ? ( 82 |
      1. 83 | 86 |
      2. 87 | ) : null} 88 | {new Array(pagesToShow).fill(null).map((_, index) => { 89 | const page = index + 1; 90 | const isCurrent = currentPage === page; 91 | 92 | return ( 93 |
      3. 94 | 97 |
      4. 98 | ); 99 | })} 100 | {showNext ? ( 101 |
      5. 102 | 105 |
      6. 106 | ) : ( 107 | '' 108 | )} 109 |
        110 |
        111 | ); 112 | }; 113 | 114 | export default Pagination; 115 | -------------------------------------------------------------------------------- /static/assets/settings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const startCase = require('lodash.startcase'); 3 | const chokidar = require(`chokidar`); 4 | const { touch } = require('./src/utils/fileUtils'); 5 | 6 | exports.createSchemaCustomization = ({ actions }) => { 7 | const { createTypes } = actions; 8 | 9 | const typeDefs = ` 10 | type MarkdownRemark implements Node { 11 | frontmatter: MdxFrontmatter 12 | } 13 | type MdxFrontmatter { 14 | showToc: Boolean 15 | tocDepth: Int 16 | editable: Boolean 17 | showMetadata: Boolean 18 | showPreviousNext: Boolean 19 | description: String 20 | metaTitle: String 21 | order: Int 22 | } 23 | type File implements Node { 24 | fields: Fields 25 | } 26 | type Fields { 27 | gitLogLatestAuthorName: String 28 | gitLogLatestAuthorEmail: String 29 | gitLogLatestDate: Date @dateformat 30 | } 31 | type SiteSiteMetadata implements Node { 32 | headerLinks: [HeaderLinks] 33 | } 34 | type HeaderLinks { 35 | text: String! 36 | link: String! 37 | external: Boolean 38 | } 39 | `; 40 | createTypes(typeDefs); 41 | }; 42 | 43 | exports.createPages = ({ graphql, actions }) => { 44 | const { createPage } = actions; 45 | return new Promise((resolve, reject) => { 46 | resolve( 47 | graphql( 48 | ` 49 | { 50 | allMdx(filter: {fields: {draft: {ne: true}}}) { 51 | edges { 52 | node { 53 | fields { 54 | id 55 | slug 56 | } 57 | } 58 | } 59 | } 60 | } 61 | ` 62 | ).then((result) => { 63 | if (result.errors) { 64 | console.log(result.errors); // eslint-disable-line no-console 65 | reject(result.errors); 66 | } 67 | actions.createPage({ 68 | path: `/404.html`, 69 | component: path.join(process.cwd(), 'src/pages/404.js'), 70 | }); 71 | 72 | // Create pages. 73 | result.data.allMdx.edges.forEach(({ node }) => { 74 | createPage({ 75 | path: node.fields.slug ? node.fields.slug : '/', 76 | component: path.resolve('./src/templates/docs.js'), 77 | context: { 78 | id: node.fields.id, 79 | }, 80 | }); 81 | }); 82 | }) 83 | ); 84 | }); 85 | }; 86 | 87 | exports.onCreateWebpackConfig = ({ actions }) => { 88 | actions.setWebpackConfig({ 89 | resolve: { 90 | modules: [path.resolve(__dirname, 'src'), 'node_modules'], 91 | alias: { 92 | $components: path.resolve(__dirname, 'src/components'), 93 | buble: '@philpl/buble', // to reduce bundle size 94 | }, 95 | }, 96 | }); 97 | }; 98 | 99 | exports.onCreateBabelConfig = ({ actions }) => { 100 | actions.setBabelPlugin({ 101 | name: '@babel/plugin-proposal-export-default-from', 102 | }); 103 | }; 104 | 105 | exports.onCreateNode = ({ node, getNode, actions }) => { 106 | const { createNodeField } = actions; 107 | 108 | if (node.internal.type === `Mdx`) { 109 | const parent = getNode(node.parent); 110 | let value = parent.relativePath.replace(parent.ext, ''); 111 | 112 | if (value === 'index') { 113 | value = ''; 114 | } 115 | 116 | createNodeField({ 117 | name: `slug`, 118 | node, 119 | value: `/${value}`, 120 | }); 121 | 122 | createNodeField({ 123 | name: 'id', 124 | node, 125 | value: node.id, 126 | }); 127 | 128 | createNodeField({ 129 | name: 'title', 130 | node, 131 | value: node.frontmatter.title || startCase(parent.name), 132 | }); 133 | } 134 | }; 135 | 136 | exports.onPreBootstrap = () => { 137 | const watcher = chokidar.watch('./config', { 138 | ignored: ['jargon*'], 139 | }); 140 | watcher.on(`change`, () => { 141 | touch('./gatsby-config.js'); 142 | }); 143 | }; 144 | -------------------------------------------------------------------------------- /src/components/Search/Input.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import styled from '@emotion/styled'; 3 | import { shadowAround } from '../../styles'; 4 | import { useTheme } from 'emotion-theming'; 5 | import { Search, Trash } from 'react-feather'; 6 | import useDebounce from '../../utils/useDebounce'; 7 | import config from 'config'; 8 | import { marginLeftRight } from './styles'; 9 | import { onMobile } from '../../styles/responsive'; 10 | 11 | const SearchIcon = styled(Search)` 12 | width: 1.2em; 13 | pointer-events: none; 14 | margin: 0 10px; 15 | `; 16 | 17 | const CleanSearch = styled(({ ...props }) => ( 18 |
        19 | 20 |
        21 | ))` 22 | cursor: pointer; 23 | margin: 0 10px; 24 | svg{ 25 | width: 1.2em; 26 | } 27 | &:hover { 28 | svg { 29 | stroke: ${(props) => props.theme.colors.primary}; 30 | } 31 | } 32 | `; 33 | 34 | const Input = styled.input` 35 | outline: none; 36 | border: none; 37 | font-size: 1em; 38 | transition: ${(props) => props.theme.transitions.hover}; 39 | border-radius: 1px; 40 | padding-left: 10px; 41 | background-color: transparent; 42 | width: calc(100% - 26px); 43 | border-width: 0 !important; 44 | &, 45 | ::placeholder { 46 | color: ${(props) => props.theme.colors.gray}; 47 | } 48 | `; 49 | 50 | const Form = styled.form` 51 | display: flex; 52 | margin-top: 12px; 53 | margin-bottom: 12px; 54 | flex-direction: row; 55 | align-items: center; 56 | ${onMobile} { 57 | width: 100%; 58 | margin-top: 8px; 59 | margin-bottom: 8px; 60 | } 61 | padding: 12px 4px; 62 | border-radius: 4px; 63 | background-color: rgba(223,225,235, .4); 64 | border: 1px solid rgba(223,225,235, 1) 65 | &, *, input::placeholder, svg { 66 | transition: ${(props) => props.theme.transitions.hover}; 67 | } 68 | &:focus, &:visited, &:hover, &:focus-within { 69 | outline: none; 70 | background-color: transparent; 71 | input, input::placeholder{ 72 | color: ${(props) => props.theme.colors.grayDark}; 73 | } 74 | svg { 75 | stroke: ${(props) => props.theme.colors.grayDark}; 76 | } 77 | } 78 | 79 | svg { 80 | stroke: ${(props) => props.theme.colors.grayLight}; 81 | } 82 | `; 83 | 84 | const SidebarSearchInputWrapper = styled.div` 85 | position: sticky; 86 | top: 0; 87 | background: ${(props) => props.theme.colors.background}; 88 | width: 100%; 89 | padding: 0 24px; 90 | `; 91 | 92 | const SidebarSearchInput = ({ search, inputRef, showClean, ...props }) => ( 93 | 94 | 95 | 96 | ); 97 | 98 | const SearchInput = ({ search, inputRef, showClean, style, ...props }) => { 99 | const theme = useTheme(); 100 | const preventSubmit = (e) => { 101 | e.preventDefault(); 102 | }; 103 | const [searchTerm, setSearchTerm] = useState(''); 104 | const debouncedSearchTerm = useDebounce(searchTerm, config.features.search.debounceTime); 105 | 106 | useEffect(() => { 107 | if (search && debouncedSearchTerm !== null) { 108 | search(debouncedSearchTerm); 109 | } 110 | }, [debouncedSearchTerm]); 111 | 112 | const clean = (e) => { 113 | e.preventDefault(); 114 | setSearchTerm(''); 115 | inputRef.current.value = ''; 116 | }; 117 | 118 | return ( 119 |
        120 | 121 | { 128 | const value = e.target.value; 129 | if (value && value.length > 0) { 130 | setSearchTerm(value); 131 | } 132 | }} 133 | {...props} 134 | /> 135 | {showClean ? : ''} 136 | 137 | ); 138 | }; 139 | 140 | export { SearchInput, SidebarSearchInput }; 141 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BooGi", 3 | "description": "Generate GitBook-like modern docs/tutorial websites using Gatsby", 4 | "author": "Mateusz Filipowicz ", 5 | "homepage": "https://boogi.netlify.app/", 6 | "keywords": [ 7 | "gatsby", 8 | "gatsby-starter", 9 | "gitbook", 10 | "documentation-tool", 11 | "documentation-generator", 12 | "docs-generator", 13 | "markdown", 14 | "mdx", 15 | "static-site-generator" 16 | ], 17 | "version": "1.0.1", 18 | "dependencies": { 19 | "@babel/plugin-proposal-export-default-from": "7.10.4", 20 | "@emotion/core": "10.0.34", 21 | "@emotion/styled": "10.0.27", 22 | "@emotion/styled-base": "10.0.31", 23 | "@loadable/component": "5.13.1", 24 | "@mdx-js/loader": "1.6.16", 25 | "@mdx-js/mdx": "1.6.16", 26 | "@mdx-js/react": "1.6.16", 27 | "@philpl/buble": "0.19.7", 28 | "@playlyfe/gql": "2.6.2", 29 | "algoliasearch": "4.4.0", 30 | "dotenv": "8.2.0", 31 | "emotion-theming": "10.0.27", 32 | "gatsby": "2.24.43", 33 | "gatsby-link": "2.4.13", 34 | "gatsby-plugin-algolia": "0.11.2", 35 | "gatsby-plugin-canonical-urls": "2.3.10", 36 | "gatsby-plugin-emotion": "4.3.10", 37 | "gatsby-plugin-feed": "2.5.11", 38 | "gatsby-plugin-gtag": "1.0.13", 39 | "gatsby-plugin-instagram-embed": "2.0.1", 40 | "gatsby-plugin-layout": "1.3.10", 41 | "gatsby-plugin-loadable-components-ssr": "2.1.0", 42 | "gatsby-plugin-local-search": "1.1.1", 43 | "gatsby-plugin-manifest": "2.4.23", 44 | "gatsby-plugin-mdx": "1.2.34", 45 | "gatsby-plugin-offline": "3.2.23", 46 | "gatsby-plugin-page-progress": "2.1.0", 47 | "gatsby-plugin-pinterest": "3.0.0", 48 | "gatsby-plugin-react-helmet": "3.3.10", 49 | "gatsby-plugin-react-svg": "3.0.0", 50 | "gatsby-plugin-remove-serviceworker": "1.0.0", 51 | "gatsby-plugin-root-import": "2.0.5", 52 | "gatsby-plugin-sass": "2.3.12", 53 | "gatsby-plugin-sharp": "2.6.27", 54 | "gatsby-plugin-sitemap": "2.4.11", 55 | "gatsby-plugin-twitter": "2.3.10", 56 | "gatsby-remark-copy-linked-files": "2.3.12", 57 | "gatsby-remark-embed-snippet": "4.3.14", 58 | "gatsby-remark-embedder": "3.0.0", 59 | "gatsby-remark-graphviz": "1.3.10", 60 | "gatsby-remark-images": "3.3.25", 61 | "gatsby-remark-jargon": "2.9.0", 62 | "gatsby-remark-mermaid": "2.0.0", 63 | "gatsby-source-filesystem": "2.3.24", 64 | "gatsby-source-local-git": "1.1.1", 65 | "gatsby-transformer-gitinfo": "1.1.0", 66 | "gatsby-transformer-remark": "2.8.28", 67 | "gatsby-transformer-sharp": "2.5.13", 68 | "is-absolute-url": "3.0.3", 69 | "lodash": "4.17.20", 70 | "lodash.flatten": "4.4.0", 71 | "lodash.startcase": "4.4.0", 72 | "node-sass": "4.14.1", 73 | "puppeteer": "5.2.1", 74 | "react": "16.13.1", 75 | "react-collapsible": "^2.8.0", 76 | "react-dom": "16.13.1", 77 | "react-feather": "2.0.8", 78 | "react-helmet": "6.1.0", 79 | "react-id-generator": "3.0.1", 80 | "react-instantsearch-dom": "6.7.0", 81 | "react-live": "2.2.2", 82 | "react-loadable": "5.5.0", 83 | "react-reveal": "1.2.2", 84 | "react-scrollspy": "3.4.3", 85 | "react-use-flexsearch": "^0.1.1", 86 | "react-visibility-sensor": "5.1.1", 87 | "remark-abbr": "1.4.0", 88 | "remark-emoji": "2.1.0", 89 | "unist-util-find-after": "3.0.0" 90 | }, 91 | "license": "MIT", 92 | "main": "n/a", 93 | "scripts": { 94 | "start": "gatsby develop", 95 | "build": "gatsby build --prefix-paths", 96 | "format": "prettier --ignore-path \".gitignore\" --write \"**/*.{js,jsx,css,json}\"", 97 | "lint": "eslint --fix \"**/*.{js,jsx}\"" 98 | }, 99 | "devDependencies": { 100 | "babel-eslint": "10.1.0", 101 | "eslint": "7.11.0", 102 | "eslint-config-prettier": "6.11.0", 103 | "eslint-plugin-import": "2.22.1", 104 | "eslint-plugin-jsx-a11y": "6.3.1", 105 | "eslint-plugin-prettier": "3.1.4", 106 | "eslint-plugin-react": "7.21.4", 107 | "gatsby-plugin-remove-trailing-slashes": "2.3.11", 108 | "prettier": "2.0.5", 109 | "prism-react-renderer": "1.1.1" 110 | }, 111 | "resolutions": { 112 | "gatsby-react-router-scroll": "3.0.3", 113 | "lodash": "4.17.20" 114 | } 115 | } -------------------------------------------------------------------------------- /src/components/Search/localsearch/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { HitsWrapper } from '../Hits'; 3 | import config from 'config'; 4 | import Input from './input'; 5 | import { PageHit } from './hitComps'; 6 | import SearchStatus from '../Status'; 7 | import Pagination from './pagination'; 8 | import Stats from './stats'; 9 | import { useFlexSearch } from 'react-use-flexsearch'; 10 | import { StaticQuery, graphql } from 'gatsby'; 11 | 12 | const Results = ({ q }) => ; 13 | 14 | const getPerformance = () => { 15 | if (typeof window !== `undefined` && window.performance) { 16 | return window.performance; 17 | } 18 | return { 19 | now: () => new Date().getMilliseconds() 20 | } 21 | } 22 | 23 | const calculatePage = (results, page) => { 24 | const hitsPerPage = config.features.search.hitsPerPage; 25 | const startIdx = hitsPerPage * page - hitsPerPage; 26 | const endIdx = startIdx + hitsPerPage; 27 | return results.slice(startIdx, endIdx); 28 | }; 29 | 30 | const search = (query, index, store, page) => { 31 | const performance = getPerformance(); 32 | const t1 = performance.now(); 33 | const results = useFlexSearch(query, index, JSON.parse(store)); 34 | const maxResults = 35 | config.features.search.pagination.totalPages * config.features.search.hitsPerPage; 36 | const nbHits = results.length > maxResults ? maxResults : results.length; 37 | 38 | const pages = Math.ceil(results.length / config.features.search.hitsPerPage); 39 | const pageHits = calculatePage(results, page); 40 | const t2 = performance.now(); 41 | const processingTimeMS = (t2 - t1).toFixed(2); 42 | return { 43 | hits: pageHits, 44 | nbHits: nbHits, 45 | pages: pages, 46 | processingTimeMS: processingTimeMS, 47 | }; 48 | }; 49 | 50 | const LocalSearch = ({ inputRef }) => ( 51 | { 61 | const [query, setQuery] = useState(null); 62 | const [focus, setFocus] = useState(false); 63 | const [page, setPage] = useState(1); 64 | const switchPage = (page) => { 65 | setPage(page); 66 | }; 67 | 68 | const searchResult = search(query, index, store, page); 69 | const showResults = query && query.length > 1 && focus; 70 | return ( 71 | <> 72 | setQuery(value)} 74 | inputRef={inputRef} 75 | onFocus={() => setFocus(true)} 76 | {...{ focus }} 77 | /> 78 |
        79 | {showResults && config.features.search.showStats ? ( 80 | 84 | ) : null} 85 | {showResults && searchResult && searchResult.hits.length === 0 ? ( 86 | 87 | ) : null} 88 | 89 |
          90 | {searchResult.hits.map((hit) => ( 91 | 97 | ))} 98 |
        99 |
        100 |
        101 | {showResults && 102 | searchResult.hits.length > 0 && 103 | config.features.search.pagination.enabled ? ( 104 | 112 | ) : null} 113 | 114 | ); 115 | }} 116 | /> 117 | ); 118 | 119 | export default LocalSearch; 120 | -------------------------------------------------------------------------------- /src/components/Layout/Layout.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { MDXProvider } from '@mdx-js/react'; 3 | import { 4 | Header, 5 | MdxComponents, 6 | SearchSidebar, 7 | ScrollTop, 8 | Sidebar, 9 | TableOfContents, 10 | ThemeProvider, 11 | } from '../'; 12 | 13 | import config from 'config'; 14 | import React, { useRef, useEffect, useState } from 'react'; 15 | import { Slide } from 'react-reveal'; 16 | import { hiddenMobile, hiddenTablet } from '../../styles'; 17 | import { onMobile, onTablet } from '../../styles/responsive'; 18 | import 'css'; 19 | 20 | const Wrapper = styled.div` 21 | display: flex; 22 | overflow-wrap: anywhere; 23 | justify-content: space-between; 24 | position: relative; 25 | 26 | ${onMobile} { 27 | min-height: 95vh; 28 | } 29 | `; 30 | 31 | const Content = styled('main')` 32 | display: flex; 33 | flex-grow: 1; 34 | flex-direction: column; 35 | padding: 50px 70px; 36 | background-color: ${(props) => props.theme.content.background}; 37 | 38 | ${onTablet} { 39 | padding: 30px; 40 | } 41 | ${onMobile} { 42 | padding: 15px; 43 | } 44 | `; 45 | 46 | /** 47 | * Hook that alerts clicks outside of the passed ref 48 | */ 49 | function actOnClose(ref, onClose) { 50 | useEffect(() => { 51 | /** 52 | * Alert if clicked on outside of element 53 | */ 54 | function handleClickOutside(event) { 55 | if (ref.current && !ref.current.contains(event.target)) { 56 | onClose(ref.current); 57 | } 58 | } 59 | 60 | function handleEscape(event) { 61 | if (event.key === 'Escape') { 62 | onClose(ref.current); 63 | } 64 | } 65 | // Bind the event listener 66 | document.addEventListener('mousedown', handleClickOutside); 67 | document.addEventListener('touchstart', handleClickOutside); 68 | document.addEventListener('keydown', handleEscape); 69 | return () => { 70 | // Unbind the event listener on clean up 71 | document.removeEventListener('mousedown', handleClickOutside); 72 | document.removeEventListener('touchstart', handleClickOutside); 73 | document.removeEventListener('keydown', handleEscape); 74 | }; 75 | }, [ref]); 76 | } 77 | 78 | const Layout = ({ children, location }) => { 79 | const [showSearch, setShowSearch] = useState(false); 80 | const [searchVisible, setSearchVisible] = useState(false); 81 | const [fullscreenMode, setFullScreenMode] = useState(false); 82 | const themeProviderRef = React.createRef(); 83 | const searchSidebarRef = useRef(null); 84 | const closeSearch = () => setShowSearch(false); 85 | actOnClose(searchSidebarRef, closeSearch); 86 | 87 | return ( 88 | 89 | {config.header.enabled === true ? ( 90 | <> 91 |
        98 | 99 | 100 | 101 |
        102 |
        setFullScreenMode(!fullscreenMode)} 108 | /> 109 | 110 | ) : ( 111 | '' 112 | )} 113 | 114 | {config.features.scrollTop === true ? : ''} 115 | 116 | {config.sidebar.enabled === true ? ( 117 | 118 | ) : ( 119 | '' 120 | )} 121 | {children} 122 | 123 | 124 | 125 | 126 | ); 127 | }; 128 | 129 | export default Layout; 130 | -------------------------------------------------------------------------------- /content/configuration/setting-up/base.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Base configuration' 3 | order: 0 4 | --- 5 | 6 | While creating your own BooGi-based page, you should first set up 7 | metadata. It defines core information about your page required for 8 | it to work, its identification and SEO. 9 | 10 | Metadata base configuration can be set in `config.yaml` under `metadata` key. 11 | 12 | | Property | Description | Required | Default value | 13 | |-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|-----------------------| 14 | | `metadata.name` | Website full name. | Yes | | 15 | | `metadata.short_name` | Website short name, it can be abbreviation. | No | | 16 | | `metadata.description` | Website description. Used to improve website SEO. | No | | 17 | | `metadata.url` | Full URL to this website, e.g. `https://mysite.com`. | Yes | http://localhost:8000 | 18 | | `metadata.language` | Website language. Use [ISO Language Code](https://www.w3schools.com/tags/ref_language_codes.asp) to define language property. | No | en | 19 | | `metadata.pathPrefix` | Prefix defined within your reverse proxy or web server, on which this page is hosted. It can be also referred to as context. Example if your page is accessible under `https://mysite.com/**mycontext**`, then `pathPrefix` should be set to `/mycontext`. | Yes | / | 20 | | `metadata.favicon` | Path to favicon. Can be relative (e.g. `/assets/favicon.png`) or absolute (e.g. `https://somesite.com/myfavicon.png`). | No | /assets/favicon.png | 21 | | `metadata.siteImage` | URL to image describing the website. Used to improve website SEO. | No | | 22 | | `metadata.themeColor` | Primary theme color of the website. More information about it you can check [here](https://developers.google.com/web/updates/2015/08/using-manifest-to-set-sitewide-theme-color). | No | | 23 | | `metadata.gaTrackingId` | Google Analytics Tracking ID. It is used to enable Google Analytics for the website. | No | | 24 | 25 | **Full configuration example** 26 | 27 | ```yaml 28 | metadata: 29 | name: Your Docs Name 30 | short_name: YDN 31 | description: This is awesome page based on BooGi 32 | url: https://myapp.com 33 | language: en 34 | pathPrefix: / 35 | favicon: "/assets/favicon.png" 36 | siteImage: "/assets/mypageimage.png" 37 | themeColor: "#AB12C3" 38 | gaTrackingId: UA-000000-2 39 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Project is no longer maintained (as if it was in recent two years 🙃). I suggest to switch to [Docusaurus](https://docusaurus.io/) which is really 2 | powerful documentation sites engine, quite similar to BooGi** 3 | 4 | # BooGi 5 | 6 | [![Netlify Status](https://api.netlify.com/api/v1/badges/c0cec88f-db01-4c57-8b8d-782e07a9f73f/deploy-status)](https://app.netlify.com/sites/boogi/deploys) 7 | ![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/filipowm/boogi) 8 | ![CI](https://github.com/filipowm/boogi/workflows/CI/badge.svg) 9 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/d0d45783a9bb47058b574a8a42d736fd)](https://www.codacy.com/manual/matfilipowicz/BooGi?utm_source=github.com&utm_medium=referral&utm_content=filipowm/BooGi&utm_campaign=Badge_Grade) 10 | 11 | Create **awesome documentation** or tutorial pages with modern look-and-feel. 12 | Customize it to your needs, run locally, deploy anywhere. 13 | 14 | **Important** Check [boogi-cli](https://github.com/filipowm/boogi-cli) to start 15 | quickly, simplify your codebase, easily run locally and build you BooGi-based app. 16 | We recommend using `boogi-cli` instead of using `gatsby-cli` directly. 17 | 18 | ## Motivation 19 | 20 | Goal is to give teams powerful tool which they can use to efficiently and 21 | collaboratively share their knowledge. They can easily host it on any 22 | infrastructure of choice or SaaS hosting like Netlify, Vercel or 23 | GitHub / GitLab Pages. We want to provide a product, which can be customized 24 | to (nearly) any needs, either using basic or advanced configuration options. 25 | 26 | BooGi is inspired by popular [Gitbook](https://gitbook.com) look and feel. 27 | It offers custom styling and components that enable building beautiful documentation 28 | for projects and products quickly. It follows docs-as-code principles, where 29 | you treat your documentation in the same way as your code. 30 | 31 | It is a fork of https://github.com/hasura/gatsby-gitbook-starter, however 32 | it went through total rework and changes. We improve it to provide significantly 33 | more features, make look-and-feel more similar to Gitbook, improve stability, 34 | performance, make it more configurable and easier to start with. 35 | 36 | ## 🔥 Features 37 | 38 | - Write using Markdown / [MDX](https://github.com/mdx-js/mdx) 39 | - customizing your page to match your branding and needs 40 | - GitBook-like style theme, inspired by https://docs.gitbook.com/ 41 | - light / dark mode themes 42 | - responsive design with mobile / tablet support 43 | - rich-content and rich-text features like text formatting, graphs and diagrams, 44 | quotes, columnar layout, emojis, highlights, live code editor, 45 | syntax highlighting, external code snippets and many many more! 46 | - draft pages 47 | - search integration with [Algolia](https://www.algolia.com/) 48 | - local search (search in a browser without need to integrate with Algolia) 49 | - Progressive Web App which can work offline 50 | - integration with Google Analytics 51 | - full screen mode 52 | - Search Engine Optimization (_SEO_) friendliness 53 | - RSS feed 54 | - easy way to edit content on Gitlab, Github or Bitbucket 55 | - custom CLI to easily initialize and develop BooGi app 56 | - easy deployment on platform of your choice 57 | 58 | ## 🔗 Docs and live Demo 59 | 60 | Here's a [BooGi documentation](https://boogi.netlify.app) being 61 | also a live demo. 62 | 63 | ## 🚀 Quickstart 64 | 65 | ### Using `boogi-cli` (recommended) 66 | 67 | You need to have `boogi-cli` installed: `npm install -g boogi-cli`. 68 | 69 | 1. Initialize BooGi project (config wizard will help you to 70 | set it up!) in current directory: 71 | ```bash 72 | boogi init 73 | ``` 74 | 75 | 2. Run your app in development mode with live reload 76 | ```bash 77 | boogi develop 78 | ``` 79 | 80 | 3. Build you app package ready for deployment 81 | ```bash 82 | boogi build 83 | ``` 84 | 85 | ### Using `gatsby-cli` 86 | 87 | You need to have `gatsby-cli` installed: `npm install -g gatsby-cli`. 88 | 89 | Get started by running the following commands (using Gatsby CLI): 90 | 91 | ``` 92 | $ git clone git@github.com:filipowm/boogi.git 93 | $ yarn 94 | $ gatsby develop 95 | ``` 96 | 97 | Visit `http://localhost:8000/` to view the app. 98 | 99 | ## ☁️ Deploy 100 | 101 | [![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/filipowm/boogi) 102 | -------------------------------------------------------------------------------- /src/components/MdxComponents/codeBlock.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Highlight, { defaultProps } from 'prism-react-renderer'; 3 | import theme from 'prism-react-renderer/themes/dracula'; 4 | import Loadable from 'react-loadable'; 5 | import LoadingProvider from './loading'; 6 | 7 | /** Removes the last token from a code example if it's empty. */ 8 | function cleanTokens(tokens) { 9 | const tokensLength = tokens.length; 10 | 11 | if (tokensLength === 0) { 12 | return tokens; 13 | } 14 | const lastToken = tokens[tokensLength - 1]; 15 | 16 | if (lastToken.length === 1 && lastToken[0].empty) { 17 | return tokens.slice(0, tokensLength - 1); 18 | } 19 | return tokens; 20 | } 21 | 22 | const LoadableComponent = Loadable({ 23 | loader: () => import('./LiveProvider'), 24 | loading: LoadingProvider, 25 | }); 26 | 27 | /* eslint-disable react/jsx-key */ 28 | const CodeBlock = ({ children: code, ...props }) => { 29 | if (props['react-live']) { 30 | return ; 31 | } else { 32 | const lang = props.className ? props.className.split('-')[1] : null; 33 | return ( 34 | 35 | {({ className, style, tokens, getLineProps, getTokenProps }) => ( 36 |
         37 |             {cleanTokens(tokens).map((line, i) => {
         38 |               let lineClass = {};
         39 | 
         40 |               let isDiff = false;
         41 | 
         42 |               if (line[0] && line[0].content.length && line[0].content[0] === '+') {
         43 |                 lineClass = { backgroundColor: 'rgba(76, 175, 80, 0.2)' };
         44 |                 isDiff = true;
         45 |               } else if (line[0] && line[0].content.length && line[0].content[0] === '-') {
         46 |                 lineClass = { backgroundColor: 'rgba(244, 67, 54, 0.2)' };
         47 |                 isDiff = true;
         48 |               } else if (line[0] && line[0].content === '' && line[1] && line[1].content === '+') {
         49 |                 lineClass = { backgroundColor: 'rgba(76, 175, 80, 0.2)' };
         50 |                 isDiff = true;
         51 |               } else if (line[0] && line[0].content === '' && line[1] && line[1].content === '-') {
         52 |                 lineClass = { backgroundColor: 'rgba(244, 67, 54, 0.2)' };
         53 |                 isDiff = true;
         54 |               }
         55 |               const lineProps = getLineProps({ line, key: i });
         56 | 
         57 |               lineProps.style = lineClass;
         58 |               const diffStyle = {
         59 |                 userSelect: 'none',
         60 |                 MozUserSelect: '-moz-none',
         61 |                 WebkitUserSelect: 'none',
         62 |               };
         63 | 
         64 |               let splitToken;
         65 | 
         66 |               return (
         67 |                 
        68 | {line.map((token, key) => { 69 | if (isDiff) { 70 | if ( 71 | (key === 0 || key === 1) & 72 | (token.content.charAt(0) === '+' || token.content.charAt(0) === '-') 73 | ) { 74 | if (token.content.length > 1) { 75 | splitToken = { 76 | types: ['template-string', 'string'], 77 | content: token.content.slice(1), 78 | }; 79 | const firstChar = { 80 | types: ['operator'], 81 | content: token.content.charAt(0), 82 | }; 83 | 84 | return ( 85 | 86 | 90 | 91 | 92 | ); 93 | } else { 94 | return ; 95 | } 96 | } 97 | } 98 | return ; 99 | })} 100 |
        101 | ); 102 | })} 103 |
        104 | )} 105 |
        106 | ); 107 | } 108 | }; 109 | 110 | export default CodeBlock; 111 | -------------------------------------------------------------------------------- /src/components/Sidebar/contentTreeNode.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import OpenedSvg from '../../images/opened'; 3 | import ClosedSvg from '../../images/closed'; 4 | import config from 'config'; 5 | import { Link } from '../'; 6 | import { css } from '@emotion/core'; 7 | 8 | import styled from '@emotion/styled'; 9 | import { useTheme } from 'emotion-theming'; 10 | import emoji from '../../utils/emoji'; 11 | 12 | // If you want to have a css call based on props, create a function that returns a css call like this 13 | // let dynamicStyle = (props) => css`color: ${props.color}` 14 | // It can be called directly with props or interpolated in a styled call like this 15 | // let SomeComponent = styled('div')`${dynamicStyle}` 16 | 17 | const activeNode = (theme) => css` 18 | border: 1px solid ${theme.navigationSidebar.row.activeBorder}; 19 | border-right: none; 20 | > a, 21 | button { 22 | padding: 7px 23px 7px 17px; 23 | background-color: ${theme.navigationSidebar.row.active}; 24 | color: ${theme.navigationSidebar.font.active} !important; 25 | } 26 | `; 27 | 28 | const ContentLink = styled(({ className, link, children }) => ( 29 | 30 | {children} 31 | 32 | ))` 33 | color: ${(props) => props.theme.navigationSidebar.font.base}; 34 | flex: 1; 35 | font-size: 14px; 36 | font-weight: 500; 37 | line-height: 1.5; 38 | padding: 8px 24px 8px 18px; 39 | border-radius: 1px; 40 | `; 41 | 42 | const NodeContent = styled(({ className, text, link, children }) => ( 43 |
      7. 44 | {text && {text}} 45 | {children} 46 |
      8. 47 | ))` 48 | list-style: none; 49 | padding: 0; 50 | display: flex; 51 | flex-wrap: wrap; 52 | align-items: stretch; 53 | align-content: stretch; 54 | justify-content: space-between; 55 | > a, 56 | > button { 57 | transition: ${(props) => props.theme.transitions.hover}; 58 | } 59 | &:hover { 60 | > a, 61 | > button { 62 | background-color: ${(props) => props.theme.navigationSidebar.row.hover}; 63 | } 64 | } 65 | `; 66 | 67 | const NestedContentTreeNode = styled( 68 | ({ className, location, children, setCollapsed, collapsed }) => ( 69 | // 70 |
          71 | {children.map((item) => ( 72 | 79 | ))} 80 |
        81 | ) 82 | //
        83 | )` 84 | flex: 100%; 85 | li { 86 | margin-left: 16px; 87 | border-left: 1px solid ${(props) => props.theme.navigationSidebar.font.nested}; 88 | a { 89 | color: ${(props) => props.theme.navigationSidebar.font.nested}; 90 | } 91 | } 92 | `; 93 | 94 | const NodeCollapseButton = styled(({ className, isCollapsed, collapse }) => { 95 | return ( 96 | 99 | ); 100 | })` 101 | background: transparent; 102 | border: none; 103 | outline: none; 104 | z-index: 10; 105 | cursor: pointer; 106 | padding: 0 25px 0 10px; 107 | svg path { 108 | fill: ${(props) => props.theme.navigationSidebar.font.base}; 109 | } 110 | &:hover { 111 | svg path { 112 | fill: ${(props) => props.theme.navigationSidebar.row.collapseHover}; 113 | } 114 | } 115 | `; 116 | 117 | const ContentTreeNode = ({ className, toggle, collapsed, url, title, location, children }) => { 118 | const hasChildren = children.length !== 0; 119 | const active = 120 | location && 121 | (location.pathname === url || 122 | location.pathname === url + '/' || 123 | location.pathname === config.metadata.pathPrefix + url); 124 | const collapse = () => { 125 | toggle(url); 126 | }; 127 | const theme = useTheme(); 128 | let isCollapsed = collapsed[url]; 129 | const text = emoji.emojify(title); 130 | return ( 131 | <> 132 | 138 | {title && hasChildren ? ( 139 | <> 140 | 141 | 142 | ) : null} 143 | 144 | 145 | {!isCollapsed ? ( 146 | 147 | {children} 148 | 149 | ) : null} 150 | 151 | ); 152 | }; 153 | export default ContentTreeNode; 154 | -------------------------------------------------------------------------------- /content/configuration/setting-up/sidebar.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Sidebar' 3 | order: 3 4 | --- 5 | 6 | Metadata base configuration can be set in `config.yaml` under `sidebar` key. 7 | 8 | | Property | Description | Default value | 9 | |-------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------| 10 | | `sidebar.enabled` | Set to `true` to enable sidebar. | `true` | 11 | | `sidebar.ignoreIndex` | Set to `true` to not show main page (index) as first entry in the sidebar. | `false` | 12 | | `sidebar.forcedNavOrder` | List of paths, for which order in sidebar will be enforced. It has priority over any other order setting. Read more about [navigation](/configuration/navigation). | | 13 | | `sidebar.expanded` | List of paths which sidebar entries will be expanded by default, if they are nested. | | 14 | | `sidebar.groups` | List of sidebar groups. | | 15 | | `sidebar.groups[].path` | Path associated with the group. Read more about [navigation](/configuration/navigation) to understand how to set this up. | | 16 | | `sidebar.groups[].title` | Group title, it can include [emojis](/richcontent/emojis). | | 17 | | `sidebar.groups[].order` | Order number of groups. The lower number, the higher order and group will appear higher in sidebar. If not provided or there are groups with equal order, groups are sorted by name. | | 18 | | `sidebar.groups[].icon` | URL to icon associated with the group. You can mix using emojis in group title and group icons. | | 19 | | `sidebar.links` | List of external links visible in at the bottom of sidebar. | | 20 | | `sidebar.links[].text` | Text of the external link. | | 21 | | `sidebar.links[].link` | External link URL. | | 22 | | `sidebar.poweredBy.trademark` | Logo for the Powered By component. | | 23 | | `sidebar.poweredBy.name` | Name on the Powered By component. If name is not provided, Powered By component will be not visible. | | 24 | | `sidebar.poweredBy.link` | URL to the page, which will appear when clicked on Powered By component. | | 25 | 26 | **Full example:** 27 | 28 | ```yaml 29 | sidebar: 30 | enabled: true 31 | forcedNavOrder: 32 | - "/introduction" 33 | - "/configuration/basic" 34 | - "/configuration/advanced" 35 | expanded: 36 | - /configuration/advanced 37 | groups: 38 | - order: 1 39 | path: "/gettingstarted" 40 | title: "Getting Started" 41 | icon: /assets/starting.png 42 | - order: 2 43 | path: "/configuration" 44 | title: ":wrench: Configuration" 45 | - order: 2 46 | path: "/editing" 47 | title: ":writing_hand: Editing Content" 48 | ignoreIndex: false 49 | links: 50 | - text: BooGi 51 | link: https://github.com/filipowm/boogi 52 | poweredBy: 53 | trademark: "/assets/gatsby.png" 54 | name: GatsbyJS 55 | link: https://www.gatsbyjs.org 56 | ``` -------------------------------------------------------------------------------- /src/components/Navigation/Navigation.js: -------------------------------------------------------------------------------- 1 | import { useStaticQuery, graphql } from 'gatsby'; 2 | import config from 'config'; 3 | 4 | const getNavigationData = () => { 5 | const { allMdx } = useStaticQuery(graphql` 6 | query NavigationQuery { 7 | allMdx(filter: {fields: {draft: {ne: true}}}) { 8 | edges { 9 | node { 10 | fields { 11 | slug 12 | title 13 | } 14 | frontmatter { 15 | order 16 | } 17 | } 18 | } 19 | } 20 | } 21 | `); 22 | return allMdx.edges; 23 | }; 24 | 25 | const getGroup = function (url) { 26 | return url ? config.sidebar.groups.find((group) => url.startsWith(group.path)) : null; 27 | }; 28 | 29 | const createUnassignedGroup = () => { 30 | return { 31 | title: '', 32 | icon: null, 33 | order: 0, 34 | id: '__root', 35 | children: [], 36 | }; 37 | }; 38 | 39 | const calculateTreeDataForData = (contentData) => { 40 | let navigationItems = contentData 41 | .map((data) => data.node) 42 | .map((node) => { 43 | let parts = node.fields.slug.substr(1).split('/'); 44 | const label = parts.join(''); 45 | parts = parts.splice(0, parts.length - 1); 46 | let parents = []; 47 | parts.forEach((part, index) => { 48 | let v = '/' + part; 49 | if (parents[index - 1]) { 50 | v = parents[index - 1] + v; 51 | } 52 | parents.push(v); 53 | }); 54 | return { 55 | parent: parents.reverse(), 56 | label: label, 57 | url: node.fields.slug, 58 | children: [], 59 | title: node.fields.title, 60 | order: node.frontmatter.order, 61 | }; 62 | }); 63 | 64 | navigationItems.sort(function (a, b) { 65 | let aIdx = config.sidebar.forcedNavOrder.indexOf(a.url); 66 | let bIdx = config.sidebar.forcedNavOrder.indexOf(b.url); 67 | const forcedOrder = aIdx - bIdx; 68 | if (forcedOrder !== 0) { 69 | return forcedOrder; 70 | } 71 | const frontOrder = a.order - b.order; 72 | return frontOrder !== 0 ? frontOrder : a.label.localeCompare(b.label); 73 | }); 74 | 75 | let result = { 76 | __root: createUnassignedGroup(), 77 | }; 78 | navigationItems.forEach((data) => { 79 | let isChild = false; 80 | let parent = null; 81 | data.parent.every((p) => { 82 | parent = navigationItems.find((d) => d.url === p); 83 | if (parent) { 84 | parent.children.push(data); 85 | isChild = true; 86 | data.parent = parent.url; 87 | return false; 88 | } 89 | return true; 90 | }); 91 | if (parent) { 92 | data.parent = parent.title; 93 | } else { 94 | data.parent = null; 95 | } 96 | if (!isChild) { 97 | // assume first level of navigation entry URL may be ID (path) of a group 98 | let group = result[data.url.split('/')[1].toLowerCase()]; 99 | if (group == null) { 100 | group = group ? group : getGroup(data.url); 101 | if (!group) { 102 | group = result['__root']; 103 | } else { 104 | group = { 105 | title: group ? group.title : '', 106 | icon: group ? group.icon : null, 107 | order: group ? group.order : 0, 108 | // assume group have 1 level, e.g. /config 109 | id: group ? group.path.replace(/^\//, '').toLowerCase() : null, 110 | children: [], 111 | }; 112 | result[group.id] = group; 113 | } 114 | } 115 | data.groupName = group.title; 116 | data.groupIcon = group.icon; 117 | data.children.forEach((child) => { 118 | child.groupName = group.title; 119 | child.groupIcon = group.icon; 120 | }); 121 | group.children.push(data); 122 | } 123 | }); 124 | result = Object.values(result); 125 | result.sort(function (a, b) { 126 | const ordered = a.order - b.order; 127 | return ordered !== 0 ? ordered : a.title.localeCompare(b.title); 128 | }); 129 | return result; 130 | }; 131 | 132 | const calculateNavigation = (edges) => { 133 | const contentData = config.sidebar.ignoreIndex 134 | ? edges.filter( 135 | ({ 136 | node: { 137 | fields: { slug }, 138 | }, 139 | }) => slug !== '/' 140 | ) 141 | : edges; 142 | const data = calculateTreeDataForData(contentData); 143 | return { 144 | children: data, 145 | }; 146 | }; 147 | 148 | const flat = (parent, acc) => { 149 | parent.children.forEach(child => { 150 | acc.push(child); 151 | flat(child, acc); 152 | }) 153 | } 154 | 155 | const calculateFlatNavigation = (edges) => { 156 | const navigation = calculateNavigation(edges); 157 | const acc = []; 158 | navigation.children.forEach(group => { 159 | flat(group, acc) 160 | }) 161 | return acc;; 162 | }; 163 | 164 | export { getNavigationData, calculateNavigation, calculateFlatNavigation }; 165 | --------------------------------------------------------------------------------