├── .gitignore ├── LICENSE ├── README.md ├── dochameleon-init ├── README.md ├── initialize.js ├── package-lock.json └── package.json ├── docs ├── README.md ├── assets │ └── img │ │ └── progressive.png ├── blog │ ├── 2018 │ │ ├── 01 │ │ │ ├── 10 │ │ │ │ └── staging-step.html │ │ │ └── 08 │ │ │ │ └── why-dochameleon.html │ │ ├── 02 │ │ │ └── 02 │ │ │ │ └── dochameleon-io-live.html │ │ └── 03 │ │ │ └── 09 │ │ │ └── everybody-should-have-an-website.html │ ├── atom.xml │ ├── feed.xml │ ├── index.html │ └── page1 │ │ └── index.html ├── docs │ ├── assets │ │ └── img │ │ │ └── progressive.png │ ├── guide_analytics.html │ ├── guide_color_scheme.html │ ├── guide_configuration.html │ ├── guide_core.html │ ├── guide_github.html │ ├── guide_i18n.html │ ├── guide_installation.html │ ├── guide_progressive.html │ ├── guide_react.html │ ├── guide_search.html │ ├── guide_site_creation.html │ ├── guide_site_publishing.html │ └── guide_theme.html ├── en │ ├── README.md │ ├── blog │ │ ├── 2018 │ │ │ ├── 01 │ │ │ │ ├── 10 │ │ │ │ │ └── staging-step.html │ │ │ │ └── 08 │ │ │ │ │ └── why-dochameleon.html │ │ │ ├── 02 │ │ │ │ └── 02 │ │ │ │ │ └── dochameleon-io-live.html │ │ │ └── 03 │ │ │ │ └── 09 │ │ │ │ └── everybody-should-have-an-website.html │ │ ├── index.html │ │ └── page1 │ │ │ └── index.html │ ├── docs │ │ ├── assets │ │ │ └── img │ │ │ │ └── progressive.png │ │ ├── guide_analytics.html │ │ ├── guide_color_scheme.html │ │ ├── guide_configuration.html │ │ ├── guide_core.html │ │ ├── guide_github.html │ │ ├── guide_i18n.html │ │ ├── guide_installation.html │ │ ├── guide_progressive.html │ │ ├── guide_react.html │ │ ├── guide_search.html │ │ ├── guide_site_creation.html │ │ ├── guide_site_publishing.html │ │ └── guide_theme.html │ ├── help.html │ ├── index.html │ ├── languages.html │ ├── readme.html │ └── users.html ├── help.html ├── img │ ├── algolia.svg │ ├── dochameleon.png │ ├── f-r.png │ ├── favicon.ico │ ├── favicon.png │ ├── fsts.png │ ├── github.png │ ├── github_pages.png │ ├── language.svg │ ├── markdown.png │ ├── octocat.jpg │ ├── progressive.png │ ├── react.svg │ ├── setup.png │ └── translation.svg ├── index.html ├── languages.html ├── readme.html ├── sitemap.xml ├── users.html └── zh │ ├── README.md │ ├── blog │ ├── 2018 │ │ ├── 01 │ │ │ ├── 10 │ │ │ │ └── staging-step.html │ │ │ └── 08 │ │ │ │ └── why-dochameleon.html │ │ ├── 02 │ │ │ └── 02 │ │ │ │ └── dochameleon-io-live.html │ │ └── 03 │ │ │ └── 09 │ │ │ └── everybody-should-have-an-website.html │ ├── index.html │ └── page1 │ │ └── index.html │ ├── docs │ ├── assets │ │ └── img │ │ │ └── progressive.png │ ├── guide_analytics.html │ ├── guide_color_scheme.html │ ├── guide_configuration.html │ ├── guide_core.html │ ├── guide_github.html │ ├── guide_i18n.html │ ├── guide_installation.html │ ├── guide_progressive.html │ ├── guide_react.html │ ├── guide_search.html │ ├── guide_site_creation.html │ ├── guide_site_publishing.html │ └── guide_theme.html │ ├── help.html │ ├── index.html │ ├── languages.html │ ├── readme.html │ └── users.html ├── examples └── basics │ ├── blog │ ├── 2018-01-08-why-dochameleon.md │ └── 2018-01-10-staging-step.md │ ├── components │ ├── Callout.js │ ├── Features.js │ ├── Footer.js │ ├── HelpDetails.js │ ├── HomeSplash.js │ ├── Showcase.js │ ├── callouts.json │ ├── features.json │ ├── headerLinks.json │ ├── help.json │ └── users.json │ ├── docs │ ├── guide_configuration.md │ ├── guide_customize_color.md │ ├── guide_customize_core.md │ ├── guide_customize_progressive.md │ ├── guide_customize_react.md │ ├── guide_customize_theme.md │ ├── guide_installation.md │ ├── guide_search.md │ ├── guide_site_creation.md │ ├── guide_site_publishing.md │ └── sidebars.json │ ├── gitignore │ ├── pages │ ├── help.js │ ├── index.js │ ├── languages.js │ └── users.js │ ├── siteConfig.js │ ├── static │ └── img │ │ ├── f-r.png │ │ └── fsts.png │ └── theme │ └── pages.js ├── lib ├── build-files.js ├── copy-examples.js ├── core │ ├── Analytics.js │ ├── Assets.js │ ├── Blog.js │ ├── Docs.js │ ├── Extractor.js │ ├── I18n.js │ ├── Pages.js │ ├── Parser.js │ ├── Search.js │ ├── components │ │ ├── AnalyticsScript.js │ │ ├── Breadcrumb.js │ │ ├── Button.js │ │ ├── CollapseIcon.js │ │ ├── Footer.js │ │ ├── Head.js │ │ ├── HeaderNav.js │ │ ├── HeaderNavItem.js │ │ ├── MarkdownBlock.js │ │ ├── Page.js │ │ ├── README.md │ │ ├── SearchBox.js │ │ ├── SideNav.js │ │ ├── SideNavCategory.js │ │ ├── SideNavItem.js │ │ ├── blog │ │ │ ├── BlogPageLayout.js │ │ │ ├── BlogPost.js │ │ │ ├── BlogPostLayout.js │ │ │ ├── BlogSidebar.js │ │ │ └── social.js │ │ ├── docs │ │ │ ├── Doc.js │ │ │ ├── DocsLayout.js │ │ │ └── DocsSidebar.js │ │ ├── headerLinks.json │ │ └── toSlug.js │ ├── dfs.js │ ├── generate.js │ ├── pages │ │ ├── README.md │ │ └── readme.js │ ├── server.js │ ├── site.js │ └── theme │ │ ├── blog.js │ │ ├── color.js │ │ ├── main.js │ │ ├── markdown.js │ │ ├── reset.js │ │ └── search.js ├── publish-gh-pages.js ├── start-server.js └── static │ └── img │ ├── algolia.svg │ ├── dochameleon.png │ ├── favicon.ico │ ├── favicon.png │ ├── github.png │ ├── language.svg │ ├── markdown.png │ ├── octocat.jpg │ ├── progressive.png │ ├── react.svg │ ├── setup.png │ └── translation.svg ├── package-lock.json ├── package.json └── website ├── .gitignore ├── analyticsConfig.js ├── blog ├── 2018-01-08-why-dochameleon.md ├── 2018-01-10-staging-step.md ├── 2018-02-02-dochameleon-io-live.md └── 2018-03-09-everybody-should-have-an-website.md ├── components ├── Callout.js ├── Features.js ├── Footer.js ├── HelpDetails.js ├── HomeSplash.js ├── Showcase.js ├── callouts.json ├── features.json ├── headerLinks.json ├── help.json └── users.json ├── docs ├── guide_analytics.md ├── guide_configuration.md ├── guide_customize_color.md ├── guide_customize_core.md ├── guide_customize_progressive.md ├── guide_customize_react.md ├── guide_customize_theme.md ├── guide_github.md ├── guide_i18n.md ├── guide_installation.md ├── guide_search.md ├── guide_site_creation.md ├── guide_site_publishing.md └── sidebars.json ├── i18n └── zh │ ├── blog.js │ ├── docs.js │ ├── docs │ └── guide_installation.md │ ├── features.js │ ├── langConfig.js │ ├── languages.js │ ├── menu.js │ ├── project.js │ └── translate.js ├── package.json ├── pages ├── help.js ├── index.js ├── languages.js └── users.js ├── siteConfig.js ├── static └── img │ ├── f-r.png │ ├── fsts.png │ └── github_pages.png └── theme └── pages.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | website/build/ 5 | website/node_modules 6 | website/package-lock.json 7 | website/yarn.lock 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Richard Zhang 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dochameleon 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | [![npm version](https://badge.fury.io/js/dochameleon.svg)](https://badge.fury.io/js/dochameleon) 5 | [![npm downloads](https://img.shields.io/npm/dm/dochameleon.svg)](https://www.npmjs.com/package/dochameleon) 6 | [![GitHub last commit](https://img.shields.io/github/last-commit/richardzcode/Dochameleon.svg)]() 7 | 8 | A Progressive Static Website Generator, built for Open Source documentation. 9 | 10 | Start your website in less than 1 minute. 11 | 12 | ```bash 13 | > npm install -g dochameleon-init 14 | > dochameleon-init 15 | > cd website 16 | > npm run start 17 | ``` 18 | 19 | Or, [install without script](https://dochameleon.io/docs/guide_installation.html#manual-installation) 20 | 21 | Progressive Customization 22 | 23 | 24 | 25 | 1. [Just write markdown for documentation and blogging](http://dochameleon.io/docs/guide_site_creation.html) 26 | 2. [Change color scheme](http://dochameleon.io/docs/guide_color_scheme.html) 27 | 3. [Customize styling theme](http://dochameleon.io/docs/guide_theme.html) 28 | 4. [Write your own page](http://dochameleon.io/docs/guide_react.html) 29 | 5. [Replace core components](http://dochameleon.io/docs/guide_core.html) 30 | 31 | Check [Dochameleon Site](http://dochameleon.io/) for its own documentation. 32 | -------------------------------------------------------------------------------- /dochameleon-init/README.md: -------------------------------------------------------------------------------- 1 | ## `dochameleon-init` 2 | 3 | An initialization script for [Dochameleon](https://dochameleon.io). 4 | 5 | ### What does it do? 6 | 7 | Dochameleon was a fork from Docusausurs 8 | 9 | 1. Go into the root of your GitHub repo directory where you will be creating the docs. 10 | 1. `yarn global add dochameleon-init` or `npm install --global dochameleon-init` 11 | 1. `dochameleon-init` 12 | 13 | Find out more in the [official docs](https://dochameleon.io/docs/guide_installation.html). 14 | -------------------------------------------------------------------------------- /dochameleon-init/initialize.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const shell = require("shelljs"); 4 | const chalk = require("chalk"); 5 | const fs = require("fs"); 6 | const path = require("path"); 7 | 8 | const CWD = process.cwd(); 9 | const join = path.join; 10 | 11 | const log = console.log; 12 | const error = console.error; 13 | 14 | let useYarn = !!(shell.which("yarn")); 15 | 16 | let file = join(CWD, "website"); 17 | if (fs.existsSync(file)) { 18 | error(chalk.yellow("/website folder already exists.\n")); 19 | log( 20 | "Dochameleon setup at 'website' folder. You will need to remove existing 'website' folder from root directory to let Dochameleon do its work." 21 | ); 22 | process.exit(1); 23 | } 24 | 25 | shell.cd(CWD); 26 | shell.mkdir("website"); 27 | log(chalk.green("Website folder created!\n")); 28 | 29 | shell.cd("website"); 30 | log(chalk.yellow("Installing latest version of Dochameleon in website.\n")); 31 | 32 | const content = { scripts: { examples: "dochameleon-examples" } }; 33 | fs.writeFileSync( 34 | CWD + "/website/package.json", 35 | JSON.stringify(content, null, 2) + "\n" 36 | ); 37 | 38 | shell.exec( 39 | useYarn 40 | ? "yarn add dochameleon --dev" 41 | : "npm install dochameleon --save-dev" 42 | ); 43 | log(chalk.green("Dochameleon installed in website folder!\n")); 44 | 45 | shell.exec( 46 | useYarn 47 | ? "yarn run examples" 48 | : "npm run examples" 49 | ); 50 | -------------------------------------------------------------------------------- /dochameleon-init/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dochameleon-init", 3 | "description": "Initialization script for Dochameleon", 4 | "version": "0.0.1", 5 | "license": "MIT", 6 | "preferGlobal": true, 7 | "keywords": [ 8 | "documentation", 9 | "websites", 10 | "open source", 11 | "docusaurus", 12 | "dochameleon" 13 | ], 14 | "bin": { 15 | "dochameleon-init": "initialize.js" 16 | }, 17 | "dependencies": { 18 | "chalk": "^2.1.0", 19 | "inquirer": "^5.1.0", 20 | "shelljs": "^0.7.8" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Dochameleon 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | [![npm version](https://badge.fury.io/js/dochameleon.svg)](https://badge.fury.io/js/dochameleon) 5 | [![npm downloads](https://img.shields.io/npm/dm/dochameleon.svg)](https://www.npmjs.com/package/dochameleon) 6 | [![GitHub last commit](https://img.shields.io/github/last-commit/richardzcode/Dochameleon.svg)]() 7 | 8 | A Progressive Static Website Generator, built for Open Source documentation. 9 | 10 | Start your website in less than 1 minute. 11 | 12 | ```bash 13 | > npm install -g dochameleon-init 14 | > dochameleon-init 15 | > cd website 16 | > npm run start 17 | ``` 18 | 19 | Or, [install without script](https://dochameleon.io/docs/guide_installation.html#manual-installation) 20 | 21 | Progressive Customization 22 | 23 | 24 | 25 | 1. [Just write markdown for documentation and blogging](http://dochameleon.io/docs/guide_site_creation.html) 26 | 2. [Change color scheme](http://dochameleon.io/docs/guide_color_scheme.html) 27 | 3. [Customize styling theme](http://dochameleon.io/docs/guide_theme.html) 28 | 4. [Write your own page](http://dochameleon.io/docs/guide_react.html) 29 | 5. [Replace core components](http://dochameleon.io/docs/guide_core.html) 30 | 31 | Check [Dochameleon Site](http://dochameleon.io/) for its own documentation. 32 | -------------------------------------------------------------------------------- /docs/assets/img/progressive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/docs/assets/img/progressive.png -------------------------------------------------------------------------------- /docs/blog/atom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://richardzcode.github.io/blog 4 | Dochameleon Blog 5 | 2018-03-09T14:00:00Z 6 | Feed for Node.js 7 | 8 | The best place to stay up-to-date with the latest Dochameleon news and events. 9 | Copyright © 2018 Richard Zhang 10 | 11 | <![CDATA[Everybody Should Have an Website]]> 12 | https://richardzcode.github.io/blog/2018/03/09/everybody-should-have-an-website.html 13 | 14 | 15 | 2018-03-09T14:00:00Z 16 | 19 | 20 | Richard Zhang 21 | 22 | 23 | 24 | <![CDATA[Dochameleon.io is Live]]> 25 | https://richardzcode.github.io/blog/2018/02/02/dochameleon-io-live.html 26 | 27 | 28 | 2018-02-02T14:00:00Z 29 | 34 | 35 | Richard Zhang 36 | 37 | 38 | 39 | <![CDATA[Staging Step]]> 40 | https://richardzcode.github.io/blog/2018/01/10/staging-step.html 41 | 42 | 43 | 2018-01-10T14:00:00Z 44 | 47 | 48 | Richard Zhang 49 | 50 | 51 | 52 | <![CDATA[Why Dochameleon]]> 53 | https://richardzcode.github.io/blog/2018/01/08/why-dochameleon.html 54 | 55 | 56 | 2018-01-08T14:00:00Z 57 | 62 | 63 | Richard Zhang 64 | 65 | 66 | -------------------------------------------------------------------------------- /docs/blog/feed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dochameleon Blog 5 | https://richardzcode.github.io/blog 6 | The best place to stay up-to-date with the latest Dochameleon news and events. 7 | Fri, 09 Mar 2018 14:00:00 GMT 8 | http://blogs.law.harvard.edu/tech/rss 9 | Feed for Node.js 10 | Copyright © 2018 Richard Zhang 11 | 12 | <![CDATA[Everybody Should Have an Website]]> 13 | https://richardzcode.github.io/blog/2018/03/09/everybody-should-have-an-website.html 14 | https://richardzcode.github.io/blog/2018/03/09/everybody-should-have-an-website.html 15 | Fri, 09 Mar 2018 14:00:00 GMT 16 | 19 | 20 | 21 | <![CDATA[Dochameleon.io is Live]]> 22 | https://richardzcode.github.io/blog/2018/02/02/dochameleon-io-live.html 23 | https://richardzcode.github.io/blog/2018/02/02/dochameleon-io-live.html 24 | Fri, 02 Feb 2018 14:00:00 GMT 25 | 30 | 31 | 32 | <![CDATA[Staging Step]]> 33 | https://richardzcode.github.io/blog/2018/01/10/staging-step.html 34 | https://richardzcode.github.io/blog/2018/01/10/staging-step.html 35 | Wed, 10 Jan 2018 14:00:00 GMT 36 | 39 | 40 | 41 | <![CDATA[Why Dochameleon]]> 42 | https://richardzcode.github.io/blog/2018/01/08/why-dochameleon.html 43 | https://richardzcode.github.io/blog/2018/01/08/why-dochameleon.html 44 | Mon, 08 Jan 2018 14:00:00 GMT 45 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /docs/docs/assets/img/progressive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/docs/docs/assets/img/progressive.png -------------------------------------------------------------------------------- /docs/en/README.md: -------------------------------------------------------------------------------- 1 | # Dochameleon 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | [![npm version](https://badge.fury.io/js/dochameleon.svg)](https://badge.fury.io/js/dochameleon) 5 | [![npm downloads](https://img.shields.io/npm/dm/dochameleon.svg)](https://www.npmjs.com/package/dochameleon) 6 | [![GitHub last commit](https://img.shields.io/github/last-commit/richardzcode/Dochameleon.svg)]() 7 | 8 | A Progressive Static Website Generator, built for Open Source documentation. 9 | 10 | Start your website in less than 1 minute. 11 | 12 | ```bash 13 | > npm install -g dochameleon-init 14 | > dochameleon-init 15 | > cd website 16 | > npm run start 17 | ``` 18 | 19 | Or, [install without script](https://dochameleon.io/docs/guide_installation.html#manual-installation) 20 | 21 | Progressive Customization 22 | 23 | 24 | 25 | 1. [Just write markdown for documentation and blogging](http://dochameleon.io/docs/guide_site_creation.html) 26 | 2. [Change color scheme](http://dochameleon.io/docs/guide_color_scheme.html) 27 | 3. [Customize styling theme](http://dochameleon.io/docs/guide_theme.html) 28 | 4. [Write your own page](http://dochameleon.io/docs/guide_react.html) 29 | 5. [Replace core components](http://dochameleon.io/docs/guide_core.html) 30 | 31 | Check [Dochameleon Site](http://dochameleon.io/) for its own documentation. 32 | -------------------------------------------------------------------------------- /docs/en/docs/assets/img/progressive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/docs/en/docs/assets/img/progressive.png -------------------------------------------------------------------------------- /docs/img/algolia.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/img/dochameleon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/docs/img/dochameleon.png -------------------------------------------------------------------------------- /docs/img/f-r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/docs/img/f-r.png -------------------------------------------------------------------------------- /docs/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/docs/img/favicon.ico -------------------------------------------------------------------------------- /docs/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/docs/img/favicon.png -------------------------------------------------------------------------------- /docs/img/fsts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/docs/img/fsts.png -------------------------------------------------------------------------------- /docs/img/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/docs/img/github.png -------------------------------------------------------------------------------- /docs/img/github_pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/docs/img/github_pages.png -------------------------------------------------------------------------------- /docs/img/language.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/img/markdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/docs/img/markdown.png -------------------------------------------------------------------------------- /docs/img/octocat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/docs/img/octocat.jpg -------------------------------------------------------------------------------- /docs/img/progressive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/docs/img/progressive.png -------------------------------------------------------------------------------- /docs/img/react.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /docs/img/setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/docs/img/setup.png -------------------------------------------------------------------------------- /docs/img/translation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://richardzcode.github.io/help.html weekly 0.5 4 | https://richardzcode.github.io/index.html weekly 0.5 5 | https://richardzcode.github.io/languages.html weekly 0.5 6 | https://richardzcode.github.io/readme.html weekly 0.5 7 | https://richardzcode.github.io/README.md weekly 0.5 8 | https://richardzcode.github.io/users.html weekly 0.5 9 | https://richardzcode.github.io/blog/2018/03/09/everybody-should-have-an-website.html weekly 0.3 10 | https://richardzcode.github.io/blog/2018/02/02/dochameleon-io-live.html weekly 0.3 11 | https://richardzcode.github.io/blog/2018/01/10/staging-step.html weekly 0.3 12 | https://richardzcode.github.io/blog/2018/01/08/why-dochameleon.html weekly 0.3 13 | https://richardzcode.github.io/docs/guide_analytics.html weekly 0.3 14 | https://richardzcode.github.io/docs/guide_configuration.html weekly 0.3 15 | https://richardzcode.github.io/docs/guide_color_scheme.html weekly 0.3 16 | https://richardzcode.github.io/docs/guide_core.html weekly 0.3 17 | https://richardzcode.github.io/docs/guide_progressive.html weekly 0.3 18 | https://richardzcode.github.io/docs/guide_react.html weekly 0.3 19 | https://richardzcode.github.io/docs/guide_theme.html weekly 0.3 20 | https://richardzcode.github.io/docs/guide_github.html weekly 0.3 21 | https://richardzcode.github.io/docs/guide_i18n.html weekly 0.3 22 | https://richardzcode.github.io/docs/guide_installation.html weekly 0.3 23 | https://richardzcode.github.io/docs/guide_search.html weekly 0.3 24 | https://richardzcode.github.io/docs/guide_site_creation.html weekly 0.3 25 | https://richardzcode.github.io/docs/guide_site_publishing.html weekly 0.3 26 | -------------------------------------------------------------------------------- /docs/zh/README.md: -------------------------------------------------------------------------------- 1 | # Dochameleon 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | [![npm version](https://badge.fury.io/js/dochameleon.svg)](https://badge.fury.io/js/dochameleon) 5 | [![npm downloads](https://img.shields.io/npm/dm/dochameleon.svg)](https://www.npmjs.com/package/dochameleon) 6 | [![GitHub last commit](https://img.shields.io/github/last-commit/richardzcode/Dochameleon.svg)]() 7 | 8 | A Progressive Static Website Generator, built for Open Source documentation. 9 | 10 | Start your website in less than 1 minute. 11 | 12 | ```bash 13 | > npm install -g dochameleon-init 14 | > dochameleon-init 15 | > cd website 16 | > npm run start 17 | ``` 18 | 19 | Or, [install without script](https://dochameleon.io/docs/guide_installation.html#manual-installation) 20 | 21 | Progressive Customization 22 | 23 | 24 | 25 | 1. [Just write markdown for documentation and blogging](http://dochameleon.io/docs/guide_site_creation.html) 26 | 2. [Change color scheme](http://dochameleon.io/docs/guide_color_scheme.html) 27 | 3. [Customize styling theme](http://dochameleon.io/docs/guide_theme.html) 28 | 4. [Write your own page](http://dochameleon.io/docs/guide_react.html) 29 | 5. [Replace core components](http://dochameleon.io/docs/guide_core.html) 30 | 31 | Check [Dochameleon Site](http://dochameleon.io/) for its own documentation. 32 | -------------------------------------------------------------------------------- /docs/zh/docs/assets/img/progressive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/docs/zh/docs/assets/img/progressive.png -------------------------------------------------------------------------------- /examples/basics/blog/2018-01-08-why-dochameleon.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Why Dochameleon 3 | author: Richard Zhang 4 | authorGitHub: richardzcode 5 | --- 6 | 7 | It is not hard to guess where the idea of Dochameleon is from. 8 | 9 | With [Docusaurus](https://github.com/facebook/Docusaurus) built and maintained by awesome team from Facebook, why do I want to build another one? 10 | 11 | One personal reason is I am so excited about Docusaurus. Can't stop checking around, rewrite, restruct source code. So only way to satisfy is to create a new project. 12 | 13 | 14 | 15 | Here are some technical reasons, 16 | 17 | 1. Dev server and site generation are written separately. Inconsistency is inevitable. 18 | 2. Pages can not share building blocks. 19 | 3. Big CSS file makes styling hard. 20 | 21 | At the same time some features are removed. I feel they are a bit too opinionated with complexity, may not suited for all open source projects. 22 | 23 | 1. Search with [algolia](https://www.algolia.com/). 24 | 2. Multi-Language with [crowdin](https://crowdin.com/). 25 | 3. Project version support. 26 | 27 | For example, at the end of the day multi-language and versioning are grouped by file hierarchies. One options is to just take pull requests on GitHub. 28 | -------------------------------------------------------------------------------- /examples/basics/blog/2018-01-10-staging-step.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Staging Step 3 | author: Richard Zhang 4 | authorGitHub: richardzcode 5 | --- 6 | 7 | The basic idea of Dochameleon is to generate a static web server by using [Server-Side-Rendering](https://reactjs.org/docs/react-dom-server.html). Contents are combined from core library and specific project. 8 | 9 | Combining from two sources can be tricky. So there is a staging step. Which in essence copies files from the two sources into one place, for SSR to generate from, and for developers to inspect on. 10 | 11 | The one place can be found at `node_modules/dochameleon/lib/stage`, contains a file structure like this: 12 | 13 | ```bash 14 | node_modules/dochameleon/lib/stage/ 15 | ├── Blog.js 16 | ├── Docs.js 17 | ├── Pages.js 18 | ├── blog 19 | │   └── 2018-01-08-why-dochameleon.md 20 | │   └── 2018-01-10-stage-step.md 21 | ├── components 22 | │   ├── Button.js 23 | │   ├── CollapseIcon.js 24 | │   ├── FeatureCallout.js 25 | │   ├── FeatureCallouts.js 26 | │   ├── Features.js 27 | │   ├── Footer.js 28 | │   ├── Head.js 29 | │   ├── HeaderNav.js 30 | │   ├── HelpDetails.js 31 | │   ├── HomeSplash.js 32 | │   ├── MarkdownBlock.js 33 | │   ├── Page.js 34 | │   ├── Showcase.js 35 | │   ├── SideNav.js 36 | │   ├── blog 37 | │   ├── docs 38 | │   ├── featureCallouts.json 39 | │   ├── features.json 40 | │   ├── help.json 41 | │   └── users.json 42 | ├── dfs.js 43 | ├── docs 44 | │   ├── doc1.md 45 | │   ├── doc2.md 46 | │   ├── doc3.md 47 | │   ├── exampledoc4.md 48 | │   ├── exampledoc5.md 49 | │   └── sidebars.json 50 | ├── pages 51 | │   ├── help.js 52 | │   ├── index.js 53 | │   └── users.js 54 | ├── parse 55 | │   ├── Markdown.js 56 | │   └── toSlug.js 57 | ├── static 58 | │   ├── css 59 | │   └── img 60 | └── theme 61 | ├── blog.js 62 | ├── main.js 63 | ├── markdown.js 64 | └── pages.js 65 | ``` 66 | -------------------------------------------------------------------------------- /examples/basics/components/Callout.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { Div, A, Img, Row, Col } = require('fluid-react'); 3 | 4 | const MarkdownBlock = require('./MarkdownBlock'); 5 | 6 | class FeatureCallout extends React.Component { 7 | render() { 8 | const { site, lang, callout } = this.props; 9 | const { theme } = site; 10 | const title = site.i18n.translate(callout.title, lang); 11 | const content = callout['content.token'] 12 | ? [].concat(site.i18n.translate(callout['content.token'], lang, callout.content)).join('') 13 | : [].concat(callout.content).join(''); 14 | return ( 15 | 16 | {callout.imgFirst && ( 17 | 18 |
19 | 23 |
24 | 25 | )} 26 | 27 | {callout.doc 28 | ? {title} 32 | :
{title}
33 | } 34 | {content} 35 | 36 | {!callout.imgFirst && ( 37 | 38 |
39 | 40 |
41 | 42 | )} 43 |
44 | ) 45 | } 46 | } 47 | 48 | module.exports = FeatureCallout; 49 | -------------------------------------------------------------------------------- /examples/basics/components/Features.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { H3, Div, Img, Row, Col } = require('fluid-react'); 3 | 4 | const MarkdownBlock = require('./MarkdownBlock.js'); 5 | const features = require('./features.json'); 6 | 7 | const header = features.header 8 | ? [].concat(features.header).join('') 9 | : ''; 10 | const footer = features.footer 11 | ? [].concat(features.footer).join('') 12 | : ''; 13 | 14 | const Features = (props) => { 15 | const { site, lang } = props; 16 | const { theme } = site; 17 | const sectionComps = features.features.map((feature, idx) => { 18 | const content = feature['content.token'] 19 | ? [].concat(site.i18n.translate(feature['content.token'], lang, feature.content)).join('') 20 | : [].concat(feature.content).join(''); 21 | return ( 22 | 23 |
24 |
25 | 26 |
27 |

{site.i18n.translate(feature.title, lang)}

28 | {content} 29 |
30 | 31 | ) 32 | }); 33 | 34 | return ( 35 | 36 | {header && {header}} 37 | {sectionComps} 38 | {footer && {footer}} 39 | 40 | ) 41 | } 42 | 43 | module.exports = Features; 44 | -------------------------------------------------------------------------------- /examples/basics/components/Footer.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | class Footer extends React.Component { 4 | render() { 5 | const { site, lang } = this.props; 6 | const { theme } = site; 7 | const currentYear = new Date().getFullYear(); 8 | return ( 9 | 58 | ); 59 | } 60 | } 61 | 62 | module.exports = Footer; 63 | -------------------------------------------------------------------------------- /examples/basics/components/HelpDetails.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { H3, Div, Row, Col } = require('fluid-react'); 3 | 4 | const MarkdownBlock = require('./MarkdownBlock.js'); 5 | const help = require('./help.json'); 6 | 7 | const HelpDetails = (props) => { 8 | const { site, lang } = props; 9 | const { theme } = site; 10 | const sectionComps = help.map((help_item, idx) => { 11 | return ( 12 | 13 |
14 |

{help_item.title}

15 | {help_item.content} 16 |
17 | 18 | ) 19 | }); 20 | return {sectionComps} 21 | } 22 | 23 | module.exports = HelpDetails; 24 | -------------------------------------------------------------------------------- /examples/basics/components/HomeSplash.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { H2, Div } = require('fluid-react'); 3 | 4 | const Button = require('./Button.js'); 5 | 6 | class HomeSplash extends React.Component { 7 | render() { 8 | const { site, lang } = this.props; 9 | const tagline = site.i18n.translate('tagline', lang, site.config.tagline); 10 | return ( 11 |
12 |

13 | {site.config.title} 14 |
{tagline}
15 |

16 |
17 | 18 | 19 | 20 |
21 |
22 | ); 23 | } 24 | } 25 | 26 | module.exports = HomeSplash; 27 | -------------------------------------------------------------------------------- /examples/basics/components/Showcase.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { H3, H5, Div, P, Row, Col } = require('fluid-react'); 3 | 4 | const users = require('./users.json'); 5 | 6 | const Showcase = props => { 7 | if (users.length === 0) { return null; } 8 | 9 | const { site, lang } = props; 10 | const { theme } = site; 11 | const userComps = users 12 | .filter(user => !props.pinned || user.pinned) 13 | .map((user, i) => { 14 | let img_src = user.img; 15 | if (!img_src.startsWith('http')) { img_src = site.url(img_src); } 16 | return ( 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | }); 24 | 25 | return ( 26 |
27 |

28 | {site.i18n.translate('Who\'s Using This?', lang)}

29 |
30 | {site.i18n.translate('This project is used by all these people', lang)} 31 |
32 | 33 | {userComps} 34 | 35 |
36 | ); 37 | }; 38 | 39 | module.exports = Showcase; 40 | -------------------------------------------------------------------------------- /examples/basics/components/callouts.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Quick Setup", 4 | "doc": "guide_installation", 5 | "content.token": "quick_setup.content", 6 | "content": [ 7 | "Get up and running quickly. Have your own website in just a few minutes", 8 | "\n", 9 | "\n```bash", 10 | "\n> npm install -g dochameleon-init", 11 | "\n> dochameleon-init", 12 | "\n> cd website", 13 | "\n> npm run start", 14 | "\n```" 15 | ], 16 | "img": "img/setup.png", 17 | "imgFirst": false 18 | }, 19 | { 20 | "title": "Develop and Deploy", 21 | "doc": "guide_site_publishing", 22 | "content.token": "develop_deploy.content", 23 | "content": [ 24 | "Develop using included live server; ", 25 | "Deploy to GitHub or any static file web hosts" 26 | ], 27 | "img": "img/octocat.jpg", 28 | "imgStyle": { "maxWidth": "50%" }, 29 | "imgFirst": true 30 | } 31 | ] 32 | -------------------------------------------------------------------------------- /examples/basics/components/features.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": "", 3 | "footer": "", 4 | "features": [ 5 | { 6 | "title": "Powered by Markdown", 7 | "content.token": "markdown.content", 8 | "content": [ 9 | "Write docs and blog posts with ", 10 | "[Markdown](https://guides.github.com/features/mastering-markdown/). ", 11 | "Dochameleon converts them into HTML files." 12 | ], 13 | "img": "img/markdown.png" 14 | }, 15 | { 16 | "title": "Built Using React", 17 | "content.token": "react.content", 18 | "content": [ 19 | "Build [React](https://reactjs.org/) Components ", 20 | "to extend or customize your project." 21 | ], 22 | "img": "img/react.svg" 23 | }, 24 | { 25 | "title": "Progressive Customization", 26 | "content.token": "customization.content", 27 | "content": "Works out-of-box, able to customize to the extend of replacing core components", 28 | "img": "img/progressive.png" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /examples/basics/components/headerLinks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "type": "page", "value": "readme", "label": "README" }, 3 | { "type": "doc", "value": "guide_installation", "label": "Docs" }, 4 | { "type": "blog", "label": "Blog" }, 5 | { 6 | "type": "url", 7 | "value": "https://github.com/richardzcode/Dochameleon", 8 | "img": "img/github.png", 9 | "label": "GitHub" 10 | } 11 | ] 12 | -------------------------------------------------------------------------------- /examples/basics/components/help.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Browse Docs", 4 | "content": "Learn more using the documentation on this site." 5 | }, 6 | { 7 | "title": "Join the community", 8 | "content": "Ask questions about the documentation and project" 9 | }, 10 | { 11 | "title": "Stay up to date", 12 | "content": "Find out what's new with this project" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /examples/basics/components/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "fsts-js", 4 | "img": "img/fsts.png", 5 | "link": "https://richardzcode.github.io/fsts-js/index.html", 6 | "pinned": true 7 | }, 8 | { 9 | "caption": "Fluid React", 10 | "img": "img/f-r.png", 11 | "link": "https://richardzcode.github.io/fluid-react/index.html", 12 | "pinned": true 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /examples/basics/docs/guide_configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_configuration 3 | title: Site Configuration 4 | sidebar_label: Site Configuration 5 | --- 6 | 7 | ```bash 8 | website/ 9 | └── siteConfig.js 10 | ``` 11 | 12 | Site configurations are defined in `website/siteConfig.js`, here is an example: 13 | 14 | ``` 15 | const currentYear = new Date().getFullYear(); 16 | 17 | const siteConfig = { 18 | projectName: 'Dochameleon', 19 | title: 'Dochameleon', 20 | tagline: 'Open Source Documentation Site Generator', 21 | copyright: 'Copyright © ' + currentYear + ' Richard Zhang', 22 | 23 | rootUrl: 'https://richardzcode.github.io', 24 | baseUrl: '/Dochameleon', 25 | 26 | icon: 'img/dochameleon.png', 27 | favicon: 'img/favicon.png', 28 | 29 | css: [], 30 | js: [ 31 | 'https://buttons.github.io/buttons.js', 32 | ], 33 | 34 | headerLinks: [ 35 | {doc: 'guide_installation', label: 'Docs'}, 36 | {url: 'https://github.com/richardzcode/Dochameleon', label: 'GitHub'}, 37 | {page: 'help', label: 'Help'}, 38 | {blog: true, label: 'Blog'}, 39 | ] 40 | }; 41 | ``` 42 | 43 | Dochameleon itself does not have css file. Theming is through CSS-in-JS. Extra css and js files can be specified in siteConfig.js 44 | 45 | `headerLinks` defines what displayed on main menu. Menu items can be one of the four types: doc, blog, page, url 46 | -------------------------------------------------------------------------------- /examples/basics/docs/guide_customize_color.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_color_scheme 3 | title: Color Scheme 4 | sidebar_label: Color Scheme 5 | --- 6 | 7 | Changing colors probably is the simplest way to differenciate a website. 8 | 9 | Dochameleon does not have CSS file. All styles are defined in Javascript, which enables easy style customization. Changing color scheme is a perfect example. 10 | 11 | The core library has a [theme](https://github.com/richardzcode/Dochameleon/tree/master/lib/core/theme) folder which defines UI style. Custom website also have a `theme` folder. At runtime the two folder will be combined together, with the custom file replace core file if names are same. 12 | 13 | Copy [color.js](https://github.com/richardzcode/Dochameleon/tree/master/lib/core/theme/color.js) into website folder `/theme`, then change `primary` and `secondary` color. So it becomes: 14 | 15 | ``` 16 | const color = { 17 | primary: '#283e4a', 18 | secondary: '#337ab7', 19 | tertiary: '#e0e0e0', 20 | font: '#393939', 21 | fontSecondary: '#000' 22 | }; 23 | 24 | color.title = color.primary; 25 | color.content = color.font; 26 | color.contentSecondary = color.fontSecondary; 27 | color.clickable = color.primary; 28 | 29 | color.nav = { 30 | primary: color.primary, 31 | secondary: color.secondary, 32 | tertiary: color.tertiary, 33 | font: '#fff', 34 | fontSecondary: 'rgba(255, 255, 255, 0.8)', 35 | fontTertiary: 'rgba(255, 255, 255, 0.6)' 36 | }; 37 | 38 | color.footer = '#808080'; 39 | 40 | module.exports = color; 41 | ``` 42 | 43 | run dev server again, `npm run start`. See what happens. 44 | -------------------------------------------------------------------------------- /examples/basics/docs/guide_customize_core.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_core 3 | title: Core Component 4 | sidebar_label: Core Component 5 | --- 6 | 7 | Like mentioned in [Customization - React Component](./guide_react.html), Dochameleon merges custom and core components together at runtime. This makes further customization possible. 8 | 9 | Let's say you don't like sidebar at the left side for docs. Just copy `components/docs/DocsLayout.js` from core library to `website/components/docs`, then modify render function to switch the order of sidebar and content. 10 | 11 | ``` 12 | return ( 13 | 14 | 15 | 16 | {content} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | ``` 26 | 27 | Now `npm run start` see the sidebar is on the right side. 28 | -------------------------------------------------------------------------------- /examples/basics/docs/guide_customize_progressive.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_progressive 3 | title: Progressive 4 | sidebar_label: Progressive Customization 5 | --- 6 | 7 | Website created by Dochameleon works out-of-box. You can just write Markdown documents and blogs. 8 | 9 | Depend on needs, you can customize the website progressively, from simply changing color to completely replace the core component rendering. 10 | 11 | ![progressive](/static/img/progressive.png) 12 | -------------------------------------------------------------------------------- /examples/basics/docs/guide_customize_react.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_react 3 | title: React Component 4 | sidebar_label: React Component 5 | --- 6 | 7 | Write web pages in React, then Dochameleon will convert them into static HTML files. 8 | 9 | ### Pages 10 | 11 | Write web pages in `website/pages` folder. Each file exports an React component which will be rendered as one page. 12 | 13 | ### Components 14 | 15 | You may write custom React components in `website/components` folder, and use them in pages. 16 | 17 | Core library has React components too. At runtime, Dochameleon merges the two sets together. 18 | 19 | Take a look at example site `components` folder. It has number of components. Among them, `Footer.js` will overwrite the core Footer. Try remove it, and run `npm run start`, you'll notice default footer is different. 20 | -------------------------------------------------------------------------------- /examples/basics/docs/guide_customize_theme.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_theme 3 | title: UI Theme 4 | sidebar_label: UI Theme 5 | --- 6 | 7 | Like mentioned in [Customization - Color Scheme](./guide_color_scheme.html), Dochameleon defines UI style in `/theme` folder, in Javascript. 8 | 9 | ### Core and Custom Theme 10 | 11 | Core theme located at `lib/core/theme` folder of the core library. Custom theme located at `website/theme` folder. Each file exports an object that defines a set of UI styles. 12 | 13 | Two steps happening at runtime. 14 | 15 | First step, files in the two folder will be merged together. If custom theme file has same name as core theme file then it will overwrite the core file. 16 | 17 | Second step, Dochameleon reads all theme files and merge all objects into one final object. This is the theme object, contains defination of all UI styles. 18 | 19 | ### Customization 20 | 21 | Customization is simply write theme files in `website/theme` folder. 22 | 23 | Create a file `website/theme/custom.js` 24 | ``` 25 | const custom = { 26 | button: { 27 | margin: '4px', 28 | border: '1px solid blue', 29 | borderRadius: '3px', 30 | color: 'blue', 31 | display: 'inline-block', 32 | fontSize: '14px', 33 | fontWeight: '400', 34 | lineHeight: '1.2em', 35 | padding: '10px', 36 | textTransform: 'uppercase', 37 | textDecoration: 'none', 38 | transition: 'background 0.3s, color 0.3s' 39 | } 40 | } 41 | 42 | module.exports = custom; 43 | ``` 44 | 45 | run dev server again, `npm run start`. See how buttons look like now. 46 | -------------------------------------------------------------------------------- /examples/basics/docs/guide_installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_installation 3 | title: Installation 4 | sidebar_label: Installation 5 | --- 6 | 7 | 8 | 9 | ### Install via script 10 | 11 | The easiest way to install Dochameleon is by using script `dochameleon-init` 12 | 13 | First install the script 14 | 15 | ``` 16 | npm install --global dochameleon-init 17 | ``` 18 | 19 | Then, go to the folder that your'd like to create the documentation website, run 20 | 21 | ``` 22 | dochameleon-init 23 | cd website 24 | npm run start 25 | ``` 26 | 27 | The init script does three things: create `website` folder; npm install `dochameleon` package; copy a basic example website to start from. 28 | 29 | 30 | ### Manual installation 31 | 32 | If you do not want `dochameleon-init`, rather create website manually. 33 | 34 | - Create and go into the `website` folder 35 | - Create package.json with content, 36 | ``` 37 | { 38 | "scripts": { 39 | "examples": "dochameleon-examples" 40 | } 41 | } 42 | ``` 43 | - Install Dochameleon, run 44 | ``` 45 | npm install dochameleon 46 | ``` 47 | 48 | - Init with basic example, run 49 | ``` 50 | npm run examples 51 | ``` 52 | 53 | ### Run Local Dev Server 54 | 55 | Once installation completed, a website with example content is ready to go. Just run 56 | 57 | ``` 58 | npm run start 59 | ``` 60 | 61 | ### File Structure 62 | 63 | Once installation successful, here is the file structure you'll have under the `website` folder 64 | 65 | ```bash 66 | website/ 67 | ├── blog/ 68 | │   ├── 2018-01-08-why-dochameleon.md 69 | │   └── 2018-01-10-staging-step.md 70 | ├── components/ 71 | ├── docs/ 72 | │   ├── doc1.md 73 | │   ├── doc2.md 74 | │   ├── doc3.md 75 | │   └── sidebars.json 76 | ├── pages/ 77 | │   ├── help.js 78 | │   ├── index.js 79 | │   └── users.js 80 | ├── siteConfig.js 81 | ├── static/ 82 | └── theme/ 83 | ``` 84 | -------------------------------------------------------------------------------- /examples/basics/docs/guide_search.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_search 3 | title: Search 4 | sidebar_label: Search 5 | --- 6 | 7 | Dochameleon support search by [Algolia](https://www.algolia.com). Algolia is super easy to setup, and its community plan is free. 8 | 9 | ### Configure 10 | 11 | After setup Algolia for your project. Add `searchConfig.js` along with `siteConfig.js` 12 | 13 | ```bash 14 | website/ 15 | └── searchConfig.js 16 | ``` 17 | 18 | Content similiar to this: 19 | ``` 20 | const searchConfig = { 21 | application_id: ..., 22 | search_api_key: ..., 23 | api_key: ..., 24 | js: 'https://cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js' 25 | } 26 | 27 | module.exports = searchConfig; 28 | ``` 29 | 30 | Make sure searchConfig.js is in your `.gitignore`. You want to keep api_key safe. 31 | 32 | ### Add to Menu 33 | 34 | Modify `siteConfig.js`, add this entry to headerLinks: 35 | ``` 36 | {type: 'search'}, 37 | ``` 38 | 39 | ### Indexing 40 | 41 | Indexing happens at site generation time. When you run `npm run build`, the latest docs are pushed to Algolia. 42 | -------------------------------------------------------------------------------- /examples/basics/docs/guide_site_creation.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_site_creation 3 | title: Site Creation 4 | sidebar_label: Site Creation 5 | --- 6 | 7 | 8 | 9 | ### Docs 10 | 11 | ```bash 12 | website/ 13 | └── docs/ 14 |     ├── doc1.md 15 |    ├── doc2.md 16 |    ├── doc3.md 17 |    └── sidebars.json 18 | ``` 19 | Write documentation under `website/docs` folder, with Markdown. 20 | 21 | At beginning of each doc file, add metadata like this: 22 | 23 | ``` 24 | --- 25 | id: doc1 26 | title: Latin-ish 27 | sidebar_label: Example Page 28 | --- 29 | ``` 30 | 31 | #### Sidebar 32 | 33 | Sidebar menu is defined in `sidebars.json` 34 | 35 | ### Blog 36 | 37 | ```bash 38 | website/ 39 | └── blog/ 40 |    ├── 2018-01-08-why-dochameleon.md 41 |    └── 2018-01-10-staging-step.md 42 | ``` 43 | Write blog posts under `website/blog` folder, with Markdown. 44 | 45 | File name must have the format of `yyyy-mm-dd-blog-file-name.md` 46 | 47 | At begging of each blog post, add metadata like this: 48 | 49 | ``` 50 | --- 51 | title: Why Dochameleon 52 | author: Richard Zhang 53 | authorUrl: https://github.com/richardzcode 54 | authorImage: https://github.com/richardzcode.png 55 | --- 56 | ``` 57 | 58 | authorUrl and authorImage can be set by name/id of GitHub, Facebook, or Twitter, for example: 59 | 60 | ``` 61 | authorGitHub: richardzcode 62 | ``` 63 | 64 | or 65 | 66 | ``` 67 | authorFBID: ... 68 | ``` 69 | 70 | or 71 | 72 | ``` 73 | authorTwitter: ... 74 | ``` 75 | 76 | ### Pages 77 | 78 | ```bash 79 | website/ 80 | ├── components/ 81 | ├── pages/ 82 | │   ├── help.js 83 | │   ├── index.js 84 | │   └── users.js 85 | └── theme/ 86 | ``` 87 | Create web pages under `website/pages` folder, by writing React. 88 | 89 | Create components under `website/components`, for pages to import. 90 | 91 | Write theme files under `website/theme`, to define styles. 92 | 93 | ### Static Files 94 | 95 | ```bash 96 | website/ 97 | └── static/ 98 | ``` 99 | 100 | Put static files under `website/static` folder. They will go to `${rootUrl}${baseUrl}/` 101 | 102 | You may refer to static files in docs as `/static/...`, for example `[Logo]( /static/img/logo.png)` 103 | -------------------------------------------------------------------------------- /examples/basics/docs/guide_site_publishing.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_site_publishing 3 | title: Site Publishing 4 | sidebar_label: Site Publishing 5 | --- 6 | 7 | Dochameleon converts Markdown and React source files into static HTML files. Then you put them on any of the web hosting solution, GitHub, Amazon S3, etc. 8 | 9 | ### Build 10 | 11 | ``` 12 | npm run build 13 | ``` 14 | 15 | Then all HTML files will be generated into `website/build` folder. 16 | 17 | ### Hosting 18 | 19 | Now you may get all of the files inside `website/build` and copy over to web hosting. 20 | 21 | ### GitHub Pages 22 | 23 | [GitHub Pages](https://pages.github.com/) is a very natural hosting choice for open source projects. To publish to GitHub Pages, 24 | 25 | 1. Follow steps to create your GitHub Pages. Select "master branch /docs folder" as `Source` 26 | 27 | 2. Copy `website/build/{projectName}` folder to your project `/docs` folder. 28 | 3. Commit and push your git repo. 29 | 30 | That's it! 31 | -------------------------------------------------------------------------------- /examples/basics/docs/sidebars.json: -------------------------------------------------------------------------------- 1 | { 2 | "docs": { 3 | "Getting Started": [ 4 | "guide_installation", 5 | "guide_configuration", 6 | "guide_site_creation", 7 | "guide_site_publishing" 8 | ], 9 | "Customization": [ 10 | "guide_progressive", 11 | "guide_color_scheme", 12 | "guide_theme", 13 | "guide_react", 14 | "guide_core" 15 | ], 16 | "More": ["guide_search"] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/basics/gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | build/ 4 | yarn.lock 5 | **/node_modules 6 | searchConfig.js 7 | -------------------------------------------------------------------------------- /examples/basics/pages/help.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | const HelpDetails = require('../components/HelpDetails.js'); 4 | 5 | class Help extends React.Component { 6 | render() { 7 | const { site, lang } = this.props; 8 | const { theme } = site; 9 | return ( 10 |
11 |
12 |

Need help?

13 |

This project is maintained by a dedicated group of people.

14 |
15 | 16 |
17 | ); 18 | } 19 | } 20 | 21 | module.exports = Help; 22 | -------------------------------------------------------------------------------- /examples/basics/pages/index.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | const HomeSplash = require('../components/HomeSplash.js'); 4 | const Features = require('../components/Features.js'); 5 | const Callout = require('../components/Callout.js'); 6 | const Showcase = require('../components/Showcase.js'); 7 | 8 | const callouts = require('../components/callouts.json'); 9 | 10 | class Index extends React.Component { 11 | render() { 12 | const { site, lang } = this.props; 13 | const { theme } = site; 14 | return ( 15 |
16 | 17 |
18 |
19 | 20 |
21 |
22 | 23 |
24 |
25 | 26 |
27 |
28 | ); 29 | } 30 | } 31 | 32 | module.exports = Index; 33 | -------------------------------------------------------------------------------- /examples/basics/pages/languages.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { Row, Col } = require('fluid-react'); 3 | 4 | const MarkdownBlock = require('../components/MarkdownBlock.js'); 5 | 6 | const no_languages_content = ` 7 | To have multi-language, write translations under \`website/i18n/$lang_code$\` 8 | 9 | [Example](https://github.com/richardzcode/Dochameleon/tree/master/website/i18n) 10 | `; 11 | 12 | class Languages extends React.Component { 13 | render() { 14 | const { site, lang } = this.props; 15 | const { theme } = site; 16 | const langs = site.i18n.langs(); 17 | const languages = langs && langs.length > 0 18 | ? langs.map((language, i) => { 19 | return ( 20 | 21 | 22 | {site.i18n.translate(language, lang)} 23 | 24 | 25 | ); 26 | }) 27 | : {no_languages_content} 28 | 29 | return ( 30 |
31 |
32 | 33 | {languages} 34 | 35 |
36 |
37 | ); 38 | } 39 | } 40 | 41 | module.exports = Languages; 42 | -------------------------------------------------------------------------------- /examples/basics/pages/users.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { JS} = require('fsts'); 3 | 4 | const Showcase = require('../components/Showcase.js'); 5 | 6 | const user_json = 'https://github.com/richardzcode/Dochameleon/blob/master/website/components/users.json'; 7 | 8 | class Users extends React.Component { 9 | render() { 10 | const { site, lang } = this.props; 11 | const { theme } = site; 12 | 13 | const cta = site.i18n.translate('Create pull request to add your logo', lang); 14 | return ( 15 |
16 |
17 | 18 |
19 |
20 |
21 | ); 22 | } 23 | } 24 | 25 | module.exports = Users; 26 | -------------------------------------------------------------------------------- /examples/basics/siteConfig.js: -------------------------------------------------------------------------------- 1 | const currentYear = new Date().getFullYear(); 2 | 3 | const siteConfig = { 4 | projectName: 'test-site', 5 | title: 'Test Site', 6 | tagline: 'A website for testing', 7 | copyright: 'Copyright © ' + currentYear + ' Richard Zhang', 8 | 9 | rootUrl: 'https://richardzcode.github.io', 10 | baseUrl: '/test-site', 11 | 12 | icon: 'img/dochameleon.png', 13 | favicon: 'img/favicon.png', 14 | 15 | js: [ 16 | 'https://buttons.github.io/buttons.js' 17 | ] 18 | }; 19 | 20 | module.exports = siteConfig; 21 | -------------------------------------------------------------------------------- /examples/basics/static/img/f-r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/examples/basics/static/img/f-r.png -------------------------------------------------------------------------------- /examples/basics/static/img/fsts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/examples/basics/static/img/fsts.png -------------------------------------------------------------------------------- /examples/basics/theme/pages.js: -------------------------------------------------------------------------------- 1 | const color = require('./color.js'); 2 | 3 | const pages = { 4 | block: { 5 | padding: '30px 10px' 6 | }, 7 | blockEven: { 8 | padding: '30px 10px', 9 | background: '#e9e9e9' 10 | }, 11 | homeSplash: { 12 | padding: '2em 10px', 13 | textAlign: 'center' 14 | }, 15 | promoSection: { 16 | display: 'flex', 17 | flexFlow: 'row wrap', 18 | justifyContent: 'center', 19 | fontSize: '125%', 20 | lineHeight: '1.6em', 21 | position: 'relative', 22 | zIndex: '99' 23 | }, 24 | featureImageContainer: { 25 | textAlign: 'center' 26 | }, 27 | featureImage: { 28 | maxHeight: '80px' 29 | }, 30 | calloutTitle: { 31 | textAlign: 'left', 32 | textDecoration: 'none', 33 | color: color.title, 34 | fontWeight: '300', 35 | fontSize: '180%', 36 | lineHeight: '1em' 37 | }, 38 | calloutImageContainer: { 39 | textAlign: 'center' 40 | }, 41 | calloutImage: { 42 | maxWidth: '80%' 43 | }, 44 | showcaseBox: { 45 | display: 'block', 46 | padding: '20px', 47 | width: '80px', 48 | textAlign: 'center' 49 | }, 50 | showcaseImage: { 51 | maxWidth: '100%', 52 | maxHeight: '80px' 53 | }, 54 | helpTitle: { 55 | textAlign: 'left', 56 | color: color.title, 57 | fontWeight: '300', 58 | fontSize: '200%', 59 | lineHeight: '1em' 60 | }, 61 | helpSection: { 62 | padding: '20px' 63 | }, 64 | helpSectionTitle: { 65 | textAlign: 'left', 66 | color: color.title, 67 | fontWeight: '300', 68 | fontSize: '150%', 69 | lineHeight: '1em' 70 | } 71 | }; 72 | 73 | module.exports = pages; 74 | -------------------------------------------------------------------------------- /lib/build-files.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('babel-register')({ 4 | babelrc: false, 5 | only: [__dirname, process.cwd() + '/core'], 6 | presets: ['react', 'env'], 7 | }); 8 | 9 | // initial check that required files are present 10 | const chalk = require('chalk'); 11 | const fs = require('fs'); 12 | const CWD = process.cwd(); 13 | 14 | if (!fs.existsSync(CWD + '/siteConfig.js')) { 15 | console.error( 16 | chalk.red('Error: No siteConfig.js file found in website folder!') 17 | ); 18 | process.exit(1); 19 | } 20 | 21 | // generate all static html files 22 | const generate = require('./core/generate.js'); 23 | generate(); 24 | console.log("Site built successfully. Generated files in 'build' folder."); 25 | -------------------------------------------------------------------------------- /lib/copy-examples.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs-extra'); 4 | const glob = require('glob'); 5 | const path = require('path'); 6 | const chalk = require('chalk'); 7 | 8 | const join = path.join; 9 | const CWD = process.cwd(); 10 | 11 | // add scripts to package.json file 12 | const package_json = join(CWD, 'package.json'); 13 | if (fs.existsSync(package_json)) { 14 | const content = JSON.parse( 15 | fs.readFileSync(package_json, 'utf8') 16 | ); 17 | 18 | if (!content.scripts) { content.scripts = {}; } 19 | 20 | content.scripts = Object.assign( 21 | {}, 22 | content.scripts, 23 | { 24 | 'start': 'dochameleon-start', 25 | 'build': 'dochameleon-build', 26 | 'publish-gh-pages': 'dochameleon-publish', 27 | 'examples': 'dochameleon-examples' 28 | } 29 | ); 30 | 31 | fs.writeFileSync( 32 | package_json, 33 | JSON.stringify(content, null, 2) + '\n' 34 | ); 35 | 36 | console.log( 37 | `${chalk.green('Wrote dochameleon scripts to package.json file.')}\n` 38 | ); 39 | } 40 | 41 | const exampleRoot = join(__dirname, '..', 'examples', 'basics'); 42 | 43 | // docs 44 | const docsFrom = join(exampleRoot, 'docs'); 45 | const docsTo = join(CWD, 'docs'); 46 | if (fs.existsSync(docsTo)) { 47 | console.log( 48 | `${chalk.yellow('Example docs already exist!')} 49 | Rename or remove ${chalk.yellow(docsTo)} to regenerate example docs.\n` 50 | ); 51 | } else { 52 | fs.copySync(docsFrom, docsTo); 53 | } 54 | 55 | // blog 56 | const blogFrom = join(exampleRoot, 'blog'); 57 | const blogTo = join(CWD, 'blog'); 58 | if (fs.existsSync(blogTo)) { 59 | console.log( 60 | `${chalk.yellow('Example blog posts already exist!')} 61 | Rename or remove ${chalk.yellow(blogTo)} to regenerate example blog posts.\n` 62 | ); 63 | } else { 64 | fs.copySync(blogFrom, blogTo); 65 | } 66 | 67 | // .gitignore 68 | const gitignoreFrom = join(exampleRoot, 'gitignore'); 69 | const gitignoreTo = join(CWD, '.gitignore'); 70 | if (fs.existsSync(gitignoreTo)) { 71 | console.log( 72 | `${chalk.yellow('.gitignore already exists')} in ${chalk.yellow(CWD)} 73 | Rename or remove the file to regenerate an example version.\n` 74 | ); 75 | } else { 76 | fs.copySync(gitignoreFrom, gitignoreTo); 77 | } 78 | 79 | // copy other files 80 | glob.sync(exampleRoot + '/**/*') 81 | .forEach(file => { 82 | if (fs.lstatSync(file).isDirectory()) { return; } 83 | 84 | const file_name = path.basename(file); 85 | if (file_name === 'gitignore') { return; } 86 | 87 | const folder = path.basename(path.dirname(file)); 88 | if (folder === 'blog' || folder === 'docs') { return; } 89 | 90 | const targetFile = path.resolve(file).replace(exampleRoot, CWD); 91 | try { 92 | fs.copySync(file, targetFile, { overwrite: false, errorOnExist: true }); 93 | } catch (e) { 94 | console.log( 95 | `${chalk.yellow(file_name + ' already exists')} 96 | Rename or remove the file to regenerate an example version.\n` 97 | ); 98 | } 99 | }); 100 | 101 | const file_structure = ` 102 | website/ 103 | ├── blog/ 104 | │   ├── 2018-01-08-why-dochameleon.md 105 | │   └── 2018-01-10-staging-step.md 106 | ├── components/ 107 | ├── docs/ 108 | │   ├── doc1.md 109 | │   ├── doc2.md 110 | │   ├── doc3.md 111 | │   └── sidebars.json 112 | ├── pages/ 113 | │   ├── help.js 114 | │   ├── index.js 115 | │   └── users.js 116 | ├── siteConfig.js 117 | ├── static/ 118 | └── theme/ 119 | ` 120 | 121 | const site_config = chalk.yellow('website/siteConfig.js'); 122 | 123 | const guide = ` 124 | ${chalk.green('Website created')} in ${chalk.green('/website')} 125 | 126 | Default website file structure: 127 | ${chalk.green.dim(file_structure)} 128 | 129 | Go to ${chalk.yellow('/website/docs')} to write docs with Markdown. 130 | Go to ${chalk.yellow('/website/blog')} to write blog posts with Markdown. 131 | Go to ${chalk.yellow('/website/pages')} to write customized pages with React. 132 | Go to ${chalk.yellow('/website/components')} to write React components. 133 | 134 | Set ${chalk.green('docsDir')} in ${site_config} to change path to docs. 135 | Set ${chalk.green('blogDir')} in ${site_config} to change path to blog posts. 136 | 137 | Modify ${site_config} for project settings. 138 | ` 139 | console.log(guide); 140 | -------------------------------------------------------------------------------- /lib/core/Analytics.js: -------------------------------------------------------------------------------- 1 | class Analytics { 2 | constructor(site) { 3 | this.config = site.analyticsConfig 4 | if (!this.config) { return; } 5 | 6 | const { js } = this.config; 7 | if (js) { // add js to config js 8 | site.config.js = (site.config.js || []).concat(js); 9 | } 10 | } 11 | } 12 | 13 | module.exports = Analytics; 14 | -------------------------------------------------------------------------------- /lib/core/Assets.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const chalk = require('chalk'); 3 | const createSitemap = require('sitemap').createSitemap; 4 | 5 | const dfs = require('./dfs.js'); 6 | 7 | const join = path.join; 8 | 9 | class Assets { 10 | constructor(site, rootPath) { 11 | this.site = site; 12 | this.rootPath = rootPath; 13 | } 14 | 15 | sitemap() { 16 | let urls = []; 17 | 18 | this.site.pages.files() 19 | .forEach(file => { 20 | const url = file.split('/pages')[1] 21 | .replace(/\.js$/, '.html'); 22 | urls.push({url, changefreq: 'weekly', priority: 0.5}); 23 | }); 24 | 25 | this.site.blog.metadatas 26 | .forEach(metadata => { 27 | const url = metadata.permalink(); 28 | urls.push({url, changefreq: 'weekly', priority: 0.3}); 29 | }); 30 | 31 | this.site.docs.metadatas 32 | .forEach(metadata => { 33 | const url = metadata.permalink(); 34 | urls.push({url, changefreq: 'weekly', priority: 0.3}); 35 | }); 36 | 37 | const sitemap = createSitemap({ 38 | hostname: this.site.rootUrl, 39 | cacheTime: 600 * 1000, // 600 sec - cache purge period 40 | urls: urls, 41 | }); 42 | 43 | return new Promise((resolve, reject) => { 44 | sitemap.toXML((err, xml) => { 45 | if (err) { 46 | reject('An error has occured.'); 47 | } else { 48 | resolve(xml); 49 | } 50 | }); 51 | }); 52 | } 53 | 54 | staticFiles() { 55 | return dfs.files(join(this.rootPath, '**')); 56 | } 57 | }; 58 | 59 | module.exports = Assets; 60 | -------------------------------------------------------------------------------- /lib/core/Blog.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const Feed = require('feed'); 3 | const React = require('react'); 4 | const renderToStaticMarkup = require('react-dom/server').renderToStaticMarkup; 5 | 6 | const dfs = require('./dfs.js'); 7 | const Extractor = require('./Extractor.js'); 8 | const BlogPageLayout = require('./components/blog/BlogPageLayout.js'); 9 | const BlogPostLayout = require('./components/blog/BlogPostLayout.js'); 10 | 11 | const join = path.join; 12 | 13 | const extractor = new Extractor(); 14 | 15 | const dtToString = dt => { 16 | const year = dt.getFullYear(); 17 | const month = [ 18 | 'January', 19 | 'February', 20 | 'March', 21 | 'April', 22 | 'May', 23 | 'June', 24 | 'July', 25 | 'August', 26 | 'September', 27 | 'October', 28 | 'November', 29 | 'December', 30 | ][dt.getMonth()]; 31 | const day = dt.getDate(); 32 | 33 | return month + ' ' + day + ', ' + year; 34 | } 35 | 36 | class Blog { 37 | constructor(site, rootPath) { 38 | this.site = site; 39 | this.rootPath = rootPath; 40 | 41 | this.loadMetadatas(); 42 | } 43 | 44 | isEmpty() { 45 | return this.metadatas.length === 0; 46 | } 47 | 48 | findByPath(path) { 49 | const found = this.metadatas 50 | .filter(metadata => metadata.path === path); 51 | return found.length > 0? found[0] : null; 52 | } 53 | 54 | files() { 55 | return dfs.files(join(this.rootPath, '**/*.*'), ['.md', '.markdown']); 56 | } 57 | 58 | loadMetadatas() { 59 | this.metadatas = this.files() 60 | .sort() 61 | .reverse() 62 | .map(file => this._parseFile(file)); 63 | } 64 | 65 | feed(type) { 66 | type = type || 'rss'; 67 | 68 | const site = this.site; 69 | const rootUrl = site.urlWithRoot('blog'); 70 | const metadatas = this.metadatas; 71 | const feed = new Feed({ 72 | title: site.config.title + ' Blog', 73 | description: 'The best place to stay up-to-date with the latest ' + 74 | site.config.title + ' news and events.', 75 | id: rootUrl, 76 | link: rootUrl, 77 | image: site.headerIcon, 78 | copyright: site.config.copyright, 79 | updated: metadatas.length > 0? new Date(metadatas[0].date) : new Date(), 80 | }); 81 | 82 | metadatas.forEach(post => { 83 | feed.addItem({ 84 | title: post.title, 85 | link: rootUrl + '/' + post.path, 86 | author: [{ 87 | name: post.author, 88 | link: post.authorURL, 89 | }], 90 | date: new Date(post.date), 91 | description: post.brief, 92 | }); 93 | }); 94 | 95 | return type === 'rss' ? feed.rss2() : feed.atom1(); 96 | } 97 | 98 | renderPage(pageNumber, pageSize, lang) { 99 | pageNumber = pageNumber || 1; 100 | pageSize = pageSize || 10; 101 | 102 | const site = this.site; 103 | const from = (pageNumber - 1) * pageSize; 104 | const to = from + pageSize; 105 | const posts = this.metadatas.slice(from, to); 106 | const previous = pageNumber > 1 107 | ? site.url('/blog/page' + (pageNumber - 1) + '/index.html', lang) 108 | : null; 109 | const next = to < (this.metadatas.length - 1) 110 | ? site.url('/blog/page' + (pageNumber + 1) + '/index.html', lang) 111 | : null; 112 | const blogPageComp = ( 113 | 120 | ); 121 | return renderToStaticMarkup(blogPageComp); 122 | } 123 | 124 | render(post, lang) { 125 | if (typeof post === 'string') { post = this.findByPath(post); } 126 | if (!post) { return ''; } 127 | 128 | const site = this.site; 129 | const recent = this.metadatas.slice(0, 10); 130 | const blogPostComp = 131 | return renderToStaticMarkup(blogPostComp); 132 | } 133 | 134 | _parseFile(file) { 135 | const metadata = extractor.extractMetadata(dfs.read(file)); 136 | // From 2015-08-13-blog-post-name-0.5.md to 2015/08/13/blog-post-name-0-5.html 137 | metadata.path = path.basename(file) 138 | .replace('-', '/') 139 | .replace('-', '/') 140 | .replace('-', '/') 141 | .replace(/\.md$/, '.html'); 142 | 143 | const site = this.site; 144 | metadata.permalink = function(lang) { return site.blogUrl(this.path, lang); } 145 | 146 | metadata.id = metadata.title; 147 | 148 | // Extract, YYYY, MM, DD from the file name 149 | const dtStr = path.basename(file) 150 | .replace(/([0-9]+\-[0-9]+\-[0-9]+)\-.+/, (match, $1) => $1 + 'T06:00:00.000'); 151 | metadata.date = new Date(dtStr); 152 | metadata.dtStr = dtToString(metadata.date); 153 | 154 | // brief 155 | const splitBrief = metadata.content.split(''); 156 | metadata.brief = splitBrief.length > 1 157 | ? splitBrief[0].trim() 158 | : metadata.content.trim().substring(0, 250).trim(); 159 | 160 | return metadata; 161 | } 162 | } 163 | 164 | module.exports = Blog; 165 | -------------------------------------------------------------------------------- /lib/core/Docs.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const React = require('react'); 3 | const renderToStaticMarkup = require('react-dom/server').renderToStaticMarkup; 4 | 5 | const dfs = require('./dfs.js'); 6 | const Extractor = require('./Extractor.js'); 7 | const DocsLayout = require('./components/docs/DocsLayout.js'); 8 | 9 | const join = path.join; 10 | 11 | const extractor = new Extractor(); 12 | 13 | class Docs { 14 | constructor(site, rootPath) { 15 | this.site = site; 16 | this.rootPath = rootPath; 17 | 18 | this.sidebars_json = join(rootPath, 'sidebars.json'); 19 | this.loadSidebars(); 20 | this.loadMetadatas(); 21 | } 22 | 23 | files() { 24 | return dfs.files(join(this.rootPath, '**'), ['.md', '.markdown']); 25 | } 26 | 27 | find(id) { 28 | if (!id) { return null; } 29 | const found = this.metadatas 30 | .filter(metadata => metadata.id === id); 31 | return found.length > 0? found[0] : null; 32 | } 33 | 34 | findByUri(uri) { 35 | const found = this.metadatas 36 | .filter(metadata => metadata.permalink() === uri); 37 | return found.length > 0? found[0] : null; 38 | } 39 | 40 | loadSidebars() { 41 | this.sidebars = {}; 42 | if (dfs.exists(this.sidebars_json)) { 43 | this.sidebars = require(this.sidebars_json); 44 | } 45 | 46 | this.sidebar_items = []; 47 | Object.keys(this.sidebars) 48 | .forEach(key => { 49 | const sidebar = this.sidebars[key]; 50 | Object.keys(sidebar) 51 | .forEach(category => { 52 | const doc_ids = sidebar[category]; 53 | doc_ids.forEach(id => { 54 | const entry = { 55 | id: id, 56 | sidebar: sidebar, 57 | category: category 58 | }; 59 | const last = this.sidebar_items.length > 0 60 | ? this.sidebar_items[this.sidebar_items.length-1] 61 | : null; 62 | if (last) { 63 | entry.previous = last.id; 64 | last.next = id; 65 | } 66 | this.sidebar_items.push(entry); 67 | }); 68 | }); 69 | }); 70 | } 71 | 72 | loadMetadatas() { 73 | this.metadatas = this.files() 74 | .map(file => this.processMetadata(file)) 75 | .filter(metadata => !!metadata); 76 | 77 | this.metadatas.forEach(metadata => { 78 | const previous = this.find(metadata.previous); 79 | if (previous) { 80 | metadata.previous_title = previous.title || 'Previous'; 81 | metadata.previous_sidebar_label = previous.sidebar_label || 'Previous'; 82 | } 83 | const next = this.find(metadata.next); 84 | if (next) { 85 | metadata.next_title = next.title || 'Next'; 86 | metadata.next_sidebar_label = next.sidebar_label || 'Next'; 87 | } 88 | }); 89 | } 90 | 91 | processMetadata(file) { 92 | const file_content = dfs.read(file); 93 | const metadata = extractor.extractMetadata(file_content); 94 | 95 | metadata.source = path.basename(file); 96 | if (!metadata.id) { 97 | metadata.id = path.basename(file, path.extname(file)); 98 | } 99 | if (metadata.id.includes('/')) { 100 | throw new Error('Document id cannot include "/".'); 101 | } 102 | if (!metadata.title) { metadata.title = metadata.id; } 103 | 104 | metadata.path = metadata.id + '.html'; 105 | 106 | const site = this.site; 107 | metadata.permalink = function(lang) { return site.docUrl(this.id, lang); } 108 | 109 | const found = this.sidebar_items 110 | .filter(item => item.id === metadata.id); 111 | if (found.length > 0) { 112 | const item = found[0]; 113 | metadata.sidebar = item.sidebar; 114 | metadata.category = item.category; 115 | 116 | if (item.next) { metadata.next = item.next; } 117 | if (item.previous) { metadata.previous = item.previous; } 118 | } 119 | 120 | return metadata; 121 | } 122 | 123 | render(metadata, lang) { 124 | if (typeof metadata === 'string') { metadata = this.find(metadata); } 125 | if (!metadata) { return null; } 126 | 127 | const site = this.site; 128 | if (lang) { 129 | const doc = site.i18n.translateDoc(metadata.id, lang); 130 | if (doc) { metadata = doc; } 131 | } 132 | const content = this._replaceDocLinksWithFull(metadata.content); 133 | 134 | return renderToStaticMarkup( 135 | 136 | {content} 137 | 138 | ); 139 | } 140 | 141 | _replaceDocLinksWithFull(content) { 142 | this.metadatas 143 | .map(metadata => ({ 144 | source: metadata.source, 145 | url: this.site.docUrl(metadata.id) 146 | })) 147 | .forEach(entry => { 148 | content = content.replace( 149 | new RegExp('\\]\\((\\./)?' + entry.source, 'g'), 150 | '](' + entry.url 151 | ); 152 | }); 153 | return content; 154 | } 155 | } 156 | 157 | module.exports = Docs; 158 | -------------------------------------------------------------------------------- /lib/core/Extractor.js: -------------------------------------------------------------------------------- 1 | class Extractor { 2 | extractMetadata(file_content) { 3 | const { header, content } = this.splitHeader(file_content); 4 | const metadata = { content: content }; 5 | metadata.brief = this.extractBrief(content); 6 | header.split('\n') 7 | .forEach(line => { 8 | const parts = line.split(':'); 9 | const key = parts[0].trim(); 10 | let value = parts.slice(1).join(':').trim(); 11 | try { 12 | value = JSON.parse(value); 13 | } catch(e) {} 14 | metadata[key] = value; 15 | }); 16 | return metadata; 17 | } 18 | 19 | splitHeader(content) { 20 | const lines = content.split(/\r?\n/); 21 | if (lines[0] !== '---') { return { header: '', content: content }; } 22 | 23 | for (var i = 1; i < lines.length - 1; i++) { 24 | if (lines[i] === '---') { 25 | return { 26 | header: lines.slice(1, i + 1).join('\n'), 27 | content: lines.slice(i + 1).join('\n'), 28 | }; 29 | } 30 | } 31 | 32 | return { header: '', content: content }; 33 | } 34 | 35 | extractBrief(content) { 36 | if (!content) { return ''; } 37 | 38 | const splitBrief = content.split(''); 39 | if (splitBrief.length > 1) { 40 | return splitBrief[0].replace(/<.+>/g, '').trim(); 41 | } 42 | 43 | const lines = this.linesForBriefExtraction(content); 44 | let len = lines[0].length; 45 | let i = 1; 46 | for (; i < lines.length; i++) { 47 | len += lines[i].length; 48 | if (len > 128) { break; } 49 | } 50 | 51 | return lines.slice(0, i).join('\n').trim(); 52 | } 53 | 54 | linesForBriefExtraction(content) { 55 | const lines = content.replace(/<.+>/g, '') 56 | .split(/\r?\n/); 57 | const result = []; 58 | 59 | let isCode = false; 60 | for (var i = 0; i < lines.length; i++) { 61 | const codeBoundary = lines[i].match(/```/); 62 | if (codeBoundary) { 63 | if (!isCode) { result.push('...'); } 64 | isCode = !isCode; 65 | continue; 66 | } 67 | if (isCode) { continue; } 68 | 69 | result.push(lines[i]); 70 | } 71 | 72 | return result; 73 | } 74 | } 75 | 76 | module.exports = Extractor; 77 | -------------------------------------------------------------------------------- /lib/core/I18n.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const dfs = require('./dfs.js'); 4 | 5 | const join = path.join; 6 | 7 | class I18n { 8 | constructor(site, rootPath) { 9 | this.site = site; 10 | this.rootPath = rootPath; 11 | 12 | this.loadLanguages(); 13 | } 14 | 15 | hasTranslation() { 16 | return this.languages && Object.keys(this.languages).length > 0; 17 | } 18 | 19 | langs() { 20 | return this.languages 21 | ? Object.keys(this.languages) 22 | : []; 23 | } 24 | 25 | translate(phrase, lang, defVal) { 26 | let val = defVal || phrase; 27 | if (!phrase || !lang) { return val; } 28 | if (typeof phrase !== 'string') { return val; } 29 | 30 | const language = this.languages[lang]; 31 | if (!language) { return val; } 32 | 33 | phrase = phrase.trim(); 34 | if (!phrase) { return val; } 35 | 36 | return language.dictionary[phrase] || val; 37 | } 38 | 39 | translateDoc(id, lang) { 40 | if (!id) { return null; } 41 | 42 | const language = this.languages[lang]; 43 | if (!language) { return null; } 44 | 45 | return language.docs[id] || null; 46 | } 47 | 48 | renderElement(el, lang) { 49 | if (!el) { return el; } 50 | if (typeof el === 'string') { return this.translate(el, lang); } 51 | return el; 52 | } 53 | 54 | loadLanguages() { 55 | this.languages = {}; 56 | 57 | console.log('check i18n directories from ' + this.rootPath); 58 | dfs.directories(join(this.rootPath, '*')) 59 | .forEach(dir => { 60 | console.log(dir); 61 | //TODO: parse out lang then call loadLanguage(lang) 62 | const match = dir.replace(this.rootPath, '') 63 | .match(/.([a-zA-Z]{2})$/); 64 | if (match) { this.loadLanguage(match[1]); } 65 | }); 66 | 67 | // Default language pages also needed to be generated, if i18n is on. 68 | const defLang = this.site.config.defaultLanguage || 'en'; 69 | if (this.langs().length > 0 && !this.languages[defLang]) { 70 | this.languages[defLang] = { 71 | dictionary: {}, 72 | docs: {} 73 | }; 74 | } 75 | } 76 | 77 | loadLanguage(lang) { 78 | console.log('load language ' + lang); 79 | const langRoot = join(this.rootPath, lang); 80 | const langDocsRoot = join(langRoot, 'docs'); 81 | 82 | const configFile = join(langRoot, 'langConfig.js'); 83 | const language = { 84 | config: dfs.exists(configFile)? require(configFile) : { label: lang }, 85 | dictionary: {}, 86 | docs: {} 87 | }; 88 | this.languages[lang] = language; 89 | 90 | // load translated docs 91 | const site = this.site; 92 | const docPaths = []; 93 | dfs.files(join(langDocsRoot, '**'), ['.md', '.markdown']) 94 | .forEach(file => { 95 | docPaths.push(file); // to be excluded from loading vocabularies 96 | const doc = site.docs.processMetadata(file); 97 | if (doc && doc.id) { 98 | language.docs[doc.id] = Object.assign({}, site.docs.find(doc.id), doc); 99 | } 100 | }); 101 | 102 | // load vocabularies 103 | dfs.files(join(langRoot, '**')) 104 | .filter(file => !docPaths.includes(file)) 105 | .filter(file => !file.endsWith('langConfig.js')) 106 | .forEach(file => { 107 | //TODO: load vocabularies from file 108 | const set = require(file); 109 | Object.assign(language.dictionary, set); 110 | }); 111 | } 112 | } 113 | 114 | module.exports = I18n; 115 | -------------------------------------------------------------------------------- /lib/core/Pages.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const React = require('react'); 3 | const renderToStaticMarkup = require('react-dom/server').renderToStaticMarkup; 4 | 5 | const dfs = require('./dfs.js'); 6 | const Page = require('./components/Page.js'); 7 | 8 | const join = path.join; 9 | 10 | class Pages { 11 | constructor(site, rootPath) { 12 | this.site = site; 13 | this.rootPath = rootPath; 14 | } 15 | 16 | files() { 17 | return dfs.files(join(this.rootPath, '**')); 18 | } 19 | 20 | render(uri, lang) { 21 | let file = join(this.rootPath, uri); 22 | if (dfs.exists(file)) { return dfs.read(file); } 23 | 24 | file = file.replace(/\.html$/, '.js'); 25 | if (!dfs.exists(file)) { return ''; } 26 | 27 | const site = this.site; 28 | const ReactComp = require(file); 29 | return renderToStaticMarkup( 30 | 31 | 32 | 33 | ); 34 | } 35 | } 36 | 37 | module.exports = Pages; 38 | -------------------------------------------------------------------------------- /lib/core/Search.js: -------------------------------------------------------------------------------- 1 | const algoliasearch = require('algoliasearch'); 2 | 3 | class Search { 4 | constructor(site) { 5 | this.config = site.searchConfig 6 | if (!this.config) { return; } 7 | 8 | const { application_id, api_key, js } = this.config; 9 | if (!application_id || !api_key) { return; } 10 | 11 | this.client = algoliasearch(application_id, api_key); 12 | 13 | if (js) { // add js to config js 14 | site.config.js = (site.config.js || []).concat(js); 15 | } 16 | } 17 | 18 | getDocIndex() { 19 | if (!this.client) { return null; } 20 | if (!this.docIndex) { 21 | const index = this.client.initIndex('docs'); 22 | const setting = { 23 | searchableAttributes: ['title', 'category', 'content'] 24 | }; 25 | index.setSettings(setting, function(err, content) { 26 | if (err) { 27 | console.log('set settings error', err); 28 | } else { 29 | console.log('set settings success', content); 30 | } 31 | }); 32 | this.docIndex = index; 33 | } 34 | return this.docIndex; 35 | } 36 | 37 | addDoc(doc) { 38 | if (!this.client) { return; } 39 | if (!doc) { return; } 40 | 41 | const index = this.getDocIndex(); 42 | const obj = { 43 | objectID: doc.id, 44 | title: doc.title, 45 | category: doc.category, 46 | permalink: doc.permalink(), 47 | brief: doc.brief, 48 | content: doc.content 49 | }; 50 | index.addObject(obj, function(err, content) { 51 | if (err) { 52 | console.log('search not able to add doc ' + doc.id); 53 | } else { 54 | console.log('search successfully added doc ' + doc.id); 55 | } 56 | }); 57 | } 58 | } 59 | 60 | module.exports = Search; 61 | -------------------------------------------------------------------------------- /lib/core/components/AnalyticsScript.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | const gascript = ` 4 | window.dataLayer = window.dataLayer || []; 5 | function gtag(){dataLayer.push(arguments);} 6 | gtag('js', new Date()); 7 | 8 | gtag('config', '$tracking_id$'); 9 | `; 10 | 11 | class AnalyticsScript extends React.Component { 12 | render() { 13 | const { site } = this.props; 14 | const { analyticsConfig } = site; 15 | if (!analyticsConfig || !analyticsConfig.tracking_id) { return null; } 16 | 17 | const ga = gascript.replace('$tracking_id$', analyticsConfig.tracking_id); 18 | return ( 19 | 20 | ); 21 | } 22 | } 23 | 24 | module.exports = AnalyticsScript; 25 | -------------------------------------------------------------------------------- /lib/core/components/Breadcrumb.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | const CollapseIcon = require('./CollapseIcon.js'); 4 | 5 | const Breadcrumb = (props) => { 6 | const { site, current, lang } = props; 7 | if (!current) { return null; } 8 | 9 | const { theme } = site; 10 | return ( 11 |
12 | 13 | ›  14 | {site.i18n.translate(current.sidebar_label || current.title, lang)} 15 |
16 | ); 17 | }; 18 | 19 | module.exports = Breadcrumb; 20 | -------------------------------------------------------------------------------- /lib/core/components/Button.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | class Button extends React.Component { 4 | render() { 5 | const { site, lang, style, href, target } = this.props; 6 | const theme = site.theme; 7 | const buttonStyle = Object.assign({}, theme.button, style); 8 | return ( 9 | 10 | {site.i18n.renderElement(this.props.children, lang)} 11 | 12 | ) 13 | } 14 | } 15 | 16 | Button.defaultProps = { 17 | target: '_self', 18 | }; 19 | 20 | module.exports = Button; 21 | -------------------------------------------------------------------------------- /lib/core/components/CollapseIcon.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | const open = { 4 | box: { 5 | position: 'relative', 6 | height: '10px', 7 | width: '10px', 8 | display: 'inline-block', 9 | margin: '2px', 10 | marginRight: '12px' 11 | }, 12 | upper: { 13 | position: 'absolute', 14 | top: '0', 15 | left: '2px', 16 | background: 'transparent', 17 | borderWidth: '0 5px 5px', 18 | borderStyle: 'solid', 19 | borderColor: 'transparent #2E8555', 20 | height: '0', 21 | opacity: '1', 22 | width: '5px', 23 | zIndex: '10' 24 | }, 25 | lower: { 26 | position: 'absolute', 27 | top: '100%', 28 | left: '2px', 29 | background: 'transparent', 30 | borderWidth: '0 5px 5px', 31 | borderStyle: 'solid', 32 | borderColor: 'transparent #2E8555', 33 | height: '0', 34 | opacity: '1', 35 | width: '5px', 36 | zIndex: '10', 37 | transform: 'rotate(180deg)' 38 | }, 39 | forward: { 40 | position: 'absolute', 41 | top: '0', 42 | left: '8px', 43 | width: '3px', 44 | height: '6px', 45 | border: '5px solid #2E8555', 46 | borderWidth: '5px 0', 47 | transform: 'rotate(45deg)', 48 | zIndex: '1' 49 | }, 50 | backward: { 51 | position: 'absolute', 52 | top: '0', 53 | left: '8px', 54 | width: '3px', 55 | height: '6px', 56 | border: '5px solid #2E8555', 57 | borderWidth: '5px 0', 58 | transform: 'rotate(-45deg)', 59 | zIndex: '1' 60 | } 61 | }; 62 | 63 | const close = { 64 | box: { 65 | position: 'relative', 66 | height: '10px', 67 | width: '10px', 68 | display: 'inline-block', 69 | margin: '2px', 70 | marginRight: '12px' 71 | }, 72 | upper: { 73 | position: 'absolute', 74 | height: '0', 75 | width: '0' 76 | }, 77 | lower: { 78 | position: 'absolute', 79 | height: '0', 80 | width: '0' 81 | }, 82 | forward: { 83 | position: 'absolute', 84 | top: '0', 85 | left: '8px', 86 | width: '3px', 87 | height: '0', 88 | border: '5px solid #2E8555', 89 | borderWidth: '8px 0', 90 | transform: 'rotate(45deg)', 91 | zIndex: '1' 92 | }, 93 | backward: { 94 | position: 'absolute', 95 | top: '0', 96 | left: '8px', 97 | width: '3px', 98 | height: '0', 99 | border: '5px solid #2E8555', 100 | borderWidth: '8px 0', 101 | transform: 'rotate(-45deg)', 102 | zIndex: '1' 103 | } 104 | }; 105 | 106 | class CollapseIcon extends React.Component { 107 | render() { 108 | const { targetId } = this.props; 109 | const script = ` 110 | var open = document.getElementById('collapse_open'); 111 | var close = document.getElementById('collapse_close'); 112 | open.onclick=function() { 113 | open.style.display = 'none'; 114 | close.style.display = 'inline-block'; 115 | var targetId = open.getAttribute('target_id'); 116 | var targetEl = document.getElementById(targetId); 117 | if (targetEl) { targetEl.style.display = ''; } 118 | }; 119 | close.onclick=function() { 120 | close.style.display = 'none'; 121 | open.style.display = 'inline-block'; 122 | var targetId = open.getAttribute('target_id'); 123 | var targetEl = document.getElementById(targetId); 124 | if (targetEl) { targetEl.style.display = 'none'; } 125 | }; 126 | `; 127 | const open_box_style = Object.assign({}, open.box, {display: 'none'}); 128 | return ( 129 | 130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 | 143 |
144 | ) 145 | } 146 | } 147 | 148 | module.exports = CollapseIcon; 149 | -------------------------------------------------------------------------------- /lib/core/components/Footer.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | class Footer extends React.Component { 4 | render() { 5 | const theme = this.props.site.theme; 6 | const currentYear = new Date().getFullYear(); 7 | return ( 8 | 15 | ); 16 | } 17 | } 18 | 19 | module.exports = Footer; 20 | -------------------------------------------------------------------------------- /lib/core/components/Head.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | const async_script = ''; 4 | 5 | class Head extends React.Component { 6 | render() { 7 | const { site, title, url, description, ogImage, redirect } = this.props; 8 | const { config } = site; 9 | 10 | const keywords = config.keywords || 'Open Source Documentation Dochameleon'; 11 | const author = config.author || 'Dochameleon.io'; 12 | let img_src = site.icon? site.urlWithRoot(site.icon) : ''; 13 | if (config.ogImage) { img_src = site.urlWithRoot(config.ogImage); } 14 | if (ogImage) { img_src = site.urlWithRoot(ogImage); } 15 | 16 | return ( 17 | 18 | 19 | 20 | {title} 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | { img_src && } 31 | {config.noIndex && } 32 | {redirect && ( 33 | 43 | )} 44 | {!site.blog.isEmpty() && ( 45 | 51 | )} 52 | 53 | { 54 | config.css && 55 | config.css.map((source, idx) => ( 56 | 57 | )) 58 | } 59 | { 60 | config.js && 61 | config.js.map((source, idx) => { 62 | const src = (typeof source === 'string')? source : source.src; 63 | return source.async 64 | ? 65 | : 102 | 103 | ) 104 | } 105 | } 106 | 107 | module.exports = SearchBox; 108 | -------------------------------------------------------------------------------- /lib/core/components/SideNav.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | const Breadcrumb = require('./Breadcrumb.js'); 4 | const SideNavCategory = require('./SideNavCategory.js'); 5 | 6 | class SideNav extends React.Component { 7 | render() { 8 | const { site, sidebar, current, lang } = this.props; 9 | const { theme } = site; 10 | 11 | return ( 12 | 13 | 14 | 26 | 27 | ); 28 | } 29 | } 30 | 31 | module.exports = SideNav; 32 | -------------------------------------------------------------------------------- /lib/core/components/SideNavCategory.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | const SideNavItem = require('./SideNavItem.js'); 4 | 5 | const SideNavCategory = (props) => { 6 | const { site, name, items, current, lang } = props; 7 | const { theme } = site; 8 | return ( 9 |
10 |

{site.i18n.translate(name, lang)}

11 |
    12 | {items.map(item => ( 13 | 20 | ))} 21 |
22 |
23 | ); 24 | }; 25 | 26 | module.exports = SideNavCategory; 27 | -------------------------------------------------------------------------------- /lib/core/components/SideNavItem.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | const SideNavItem = (props) => { 4 | const { site, item, current, lang } = props; 5 | const { theme } = site; 6 | const active = current && current.id === item.id; 7 | let linkStyle = theme.sideNavItemLink; 8 | if (current && current.id === item.id) { 9 | linkStyle = Object.assign({}, linkStyle, theme.sideNavItemLinkActive); 10 | } 11 | return ( 12 |
  • 13 | 14 | {site.i18n.translate(item.sidebar_label || item.title, lang)} 15 | 16 |
  • 17 | ); 18 | }; 19 | 20 | module.exports = SideNavItem; 21 | -------------------------------------------------------------------------------- /lib/core/components/blog/BlogPageLayout.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { Row, Col } = require('fluid-react'); 3 | 4 | const Page = require('../Page.js'); 5 | const BlogPost = require('./BlogPost.js'); 6 | const BlogSidebar = require('./BlogSidebar.js'); 7 | 8 | class BlogPageLayout extends React.Component { 9 | render() { 10 | const { site, posts, previous, next, lang } = this.props; 11 | const { theme } = site; 12 | const postComps = posts.map(post => ( 13 | 20 | )); 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | 28 | {postComps} 29 | 34 | 35 | 36 | 37 | ); 38 | } 39 | } 40 | 41 | module.exports = BlogPageLayout; 42 | -------------------------------------------------------------------------------- /lib/core/components/blog/BlogPost.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | const social = require('./social.js'); 4 | const MarkdownBlock = require('../MarkdownBlock.js'); 5 | 6 | const Author = props => { 7 | const { site, post, lang } = props; 8 | if (!post.author) { return null; } 9 | 10 | let author = {}; 11 | if (post.authorGitHub) { 12 | Object.assign(author, social.gitHubUser(post.authorGitHub)); 13 | } 14 | if (post.authorFBID) { 15 | Object.assign(author, social.facebookUser(post.authorFBID)); 16 | } 17 | if (post.authorTwitter) { 18 | Object.assign(author, social.twitterUser(post.authorTwitter)); 19 | } 20 | author.name = post.author; 21 | author.title = post.authorTitle; 22 | if (post.authorUrl) { author.url = post.authorUrl } 23 | if (post.authorImage) { author.image = post.authorImage } 24 | 25 | const theme = site.theme.blog; 26 | return ( 27 | 40 | ); 41 | } 42 | 43 | const PostHeader = props => { 44 | const { site, post, url, lang } = props; 45 | const theme = site.theme.blog; 46 | return ( 47 |
    48 |

    49 | 50 | {site.i18n.translate(post.title, lang)} 51 | 52 |

    53 |

    {post.dtStr}

    54 | 55 |
    56 | ); 57 | } 58 | 59 | const PostContent = props => { 60 | const { post, site, truncate, url, lang } = props; 61 | const theme = site.theme.blog; 62 | if (!truncate) { return {post.content} } 63 | 64 | return ( 65 | 73 | ) 74 | } 75 | 76 | class BlogPost extends React.Component { 77 | render() { 78 | const { site, post, truncate, lang } = this.props; 79 | const theme = site.theme.blog; 80 | return ( 81 |
    82 | 83 | 84 |
    85 | ); 86 | } 87 | } 88 | 89 | module.exports = BlogPost; 90 | -------------------------------------------------------------------------------- /lib/core/components/blog/BlogPostLayout.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { Row, Col } = require('fluid-react'); 3 | 4 | const Page = require('../Page.js'); 5 | const Button = require('../Button.js'); 6 | const BlogPost = require('./BlogPost.js'); 7 | const BlogSidebar = require('./BlogSidebar.js'); 8 | 9 | class BlogPostLayout extends React.Component { 10 | render() { 11 | const { site, post, recent, lang } = this.props; 12 | const { theme } = site; 13 | const url = site.urlWithRoot(site.blogUrl(post.path, lang)); 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
    23 |   24 | 27 |
    28 | 29 |
    30 |
    31 | ); 32 | } 33 | } 34 | 35 | module.exports = BlogPostLayout; 36 | -------------------------------------------------------------------------------- /lib/core/components/blog/BlogSidebar.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | const SideNav = require('../SideNav.js'); 4 | 5 | class BlogSidebar extends React.Component { 6 | render() { 7 | const { site, metadatas, current, lang } = this.props; 8 | const sidebar = { 9 | 'Recent Posts': metadatas 10 | }; 11 | return 12 | } 13 | } 14 | 15 | module.exports = BlogSidebar; 16 | -------------------------------------------------------------------------------- /lib/core/components/blog/social.js: -------------------------------------------------------------------------------- 1 | const gitHubUser = username => { 2 | return { 3 | url: 'https://github.com/' + username, 4 | image: 'https://github.com/' + username + '.png' 5 | } 6 | } 7 | 8 | const facebookUser = FBID => { 9 | return { 10 | url: 'https://www.facebook.com/' + FBID, 11 | image: 'https://graph.facebook.com/' + FBID + '/picture/?height=200&width=200' 12 | } 13 | } 14 | 15 | const twitterUser = screenName => { 16 | return { 17 | url: 'https://twitter.com/' + screenName, 18 | image: 'https://twitter.com/' + screenName + '/profile_image?size=original' 19 | } 20 | } 21 | 22 | module.exports = { 23 | gitHubUser, 24 | facebookUser, 25 | twitterUser 26 | } 27 | -------------------------------------------------------------------------------- /lib/core/components/docs/Doc.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | const MarkdownBlock = require('../MarkdownBlock.js'); 4 | 5 | class Doc extends React.Component { 6 | render() { 7 | const { site, lang, title } = this.props; 8 | const { theme } = site; 9 | 10 | return ( 11 |
    12 | { title && ( 13 |
    14 |

    {site.i18n.translate(title, lang)}

    15 |
    16 | ) 17 | } 18 |
    19 | {this.props.children} 20 |
    21 |
    22 | ); 23 | } 24 | } 25 | 26 | module.exports = Doc; 27 | -------------------------------------------------------------------------------- /lib/core/components/docs/DocsLayout.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { Row, Col } = require('fluid-react'); 3 | 4 | const Page = require('../Page.js'); 5 | const Button = require('../Button.js'); 6 | const Doc = require('./Doc.js'); 7 | const DocsSidebar = require('./DocsSidebar.js'); 8 | 9 | const PrevNext = props => { 10 | const { site, metadata, lang } = props; 11 | const { theme } = site; 12 | if (!metadata.previous && !metadata.next) { return null; } 13 | 14 | return ( 15 |
    16 | {metadata.previous && ( 17 | 20 | )} 21 |   22 | {metadata.next && ( 23 | 26 | )} 27 |
    28 | ) 29 | } 30 | 31 | class DocsLayout extends React.Component { 32 | render() { 33 | const { site, metadata, lang } = this.props; 34 | const { theme } = site; 35 | const { title, brief } = metadata; 36 | const content = this.props.children; 37 | const url = site.urlWithRoot(site.docUrl(metadata.id)); 38 | 39 | return ( 40 | 41 | 42 | { metadata.sidebar 43 | ? ( 44 | 45 | 46 | 47 | 48 | 49 | {content} 50 | 51 | 52 | 53 | ) 54 | : ( 55 | 56 | {content} 57 | 58 | 59 | ) 60 | } 61 | 62 | 63 | ); 64 | } 65 | } 66 | module.exports = DocsLayout; 67 | -------------------------------------------------------------------------------- /lib/core/components/docs/DocsSidebar.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | const SideNav = require('../SideNav.js'); 4 | 5 | class DocsSidebar extends React.Component { 6 | render() { 7 | const { site, metadata, lang } = this.props; 8 | const { sidebar } = metadata; 9 | if (!sidebar) { return null; } 10 | 11 | const expand_sidebar = {}; 12 | Object.keys(sidebar) 13 | .forEach(category => { 14 | expand_sidebar[category] = sidebar[category] 15 | .map(item => site.docs.find(item)) 16 | .filter(metadata => !!metadata); 17 | }); 18 | 19 | return ( 20 | 26 | ); 27 | } 28 | } 29 | 30 | module.exports = DocsSidebar; 31 | -------------------------------------------------------------------------------- /lib/core/components/headerLinks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "type": "doc", "label": "Docs" }, 3 | { "type": "blog", "label": "Blog" }, 4 | { "type": "search" } 5 | ] 6 | -------------------------------------------------------------------------------- /lib/core/components/toSlug.js: -------------------------------------------------------------------------------- 1 | module.exports = string => { 2 | // var accents = "àáäâèéëêìíïîòóöôùúüûñç"; 3 | const accents = 4 | '\u00e0\u00e1\u00e4\u00e2\u00e8' + 5 | '\u00e9\u00eb\u00ea\u00ec\u00ed\u00ef' + 6 | '\u00ee\u00f2\u00f3\u00f6\u00f4\u00f9' + 7 | '\u00fa\u00fc\u00fb\u00f1\u00e7'; 8 | 9 | const without = 'aaaaeeeeiiiioooouuuunc'; 10 | 11 | let slug = string 12 | .toString() 13 | .toLowerCase() 14 | // Handle accentuated characters 15 | .replace(new RegExp('[' + accents + ']', 'g'), c => { 16 | return without.charAt(accents.indexOf(c)); 17 | }) 18 | // Replace `.`, `(` and `?` with blank string like Github does 19 | .replace(/\.|\(|\?/g, '') 20 | .replace(/[^a-z0-9]/g, '-') 21 | .replace(/-+/g, '-') 22 | .replace(/^-|-$/g, ''); 23 | 24 | // Add trailing `-` if string contains ` ...` in the end like Github does 25 | if (/\s[.]{1,}/.test(string)) { 26 | slug += '-'; 27 | } 28 | 29 | return slug; 30 | }; 31 | -------------------------------------------------------------------------------- /lib/core/dfs.js: -------------------------------------------------------------------------------- 1 | /** Dochameleon File System */ 2 | 3 | const fs = require('fs-extra'); 4 | const glob = require('glob'); 5 | const path = require('path'); 6 | 7 | function directories(pattern) { 8 | return glob.sync(pattern, { dot: true }) 9 | .filter(file => fs.lstatSync(file).isDirectory()); 10 | } 11 | 12 | function files(pattern, ext) { 13 | return glob.sync(pattern, { dot: true }) 14 | .filter(file => { 15 | if (fs.lstatSync(file).isDirectory()) { return false; } 16 | if (!ext) { return true; } 17 | let file_ext = path.extname(file); 18 | if (typeof ext === 'string') { return ext === file_ext; } 19 | return ext.includes(file_ext); 20 | }); 21 | } 22 | 23 | function exists(file) { 24 | return fs.existsSync(file); 25 | } 26 | 27 | function read(file) { 28 | if (!fs.existsSync(file)) { return ''; } 29 | 30 | return fs.readFileSync(file, { encoding: 'utf8' }); 31 | } 32 | 33 | function remove(file) { 34 | fs.removeSync(file); 35 | } 36 | 37 | function write(file, content) { 38 | fs.mkdirsSync(file.replace(new RegExp('/[^/]*$'), '')); 39 | fs.writeFileSync(file, content); 40 | } 41 | 42 | function copy(src, dest) { 43 | fs.mkdirsSync(dest.replace(new RegExp('/[^/]*$'), '')); 44 | fs.copySync(src, dest); 45 | } 46 | 47 | module.exports = { 48 | directories, 49 | files, 50 | exists, 51 | read, 52 | remove, 53 | write, 54 | copy 55 | } 56 | -------------------------------------------------------------------------------- /lib/core/generate.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const join = path.join; 4 | 5 | const dfs = require('./dfs.js'); 6 | const Site = require('./site.js'); 7 | 8 | function generate(buildDir, site, lang) { 9 | const targetDir = join(buildDir, lang || ''); 10 | // docs 11 | site.docs.metadatas.forEach(metadata => { 12 | dfs.write( 13 | join(targetDir, 'docs', metadata.path), 14 | site.docs.render(metadata, lang) 15 | ); 16 | }); 17 | 18 | // copy docs assets if they exist 19 | const stage_docs_assets = join(site.stageRoot, 'docs', 'assets'); 20 | if (dfs.exists(stage_docs_assets)) { 21 | dfs.copy(stage_docs_assets, join(targetDir, 'docs', 'assets')); 22 | } 23 | 24 | // blog 25 | site.blog.metadatas.forEach(metadata => { 26 | dfs.write( 27 | join(targetDir, 'blog', metadata.path), 28 | site.blog.render(metadata, lang) 29 | ); 30 | }); 31 | 32 | // blog index 33 | const pageSize = 10; 34 | for (var i = 0; i < site.blog.metadatas.length; i += pageSize) { 35 | const pageNumber = Math.floor(i / pageSize) + 1; 36 | const content = site.blog.renderPage(pageNumber, pageSize, lang); 37 | dfs.write( 38 | join(targetDir, 'blog', 'page' + pageNumber, 'index.html'), 39 | content 40 | ); 41 | if (pageNumber === 1) { 42 | dfs.write( 43 | join(targetDir, 'blog', 'index.html'), 44 | content 45 | ); 46 | } 47 | } 48 | 49 | // copy blog assets if they exist 50 | const stage_blog_assets = join(site.stageRoot, 'blog', 'assets'); 51 | if (dfs.exists(stage_blog_assets)) { 52 | dfs.copy(stage_blog_assets, join(targetDir, 'blog', 'assets')); 53 | } 54 | 55 | // pages 56 | site.pages.files() 57 | .forEach(file => { 58 | if (file.match(/\.js$/)) { 59 | const uri = file.replace(site.pages.rootPath, '') 60 | .replace(/\.js$/, '.html'); 61 | dfs.write(join(targetDir, uri), site.pages.render(uri, lang)); 62 | return; 63 | } 64 | 65 | const targetFile = join(targetDir, file.split('pages')[1]); 66 | dfs.copy(file, targetFile); 67 | }); 68 | } 69 | 70 | function execute() { 71 | const CWD = process.cwd(); 72 | 73 | const siteConfig = require(CWD + '/siteConfig.js'); 74 | const site = new Site(siteConfig); 75 | 76 | console.log('generate.js triggered...'); 77 | 78 | const buildDir = siteConfig.buildDir 79 | ? join(CWD, siteConfig.buildDir) 80 | : join(CWD, 'build', siteConfig.projectName); 81 | console.log('destination folder: ' + buildDir); 82 | 83 | dfs.remove(buildDir); 84 | 85 | generate(buildDir, site); 86 | const languages = site.i18n.langs(); 87 | if (languages) { 88 | languages.forEach(language => generate(buildDir, site, language)); 89 | } 90 | 91 | // put back docs assets if building to GitHub repo docs folder, 92 | // i.e. dir ends with '/docs' 93 | if (buildDir.match(/[\\/]docs[\\/]?$/)) { 94 | const stage_docs_assets = join(site.stageRoot, 'docs', 'assets'); 95 | if (dfs.exists(stage_docs_assets)) { 96 | dfs.copy(stage_docs_assets, join(buildDir, 'assets')); 97 | } 98 | } 99 | 100 | // blog feed 101 | if (site.blog.metadatas.length > 0) { 102 | dfs.write( 103 | join(buildDir, 'blog', 'feed.xml'), 104 | site.blog.feed('rss') 105 | ); 106 | dfs.write( 107 | join(buildDir, 'blog', 'atom.xml'), 108 | site.blog.feed('atom') 109 | ); 110 | } 111 | 112 | // create sitemap 113 | site.assets.sitemap(siteConfig) 114 | .then(xml => dfs.write(join(buildDir, 'sitemap.xml'), xml)); 115 | 116 | // copy all static files 117 | site.assets.staticFiles() 118 | .forEach(file => { 119 | let parts = file.split('/static/'); 120 | let targetFile = join(buildDir, parts[1]); 121 | dfs.copy(file, targetFile); 122 | }); 123 | 124 | // Search Indexing 125 | site.docs.metadatas.forEach(metadata => { 126 | site.search.addDoc(metadata); 127 | }); 128 | 129 | // Generate CNAME file if a custom domain is specified in siteConfig 130 | if (siteConfig.cname) { 131 | dfs.write(join(buildDir, 'CNAME'), siteConfig.cname); 132 | } 133 | } 134 | 135 | module.exports = execute; 136 | -------------------------------------------------------------------------------- /lib/core/pages/README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | Dochameleon generates readme.html from README.md. By default it looks up from `../README.md`. You may also copy your file into `website/pages` folder. 4 | 5 | If wanted to have README as index file, copy `pages/readme.js` from core library to your `website/pages/index.js` 6 | -------------------------------------------------------------------------------- /lib/core/pages/readme.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const path = require('path'); 3 | 4 | const dfs = require('../dfs.js'); 5 | 6 | const Doc = require('../components/docs/Doc.js'); 7 | 8 | const join = path.join; 9 | 10 | class ReadMe extends React.Component { 11 | render() { 12 | const { site, lang } = this.props; 13 | 14 | let readme_path = join(__dirname, './README.md'); 15 | const doc = site.docs.processMetadata(readme_path); 16 | return ( 17 |
    18 | 19 | {doc.content} 20 | 21 |
    22 | ); 23 | } 24 | } 25 | 26 | module.exports = ReadMe; 27 | -------------------------------------------------------------------------------- /lib/core/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const request = require('request'); 3 | const path = require('path'); 4 | 5 | const Site = require('./site.js'); 6 | 7 | function execute(port) { 8 | const CWD = process.cwd(); 9 | const join = path.join; 10 | 11 | const siteConfig = require(join(CWD, 'siteConfig.js')); 12 | const site = new Site(siteConfig); 13 | 14 | const app = express(); 15 | 16 | app.get(/docs\/.*html$/, (req, res, next) => { 17 | let uri = req.path.toString(); 18 | let lang = ''; 19 | const i18nMatch = uri.match(/\/([a-zA-Z]{2})(\/docs\/.+)/); 20 | if (i18nMatch) { 21 | lang = i18nMatch[1]; 22 | uri = i18nMatch[2]; 23 | } 24 | 25 | const metadata = site.docs.findByUri(uri); 26 | if (metadata) { 27 | res.send(site.docs.render(metadata, lang)); 28 | } else { 29 | next(); 30 | } 31 | }); 32 | 33 | app.get('/sitemap.xml', function(req, res) { 34 | site.assets.sitemap(siteConfig) 35 | .then(xml => { 36 | res.set('Content-Type', 'application/xml'); 37 | res.send(xml); 38 | }) 39 | .catch(err => { 40 | res.send(err); 41 | }); 42 | }); 43 | 44 | app.get(/blog\/.*xml$/, (req, res) => { 45 | const type = req.path.toString().match(/atom\.xml$/) ? 'atom' : 'rss'; 46 | res.set('Content-Type', 'application/rss+xml'); 47 | res.send(site.blog.feed(type)); 48 | }); 49 | 50 | app.get(/blog\/.*html$/, (req, res, next) => { 51 | let uri = req.path.toString(); 52 | let lang = ''; 53 | const i18nMatch = uri.match(/\/([a-zA-Z]{2})\/blog\/(.+)/); 54 | if (i18nMatch) { 55 | lang = i18nMatch[1]; 56 | uri = i18nMatch[2]; 57 | } else { 58 | uri = uri.split('/blog/')[1]; 59 | } 60 | 61 | if (uri === 'index.html') { 62 | res.send(site.blog.renderPage(1, 10, lang)); 63 | } else { 64 | res.send(site.blog.render(uri, lang)); 65 | } 66 | }); 67 | 68 | app.get('*.html', (req, res, next) => { 69 | let uri = req.path.toString().replace(siteConfig.baseUrl, ''); 70 | if (uri.startsWith('/')) { uri = uri.substring(1); } 71 | 72 | let lang = ''; 73 | const i18nMatch = uri.match(/^([a-zA-Z]{2})(\/.+)/); 74 | if (i18nMatch) { 75 | lang = i18nMatch[1]; 76 | uri = i18nMatch[2]; 77 | } 78 | 79 | const html = site.pages.render(uri, lang); 80 | if (html) { 81 | res.send(html); 82 | } else { 83 | next(); 84 | } 85 | }); 86 | 87 | // serve static assets from these locations 88 | app.use( 89 | site.url('/docs/assets'), 90 | express.static(join(site.stageRoot, 'docs', 'assets')) 91 | ); 92 | app.use( 93 | site.url('/[a-zA-Z]{2}/docs/assets'), 94 | express.static(join(site.stageRoot, 'docs', 'assets')) 95 | ); 96 | app.use( 97 | site.url('/blog/assets'), 98 | express.static(join(site.stageRoot, 'blog', 'assets')) 99 | ); 100 | app.use( 101 | site.url('/[a-zA-Z]{2}/blog/assets'), 102 | express.static(join(site.stageRoot, 'blog', 'assets')) 103 | ); 104 | app.use( 105 | site.url(''), 106 | express.static(join(site.stageRoot, 'static')) 107 | ); 108 | 109 | // "redirect" requests to pages ending with "/" or no extension so that 110 | // request to "...blog" returns same result as "...blog/index.html" 111 | app.get(/\/[^\.]*\/?$/, (req, res) => { 112 | let path = req.path.toString(); 113 | if (!path.endsWith('/')) { path = path + '/'; } 114 | request.get( 115 | 'http://localhost:' + port + path + 'index.html', 116 | (err, response, body) => { 117 | if (!err) { res.send(body); } 118 | } 119 | ); 120 | }); 121 | 122 | app.listen(port); 123 | console.log('Open http://localhost:' + port + '/'); 124 | } 125 | 126 | module.exports = execute; 127 | -------------------------------------------------------------------------------- /lib/core/theme/blog.js: -------------------------------------------------------------------------------- 1 | const color = require('./color.js'); 2 | 3 | const BlogTheme = { blog: { 4 | post: { 5 | padding: '20px' 6 | }, 7 | title: { 8 | color: color.title, 9 | fontWeight: 300, 10 | fontSize: '200%', 11 | lineHeight: '1em' 12 | }, 13 | titleLink: { 14 | color: color.title 15 | }, 16 | authorBlock: { 17 | display: 'flex' 18 | }, 19 | authorName: { 20 | paddingTop: '5px' 21 | }, 22 | authorNameLink: { 23 | color: color.title 24 | }, 25 | authorImgContainer: { 26 | marginLeft: '0.5em', 27 | width: '30px', 28 | height: '30px', 29 | borderRadius: '50%', 30 | overflow: 'hidden' 31 | }, 32 | readMore: { 33 | marginTop: '0.5em' 34 | } 35 | }}; 36 | 37 | module.exports = BlogTheme; 38 | -------------------------------------------------------------------------------- /lib/core/theme/color.js: -------------------------------------------------------------------------------- 1 | const color = { 2 | primary: '#2E8555', 3 | secondary: '#205C3B', 4 | tertiary: '#e0e0e0', 5 | font: '#393939', 6 | fontSecondary: '#000' 7 | }; 8 | 9 | color.title = color.primary; 10 | color.content = color.font; 11 | color.contentSecondary = color.fontSecondary; 12 | color.clickable = color.primary; 13 | 14 | color.nav = { 15 | primary: color.primary, 16 | secondary: color.secondary, 17 | tertiary: color.tertiary, 18 | font: '#fff', 19 | fontSecondary: 'rgba(255, 255, 255, 0.8)', 20 | fontTertiary: 'rgba(255, 255, 255, 0.6)', 21 | }; 22 | 23 | color.footer = '#808080'; 24 | 25 | module.exports = color; 26 | -------------------------------------------------------------------------------- /lib/core/theme/markdown.js: -------------------------------------------------------------------------------- 1 | const color = require('./color.js'); 2 | 3 | const MarkdownTheme = { md: { 4 | h1: { 5 | textAlign: 'left', 6 | marginTop: '0.5em', 7 | color: color.title, 8 | fontWeight: 300, 9 | fontSize: '200%', 10 | lineHeight: '1.2em' 11 | }, 12 | h2: { 13 | textAlign: 'left', 14 | marginTop: '0.5em', 15 | color: color.title, 16 | fontWeight: 300, 17 | fontSize: '180%', 18 | lineHeight: '1.2em' 19 | }, 20 | h3: { 21 | textAlign: 'left', 22 | marginTop: '0.5em', 23 | color: color.title, 24 | fontWeight: 300, 25 | fontSize: '150%', 26 | lineHeight: '1.2em' 27 | }, 28 | h4: { 29 | textAlign: 'left', 30 | marginTop: '0.5em', 31 | color: color.content, 32 | fontWeight: 300, 33 | fontSize: '120%', 34 | lineHeight: '1.2em' 35 | }, 36 | h5: { 37 | textAlign: 'left', 38 | marginTop: '0.5em', 39 | color: color.content, 40 | fontWeight: 300, 41 | fontSize: '100%', 42 | lineHeight: '1.2em' 43 | }, 44 | h6: { 45 | textAlign: 'left', 46 | marginTop: '0.5em', 47 | color: color.contentSecondary, 48 | fontWeight: 300, 49 | fontSize: '100%', 50 | lineHeight: '1.2em' 51 | }, 52 | p: { 53 | lineHeight: '1.5em', 54 | color: color.content, 55 | paddingTop: '10px', 56 | wordWrap: 'break-word' 57 | }, 58 | ul: { 59 | padding: '5px 20px' 60 | }, 61 | ol: { 62 | padding: '5px 20px' 63 | }, 64 | li_p: { 65 | lineHeight: '1.5em', 66 | color: color.content, 67 | wordWrap: 'break-word' 68 | }, 69 | fence: { 70 | fontFamily: ` 71 | "SFMono-Regular", 72 | source-code-pro, 73 | Menlo, 74 | Monaco, 75 | Consolas, 76 | "Roboto Mono", 77 | "Droid Sans Mono", 78 | "Liberation Mono", 79 | Consolas, 80 | "Courier New", 81 | Courier, 82 | monospace`, 83 | display: 'block', 84 | margin: '20px 0', 85 | padding: '0.5em', 86 | borderLeft: '4px solid ' + color.primary, 87 | borderRadius: '0.3em', 88 | fontSize: '0.8em', 89 | wordBreak: 'break-word', 90 | overflowX: 'auto', 91 | lineHeight: '1.5em' 92 | }, 93 | code: { 94 | padding: '0.2em 0.4em', 95 | background: color.tertiary, 96 | fontSize: '85%', 97 | borderRadius: '3px' 98 | }, 99 | anchor: { 100 | display: 'block', 101 | position: 'relative', 102 | top: '-100px' 103 | }, 104 | table: { 105 | display: 'block', 106 | width: '100%', 107 | overflow: 'auto', 108 | marginTop: '10px', 109 | marginBottom: '10px', 110 | borderSpacing: '0', 111 | borderCollapse: 'collapse', 112 | boxSizing: 'borderBox', 113 | wordWrap: 'break-word' 114 | }, 115 | tr: {}, 116 | th: { 117 | padding: '5px 10px', 118 | border: '1px solid #dfe2e5', 119 | verticalAlign: 'top', 120 | fontWeight: '600' 121 | }, 122 | td: { 123 | padding: '5px 10px', 124 | border: '1px solid #dfe2e5', 125 | verticalAlign: 'top' 126 | } 127 | }} 128 | 129 | MarkdownTheme.md.fence_bash = Object.assign({}, MarkdownTheme.md.fence, { 130 | background: color.nav.primary, 131 | color: color.nav.fontSecondary 132 | }); 133 | 134 | module.exports = MarkdownTheme; 135 | -------------------------------------------------------------------------------- /lib/core/theme/reset.js: -------------------------------------------------------------------------------- 1 | const color = require('./color.js'); 2 | 3 | const reset = {} 4 | 5 | reset['html, body, div, span, applet, object, iframe,' + 6 | 'h1, h2, h3, h4, h5, h6, p, blockquote, pre,' + 7 | 'a, abbr, acronym, address, big, cite, code,' + 8 | 'del, dfn, em, img, ins, kbd, q, s, samp,' + 9 | 'small, strike, strong, sub, sup, tt, var,' + 10 | 'b, u, i, center,' + 11 | 'dl, dt, dd, ol, ul, li,' + 12 | 'fieldset, form, label, legend,' + 13 | 'table, caption, tbody, tfoot, thead, tr, th, td,' + 14 | 'article, aside, canvas, details, embed,' + 15 | 'figure, figcaption, footer, header, hgroup,' + 16 | 'menu, nav, output, ruby, section, summary,' + 17 | 'time, mark, audio, video'] = { 18 | margin: '0', 19 | padding: '0', 20 | border: '0' 21 | }; 22 | 23 | reset['article, aside, details, figcaption, figure,' + 24 | 'footer, header, hgroup, menu, nav, section'] = { 25 | display: 'block' 26 | }; 27 | 28 | reset['body'] = { 29 | lineHeight: '1', 30 | '-webkit-font-smoothing': 'antialiased', 31 | '-moz-osx-font-smoothing': 'antialiased' 32 | }; 33 | 34 | reset['blockquote, q'] = { 35 | quotes: 'none' 36 | }; 37 | 38 | reset['blockquote:before, blockquote:after,' + 39 | 'q:before, q:after'] = { 40 | content: '', 41 | content: 'none' 42 | }; 43 | 44 | reset['table'] = { 45 | borderCollapse: 'collapse', 46 | borderSpacing: '0' 47 | }; 48 | 49 | reset['p'] = { 50 | lineHeight: '1.4' 51 | }; 52 | 53 | reset['img'] = { 54 | maxWidth: '100%', 55 | height: 'auto' 56 | }; 57 | 58 | reset['article p img, article iframe'] = { 59 | maxWidth: '100%', 60 | display: 'inline-block', 61 | marginLeft: 'auto', 62 | marginRight: 'auto' 63 | }; 64 | 65 | reset['a'] = { 66 | color: color.clickable, 67 | textDecoration: 'none' 68 | }; 69 | 70 | reset['a.hash-link'] = { 71 | float: 'left', 72 | paddingRight: '4px', 73 | marginTop: '4px', 74 | marginLeft: '-20px', 75 | lineHeight: '1.2', 76 | opacity: '0', 77 | transition: 'opacity 0.3s' 78 | }; 79 | 80 | reset['h1:hover .hash-link,' + 81 | 'h2:hover .hash-link,' + 82 | 'h3:hover .hash-link,' + 83 | 'h4:hover .hash-link'] = { 84 | opacity: '0.5', 85 | transition: 'none' 86 | }; 87 | 88 | reset['a.hash-link:hover'] = { 89 | opacity: '1!important', 90 | transition: 'none' 91 | }; 92 | 93 | reset['blockquote'] = { 94 | padding: '15px 30px 15px 15px', 95 | margin: '20px 0', 96 | backgroundColor: 'rgba(204, 122, 111, 0.1)', 97 | borderLeft: '5px solid rgba(191, 87, 73, 0.2)' 98 | }; 99 | 100 | reset['ul, ol'] = { 101 | padding: '10px 20px' 102 | }; 103 | 104 | reset['input::-webkit-input-placeholder'] = { 105 | color: '#e5e5e5' 106 | }; 107 | 108 | reset['input::-moz-placeholder'] = { 109 | color: '#e5e5e5' 110 | }; 111 | 112 | reset['input::placeholder'] = { 113 | color: '#e5e5e5' 114 | }; 115 | 116 | const resetTheme = { reset }; 117 | 118 | module.exports = resetTheme; 119 | -------------------------------------------------------------------------------- /lib/core/theme/search.js: -------------------------------------------------------------------------------- 1 | const color = require('./color.js'); 2 | 3 | const SearchTheme = { search: { 4 | container: { 5 | position: 'relative' 6 | }, 7 | input: { 8 | lineHeight: '1.5em', 9 | background: '#eee', 10 | padding: '0 0.5em', 11 | margin: '0 10px', 12 | fontSize: '0.8em', 13 | verticalAlign: 'middle', 14 | boxSizing: 'border-box', 15 | borderStyle: 'solid', 16 | borderColor: color.nav.primary, 17 | borderRadius: '1em', 18 | width: '12em', 19 | '@media (max-width: 767px)': { 20 | width: '8em', 21 | borderColor: color.nav.secondary 22 | } 23 | }, 24 | list: { 25 | display: 'none', 26 | position: 'absolute', 27 | right: '1em', 28 | top: '1.5em', 29 | minWidth: '25em', 30 | maxWidth: '30em', 31 | maxHeight: '25em', 32 | whiteSpace: 'pre-wrap', 33 | overflow: 'auto', 34 | background: '#fff', 35 | opacity: '0.9', 36 | listStyle: 'none', 37 | textAlign: 'left', 38 | borderRadius: '5px', 39 | borderBottom: '1px solid', 40 | borderColor: color.nav.primary, 41 | boxSizing: 'border-box', 42 | padding: '0', 43 | '@media (max-width: 575px)': { 44 | right: '0.5em', 45 | minWidth: '20em', 46 | maxWidth: '25em' 47 | } 48 | }, 49 | item: { 50 | padding: '0', 51 | title: { 52 | padding: '1em 1em 0.5em 1em', 53 | color: color.primary, 54 | display: 'block', 55 | textDecoration: 'underline' 56 | }, 57 | description: { 58 | padding: '0 1em 0 2em', 59 | color: color.secondary, 60 | whiteSpace: 'pre-wrap', 61 | fontSize: '0.7em' 62 | } 63 | }, 64 | footer: { 65 | padding: '1em', 66 | textAlign: 'right', 67 | search_by: { 68 | marginRight: '0.5em' 69 | }, 70 | logo: { 71 | width: '4em', 72 | verticalAlign: 'middle' 73 | } 74 | } 75 | }} 76 | 77 | module.exports = SearchTheme; 78 | -------------------------------------------------------------------------------- /lib/start-server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('babel-register')({ 4 | babelrc: false, 5 | only: [__dirname, process.cwd() + '/core'], 6 | presets: ['react', 'env'], 7 | }); 8 | 9 | const chalk = require('chalk'); 10 | const fs = require('fs'); 11 | const commander = require('commander'); 12 | const tcpPortUsed = require('tcp-port-used'); 13 | 14 | const CWD = process.cwd(); 15 | 16 | if (!fs.existsSync(CWD + '/siteConfig.js')) { 17 | console.error( 18 | chalk.red('Error: No siteConfig.js file found in website folder!') 19 | ); 20 | process.exit(1); 21 | } 22 | 23 | commander.option('--port ', 'Specify port number').parse(process.argv); 24 | const port = parseInt(commander.port, 10) || 3000; 25 | 26 | tcpPortUsed 27 | .check(port, 'localhost') 28 | .then(function(inUse) { 29 | if (inUse) { 30 | console.error(chalk.red('Port ' + port + ' is in use')); 31 | process.exit(1); 32 | } else { 33 | console.log('Starting Dochameleon server on port ' + port + '...'); 34 | const server = require('./core/server.js'); 35 | server(port); 36 | } 37 | }) 38 | .catch(function(ex) { 39 | setTimeout(function() { 40 | throw ex; 41 | }, 0); 42 | }); 43 | -------------------------------------------------------------------------------- /lib/static/img/algolia.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /lib/static/img/dochameleon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/lib/static/img/dochameleon.png -------------------------------------------------------------------------------- /lib/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/lib/static/img/favicon.ico -------------------------------------------------------------------------------- /lib/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/lib/static/img/favicon.png -------------------------------------------------------------------------------- /lib/static/img/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/lib/static/img/github.png -------------------------------------------------------------------------------- /lib/static/img/language.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /lib/static/img/markdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/lib/static/img/markdown.png -------------------------------------------------------------------------------- /lib/static/img/octocat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/lib/static/img/octocat.jpg -------------------------------------------------------------------------------- /lib/static/img/progressive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/lib/static/img/progressive.png -------------------------------------------------------------------------------- /lib/static/img/react.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /lib/static/img/setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/lib/static/img/setup.png -------------------------------------------------------------------------------- /lib/static/img/translation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dochameleon", 3 | "description": "Simpler but more flexible Docusaurus", 4 | "version": "0.0.51", 5 | "license": "MIT", 6 | "keywords": [ 7 | "documentation", 8 | "websites", 9 | "open source", 10 | "dochameleon", 11 | "docusaurus" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/richardzcode/Dochameleon.git" 16 | }, 17 | "scripts": { 18 | "ci-check": "yarn prettier:diff", 19 | "format:source": "prettier --config .prettierrc --write \"lib/**/*.js\"", 20 | "format:examples": "prettier --config .prettierrc --write \"examples/**/*.js\"", 21 | "nit:source": "prettier --config .prettierrc --list-different \"lib/**/*.js\"", 22 | "nit:examples": "prettier --config .prettierrc --list-different \"examples/**/*.js\"", 23 | "prettier": "yarn format:source && yarn format:examples", 24 | "prettier:diff": "yarn nit:source && yarn nit:examples", 25 | "test": "jest" 26 | }, 27 | "dependencies": { 28 | "algoliasearch": "^3.27.1", 29 | "babel-preset-env": "^1.7.0", 30 | "babel-preset-react": "^6.24.1", 31 | "babel-register": "^6.24.1", 32 | "babel-traverse": "^6.25.0", 33 | "babylon": "^6.17.4", 34 | "chalk": "^2.4.1", 35 | "commander": "^2.15.1", 36 | "express": "^4.16.3", 37 | "feed": "^1.1.1", 38 | "fluid-react": "0.0.41", 39 | "fs-extra": "^5.0.0", 40 | "fsts": "0.0.40", 41 | "glob": "^7.1.2", 42 | "react": "^16.4.0", 43 | "react-dom": "^16.4.0", 44 | "react-dom-factories": "^1.0.1", 45 | "remarkable": "^1.7.1", 46 | "request": "^2.87.0", 47 | "shelljs": "^0.7.8", 48 | "sitemap": "^1.13.0", 49 | "tcp-port-used": "^0.1.2" 50 | }, 51 | "bin": { 52 | "dochameleon-start": "./lib/start-server.js", 53 | "dochameleon-build": "./lib/build-files.js", 54 | "dochameleon-publish": "./lib/publish-gh-pages.js", 55 | "dochameleon-examples": "./lib/copy-examples.js" 56 | }, 57 | "devDependencies": { 58 | "jest": "^21.2.1", 59 | "prettier": "^1.13.3", 60 | "rimraf": "^2.6.2" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | build/ 4 | yarn.lock 5 | **/node_modules 6 | searchConfig.js 7 | -------------------------------------------------------------------------------- /website/analyticsConfig.js: -------------------------------------------------------------------------------- 1 | const analyticsConfig = { 2 | tracking_id: 'UA-113485999-1', 3 | js: { src: 'https://www.googletagmanager.com/gtag/js', async: true } 4 | } 5 | 6 | module.exports = analyticsConfig; 7 | -------------------------------------------------------------------------------- /website/blog/2018-01-08-why-dochameleon.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Why Dochameleon 3 | author: Richard Zhang 4 | authorGitHub: richardzcode 5 | --- 6 | 7 | It is not hard to guess where the idea of Dochameleon is from. 8 | 9 | With [Docusaurus](https://github.com/facebook/Docusaurus) built and maintained by awesome team from Facebook, why do I want to build another one? 10 | 11 | One personal reason is I am so excited about Docusaurus. Can't stop checking around, rewrite, restruct source code. So only way to satisfy is to create a new project. 12 | 13 | 14 | 15 | Here are some technical reasons, 16 | 17 | 1. Dev server and site generation are written separately. Inconsistency is inevitable. 18 | 2. Pages can not share building blocks. 19 | 3. Big CSS file makes styling hard. 20 | 21 | At the same time some features are removed. I feel they are a bit too opinionated with complexity, may not suited for all open source projects. 22 | 23 | 1. Search with [algolia](https://www.algolia.com/). 24 | 2. Multi-Language with [crowdin](https://crowdin.com/). 25 | 3. Project version support. 26 | 27 | For example, at the end of the day multi-language and versioning are grouped by file hierarchies. One options is to just take pull requests on GitHub. 28 | -------------------------------------------------------------------------------- /website/blog/2018-01-10-staging-step.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Staging Step 3 | author: Richard Zhang 4 | authorGitHub: richardzcode 5 | --- 6 | 7 | The basic idea of Dochameleon is to generate a static web server by using [Server-Side-Rendering](https://reactjs.org/docs/react-dom-server.html). Contents are combined from core library and specific project. 8 | 9 | Combining from two sources can be tricky. So there is a staging step. Which in essence copies files from the two sources into one place, for SSR to generate from, and for developers to inspect on. 10 | 11 | The one place can be found at `node_modules/dochameleon/lib/stage`, contains a file structure like this: 12 | 13 | ```bash 14 | node_modules/dochameleon/lib/stage/ 15 | ├── Blog.js 16 | ├── Docs.js 17 | ├── Pages.js 18 | ├── blog 19 | │   └── 2018-01-08-why-dochameleon.md 20 | │   └── 2018-01-10-stage-step.md 21 | ├── components 22 | │   ├── Button.js 23 | │   ├── CollapseIcon.js 24 | │   ├── FeatureCallout.js 25 | │   ├── FeatureCallouts.js 26 | │   ├── Features.js 27 | │   ├── Footer.js 28 | │   ├── Head.js 29 | │   ├── HeaderNav.js 30 | │   ├── HelpDetails.js 31 | │   ├── HomeSplash.js 32 | │   ├── MarkdownBlock.js 33 | │   ├── Page.js 34 | │   ├── Showcase.js 35 | │   ├── SideNav.js 36 | │   ├── blog 37 | │   ├── docs 38 | │   ├── featureCallouts.json 39 | │   ├── features.json 40 | │   ├── help.json 41 | │   └── users.json 42 | ├── dfs.js 43 | ├── docs 44 | │   ├── doc1.md 45 | │   ├── doc2.md 46 | │   ├── doc3.md 47 | │   ├── exampledoc4.md 48 | │   ├── exampledoc5.md 49 | │   └── sidebars.json 50 | ├── pages 51 | │   ├── help.js 52 | │   ├── index.js 53 | │   └── users.js 54 | ├── parse 55 | │   ├── Markdown.js 56 | │   └── toSlug.js 57 | ├── static 58 | │   ├── css 59 | │   └── img 60 | └── theme 61 | ├── blog.js 62 | ├── main.js 63 | ├── markdown.js 64 | └── pages.js 65 | ``` 66 | -------------------------------------------------------------------------------- /website/blog/2018-02-02-dochameleon-io-live.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Dochameleon.io is Live 3 | author: Richard Zhang 4 | authorGitHub: richardzcode 5 | --- 6 | 7 | Initially building [Dochameleon](https://dochameleon.io) was purely for fun. Writing code is like playing video game to me. [Docusaurus](https://github.com/facebook/Docusaurus) was like a new game release. 8 | 9 | So just play. 10 | 11 | Since Dochameleon is my own project I have the luxury to cut down, rewrite, in any way I feel right. Build the core first, then add features like search and analytics. I started to think this is actually useful. In some areas it is even better than the awesome Docusaurus built by Facebook folks. (disclaimer: just some areas, not the whole project) 12 | 13 | Why not put it online. 14 | 15 | Register a domain with AWS Route 53; Create a CloudFront distribution in order to serve SSL certificate of its own domain instead of GitHub. 16 | 17 | https://dochameleon.io is live! 18 | -------------------------------------------------------------------------------- /website/blog/2018-03-09-everybody-should-have-an-website.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Everybody Should Have an Website 3 | author: Richard Zhang 4 | authorGitHub: richardzcode 5 | --- 6 | 7 | Everybody has social networks apps, probably more than one. But not a lot of people own websites. 8 | 9 | The assumption is building an website is hard and probably requires some cost. Even [Wordpress](https://wordpress.org) famous 5 minutes installation doesn't cut it. 10 | 11 | Actually it is easy, and requires no money, or very little depending on your needs. 12 | 13 | This formula is generally true for many use cases: 14 | 15 | ``` 16 | Website = Hosting + Domain + Static Content 17 | ``` 18 | 19 | ## Hosting 20 | 21 | [GitHub](https://github.com) hosting is free. If you don't know Git. Just follow instructions on GitHub. They made everything simple and clear. 22 | 23 | [Netlify](https://netlify.com) also has free personal plan. It is not just hosting. You may end up have Netlify for everything. 24 | 25 | Of course there are [Amazon S3](https://aws.amazon.com/s3/) and alike. 26 | 27 | ## Domain 28 | 29 | Domain registration is the part that needs some money. Price is different per providers. I use [Amazon AWS](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/registrar.html). As of today, $12 for a '.com' domain for one year. 30 | 31 | On the other hand, to get a domain like '_your_name_.github.io' or '_your_name_.netlify.com' is free. So this is really depend on how personalized do you want. 32 | 33 | ## Content Creation 34 | 35 | People are afraid of learning HTML/CSS/Javascript. 36 | 37 | But you don't have to. Just learn [Markdown](https://guides.github.com/features/mastering-markdown/), the guide is only one page. It is easier than MS Word. 38 | 39 | ## Content Generation 40 | 41 | There are tools to convert your content to HTML. 42 | 43 | [Jekyll](https://jekyllrb.com/) is built for transforming your plain text into static websites and blogs. 44 | 45 | [Hugo](https://gohugo.io) is another popular choice. 46 | 47 | If you know VueJs, try [NUXT](https://nuxtjs.org/). 48 | 49 | If you know React, try [Docusaurus](https://docusaurus.io) or [Dochameleon](https://dochameleon.io). You still just write Markdown for content, but have the chance to extend with ReactJs. 50 | 51 | There are so many, these are only the ones I have experience or interested in. 52 | 53 | ## Security 54 | 55 | To make your site secure require some learning and maybe some money. However for most personal websites for just sharing contents. This may not be necessary. 56 | 57 | ## Conclusion 58 | 59 | Social networks are boring. Have an internet handle for your personalities. The cost is less than one day of time, and zero amount of money. 60 | -------------------------------------------------------------------------------- /website/components/Callout.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { Div, A, Img, Row, Col } = require('fluid-react'); 3 | 4 | const MarkdownBlock = require('./MarkdownBlock'); 5 | 6 | class FeatureCallout extends React.Component { 7 | render() { 8 | const { site, lang, callout } = this.props; 9 | const { theme } = site; 10 | const title = site.i18n.translate(callout.title, lang); 11 | const content = callout['content.token'] 12 | ? [].concat(site.i18n.translate(callout['content.token'], lang, callout.content)).join('') 13 | : [].concat(callout.content).join(''); 14 | return ( 15 | 16 | {callout.imgFirst && ( 17 | 18 |
    19 | 23 |
    24 | 25 | )} 26 | 27 | {callout.doc 28 | ? {title} 32 | :
    {title}
    33 | } 34 | {content} 35 | 36 | {!callout.imgFirst && ( 37 | 38 |
    39 | 40 |
    41 | 42 | )} 43 |
    44 | ) 45 | } 46 | } 47 | 48 | module.exports = FeatureCallout; 49 | -------------------------------------------------------------------------------- /website/components/Features.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { H3, Div, Img, Row, Col } = require('fluid-react'); 3 | 4 | const MarkdownBlock = require('./MarkdownBlock.js'); 5 | const features = require('./features.json'); 6 | 7 | const header = features.header 8 | ? [].concat(features.header).join('') 9 | : ''; 10 | const footer = features.footer 11 | ? [].concat(features.footer).join('') 12 | : ''; 13 | 14 | const Features = (props) => { 15 | const { site, lang } = props; 16 | const { theme } = site; 17 | const sectionComps = features.features.map((feature, idx) => { 18 | const content = feature['content.token'] 19 | ? [].concat(site.i18n.translate(feature['content.token'], lang, feature.content)).join('') 20 | : [].concat(feature.content).join(''); 21 | return ( 22 | 23 |
    24 |
    25 | 26 |
    27 |

    {site.i18n.translate(feature.title, lang)}

    28 | {content} 29 |
    30 | 31 | ) 32 | }); 33 | 34 | return ( 35 | 36 | {header && {header}} 37 | {sectionComps} 38 | {footer && {footer}} 39 | 40 | ) 41 | } 42 | 43 | module.exports = Features; 44 | -------------------------------------------------------------------------------- /website/components/Footer.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | class Footer extends React.Component { 4 | render() { 5 | const { site, lang } = this.props; 6 | const { theme } = site; 7 | const currentYear = new Date().getFullYear(); 8 | return ( 9 | 61 | ); 62 | } 63 | } 64 | 65 | module.exports = Footer; 66 | -------------------------------------------------------------------------------- /website/components/HelpDetails.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { H3, Div, Row, Col } = require('fluid-react'); 3 | 4 | const MarkdownBlock = require('./MarkdownBlock.js'); 5 | const help = require('./help.json'); 6 | 7 | const HelpDetails = (props) => { 8 | const { site, lang } = props; 9 | const { theme } = site; 10 | const sectionComps = help.map((help_item, idx) => { 11 | return ( 12 | 13 |
    14 |

    {help_item.title}

    15 | {help_item.content} 16 |
    17 | 18 | ) 19 | }); 20 | return {sectionComps} 21 | } 22 | 23 | module.exports = HelpDetails; 24 | -------------------------------------------------------------------------------- /website/components/HomeSplash.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { H2, Div } = require('fluid-react'); 3 | 4 | const Button = require('./Button.js'); 5 | 6 | class HomeSplash extends React.Component { 7 | render() { 8 | const { site, lang } = this.props; 9 | const tagline = site.i18n.translate('tagline', lang, site.config.tagline); 10 | return ( 11 |
    12 |

    13 | {site.config.title} 14 |
    {tagline}
    15 |

    16 |
    17 | 18 | 19 | 20 |
    21 |
    22 | ); 23 | } 24 | } 25 | 26 | module.exports = HomeSplash; 27 | -------------------------------------------------------------------------------- /website/components/Showcase.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { H3, H5, Div, P, Row, Col } = require('fluid-react'); 3 | 4 | const users = require('./users.json'); 5 | 6 | const Showcase = props => { 7 | if (users.length === 0) { return null; } 8 | 9 | const { site, lang } = props; 10 | const { theme } = site; 11 | const userComps = users 12 | .filter(user => !props.pinned || user.pinned) 13 | .map((user, i) => { 14 | let img_src = user.img; 15 | if (!img_src.startsWith('http')) { img_src = site.url(img_src); } 16 | return ( 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | }); 24 | 25 | return ( 26 |
    27 |

    28 | {site.i18n.translate('Who\'s Using This?', lang)}

    29 |
    30 | {site.i18n.translate('This project is used by all these people', lang)} 31 |
    32 | 33 | {userComps} 34 | 35 |
    36 | ); 37 | }; 38 | 39 | module.exports = Showcase; 40 | -------------------------------------------------------------------------------- /website/components/callouts.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Quick Setup", 4 | "doc": "guide_installation", 5 | "content.token": "quick_setup.content", 6 | "content": [ 7 | "Get up and running quickly. Have your own website in just a few minutes", 8 | "\n", 9 | "\n```bash", 10 | "\n> npm install -g dochameleon-init", 11 | "\n> dochameleon-init", 12 | "\n> cd website", 13 | "\n> npm run start", 14 | "\n```" 15 | ], 16 | "img": "img/setup.png", 17 | "imgFirst": false 18 | }, 19 | { 20 | "title": "Develop and Deploy", 21 | "doc": "guide_site_publishing", 22 | "content.token": "develop_deploy.content", 23 | "content": [ 24 | "Develop using included live server; ", 25 | "Deploy to GitHub or any static file web hosts" 26 | ], 27 | "img": "img/octocat.jpg", 28 | "imgStyle": { "maxWidth": "40%" }, 29 | "imgFirst": true 30 | } 31 | ] 32 | -------------------------------------------------------------------------------- /website/components/features.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": "", 3 | "footer": "", 4 | "features": [ 5 | { 6 | "title": "Powered by Markdown", 7 | "content.token": "markdown.content", 8 | "content": [ 9 | "Write docs and blog posts with ", 10 | "[Markdown](https://guides.github.com/features/mastering-markdown/). ", 11 | "Dochameleon converts them into HTML files." 12 | ], 13 | "img": "img/markdown.png" 14 | }, 15 | { 16 | "title": "Built Using React", 17 | "content.token": "react.content", 18 | "content": [ 19 | "Build [React](https://reactjs.org/) Components ", 20 | "to extend or customize your project." 21 | ], 22 | "img": "img/react.svg" 23 | }, 24 | { 25 | "title": "Progressive Customization", 26 | "content.token": "customization.content", 27 | "content": "Works out-of-box, able to customize to the extend of replacing core components", 28 | "img": "img/progressive.png" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /website/components/headerLinks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "type": "doc", "value": "guide_installation", "label": "Docs" }, 3 | { "type": "page", "value": "help", "label": "Help" }, 4 | { "type": "blog", "label": "Blog" }, 5 | { 6 | "type": "page", 7 | "value": "languages", 8 | "img": "img/translation.svg", 9 | "label": "Languages" 10 | }, 11 | { 12 | "type": "url", 13 | "value": "https://github.com/richardzcode/Dochameleon", 14 | "img": "img/github.png", 15 | "label": "GitHub" 16 | }, 17 | { "type": "search" } 18 | ] 19 | -------------------------------------------------------------------------------- /website/components/help.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Browse Docs", 4 | "content": "Learn more using the [documentation](docs/guide_installation.html)." 5 | }, 6 | { 7 | "title": "Join the community", 8 | "content": "Ask questions at [Gitter](https://gitter.im/Dochameleon/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link)." 9 | }, 10 | { 11 | "title": "Stay up to date", 12 | "content": "Find out what's [new](https://github.com/richardzcode/Dochameleon)." 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /website/components/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "fsts-js", 4 | "img": "img/fsts.png", 5 | "link": "https://richardzcode.github.io/fsts-js/index.html", 6 | "pinned": true 7 | }, 8 | { 9 | "caption": "Fluid React", 10 | "img": "img/f-r.png", 11 | "link": "https://richardzcode.github.io/fluid-react/index.html", 12 | "pinned": true 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /website/docs/guide_analytics.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_analytics 3 | title: Analytics 4 | sidebar_label: Analytics 5 | --- 6 | 7 | Dochameleon support [Google Analytics](https://developers.google.com/analytics/). 8 | 9 | After setup Google Analytics for your project. Add `analyticsConfig.js` along with `siteConfig.js` 10 | 11 | ```bash 12 | website/ 13 | └── analyticsConfig.js 14 | ``` 15 | 16 | Content similiar to this, remember to replace tracker_id: 17 | ``` 18 | const analyticsConfig = { 19 | tracking_id: 'UA-XXXX-Y', 20 | js: { src: 'https://www.googletagmanager.com/gtag/js', async: true } 21 | } 22 | 23 | module.exports = analyticsConfig; 24 | ``` 25 | 26 | Then build and publish. Check results at [Google Analytics](https://analytics.google.com) 27 | -------------------------------------------------------------------------------- /website/docs/guide_configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_configuration 3 | title: Site Configuration 4 | sidebar_label: Site Configuration 5 | --- 6 | 7 | 8 | 9 | ### Config File 10 | 11 | ```bash 12 | website/ 13 | └── siteConfig.js 14 | ``` 15 | 16 | Site configurations are defined in `website/siteConfig.js`, here is an example: 17 | 18 | ``` 19 | const currentYear = new Date().getFullYear(); 20 | 21 | const siteConfig = { 22 | projectName: 'Dochameleon', 23 | title: 'Dochameleon', 24 | tagline: 'Open Source Documentation Site Generator', 25 | copyright: 'Copyright © ' + currentYear + ' Richard Zhang', 26 | 27 | rootUrl: 'https://richardzcode.github.io', 28 | baseUrl: '/Dochameleon', 29 | 30 | icon: 'img/dochameleon.png', 31 | favicon: 'img/favicon.png', 32 | 33 | css: [], 34 | js: [ 35 | 'https://buttons.github.io/buttons.js', 36 | ] 37 | }; 38 | ``` 39 | 40 | Dochameleon itself does not have css file. Theming is through CSS-in-JS. Extra css and js files can be specified in siteConfig.js 41 | 42 | ### Header Links 43 | 44 | `website/components/headerLinks.json` defines what displayed on main menu. Create this file to replace default header links. 45 | 46 | Menu item types: 47 | 48 | * doc 49 | * blog 50 | * page 51 | * url 52 | * search 53 | 54 | Example: 55 | 56 | ``` 57 | [ 58 | { "type": "doc", "value": "guide_installation", "label": "Docs" }, 59 | { "type": "page", "value": "help", "label": "Help" }, 60 | { "type": "blog", "label": "Blog" }, 61 | { 62 | "type": "page", 63 | "value": "languages", 64 | "img": "img/translation.svg", 65 | "label": "Languages" 66 | }, 67 | { 68 | "type": "url", 69 | "value": "https://github.com/richardzcode/Dochameleon", 70 | "img": "img/github.png", 71 | "label": "GitHub" 72 | }, 73 | { "type": "search" } 74 | ] 75 | ``` 76 | -------------------------------------------------------------------------------- /website/docs/guide_customize_color.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_color_scheme 3 | title: Color Scheme 4 | sidebar_label: Color Scheme 5 | --- 6 | 7 | Changing colors probably is the simplest way to differenciate a website. 8 | 9 | Dochameleon does not have CSS file. All styles are defined in Javascript, which enables easy style customization. Changing color scheme is a perfect example. 10 | 11 | The core library has a [theme](https://github.com/richardzcode/Dochameleon/tree/master/lib/core/theme) folder which defines UI style. Custom website also have a `theme` folder. At runtime the two folder will be combined together, with the custom file replace core file if names are same. 12 | 13 | Copy [color.js](https://github.com/richardzcode/Dochameleon/tree/master/lib/core/theme/color.js) into website folder `/theme`, then change `primary` and `secondary` color. So it becomes: 14 | 15 | ``` 16 | const color = { 17 | primary: '#283e4a', 18 | secondary: '#337ab7', 19 | tertiary: '#e0e0e0', 20 | font: '#393939', 21 | fontSecondary: '#000' 22 | }; 23 | 24 | color.title = color.primary; 25 | color.content = color.font; 26 | color.contentSecondary = color.fontSecondary; 27 | color.clickable = color.primary; 28 | 29 | color.nav = { 30 | primary: color.primary, 31 | secondary: color.secondary, 32 | tertiary: color.tertiary, 33 | font: '#fff', 34 | fontSecondary: 'rgba(255, 255, 255, 0.8)', 35 | fontTertiary: 'rgba(255, 255, 255, 0.6)' 36 | }; 37 | 38 | color.footer = '#808080'; 39 | 40 | module.exports = color; 41 | ``` 42 | 43 | run dev server again, `npm run start`. See what happens. 44 | -------------------------------------------------------------------------------- /website/docs/guide_customize_core.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_core 3 | title: Core Component 4 | sidebar_label: Core Component 5 | --- 6 | 7 | Like mentioned in [Customization - React Component](./guide_react.html), Dochameleon merges custom and core components together at runtime. This makes further customization possible. 8 | 9 | Let's say you don't like sidebar at the left side for docs. Just copy `components/docs/DocsLayout.js` from core library to `website/components/docs`, then modify render function to switch the order of sidebar and content. 10 | 11 | ``` 12 | return ( 13 | 14 | 15 | 16 | {content} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | ``` 26 | 27 | Now `npm run start` see the sidebar is on the right side. 28 | -------------------------------------------------------------------------------- /website/docs/guide_customize_progressive.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_progressive 3 | title: Progressive 4 | sidebar_label: Progressive Customization 5 | --- 6 | 7 | Website created by Dochameleon works out-of-box. You can just write Markdown documents and blogs. 8 | 9 | Depend on needs, you can customize the website progressively, from simply changing color to completely replace the core component rendering. 10 | 11 | ![progressive](/static/img/progressive.png) 12 | -------------------------------------------------------------------------------- /website/docs/guide_customize_react.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_react 3 | title: React Component 4 | sidebar_label: React Component 5 | --- 6 | 7 | Write web pages in React, then Dochameleon will convert them into static HTML files. 8 | 9 | ### Pages 10 | 11 | Write web pages in `website/pages` folder. Each file exports an React component which will be rendered as one page. 12 | 13 | ### Components 14 | 15 | You may write custom React components in `website/components` folder, and use them in pages. 16 | 17 | Core library has React components too. At runtime, Dochameleon merges the two sets together. 18 | 19 | Take a look at example site `components` folder. It has number of components. Among them, `Footer.js` will overwrite the core Footer. Try remove it, and run `npm run start`, you'll notice default footer is different. 20 | -------------------------------------------------------------------------------- /website/docs/guide_customize_theme.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_theme 3 | title: UI Theme 4 | sidebar_label: UI Theme 5 | --- 6 | 7 | Like mentioned in [Customization - Color Scheme](./guide_color_scheme.html), Dochameleon defines UI style in `/theme` folder, in Javascript. 8 | 9 | ### Core and Custom Theme 10 | 11 | Core theme located at `lib/core/theme` folder of the core library. Custom theme located at `website/theme` folder. Each file exports an object that defines a set of UI styles. 12 | 13 | Two steps happening at runtime. 14 | 15 | First step, files in the two folder will be merged together. If custom theme file has same name as core theme file then it will overwrite the core file. 16 | 17 | Second step, Dochameleon reads all theme files and merge all objects into one final object. This is the theme object, contains defination of all UI styles. 18 | 19 | ### Customization 20 | 21 | Customization is simply write theme files in `website/theme` folder. 22 | 23 | Create a file `website/theme/custom.js` 24 | ``` 25 | const custom = { 26 | button: { 27 | margin: '4px', 28 | border: '1px solid blue', 29 | borderRadius: '3px', 30 | color: 'blue', 31 | display: 'inline-block', 32 | fontSize: '14px', 33 | fontWeight: '400', 34 | lineHeight: '1.2em', 35 | padding: '10px', 36 | textTransform: 'uppercase', 37 | textDecoration: 'none', 38 | transition: 'background 0.3s, color 0.3s' 39 | } 40 | } 41 | 42 | module.exports = custom; 43 | ``` 44 | 45 | run dev server again, `npm run start`. See how buttons look like now. 46 | -------------------------------------------------------------------------------- /website/docs/guide_github.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_github 3 | title: Project on GitHub 4 | sidebar_label: Project on GitHub 5 | --- 6 | 7 | 8 | 9 | Dochameleon is aware of GitHub conventions. Follow this steps to quickly create website for your GitHub project. 10 | 11 | ### Installation 12 | 13 | Just make sure run `dochameleon-init` at your project repo root. 14 | 15 | ``` 16 | > cd $path_to_project$ 17 | > dochameleon-init 18 | > cd website 19 | > npm run start 20 | ``` 21 | 22 | ### Configuration 23 | 24 | Modify `siteConfig.js` with your project information, particularly: 25 | 26 | * projectName 27 | * title 28 | * copyright 29 | * rootUrl 30 | * baseUrl 31 | * icon 32 | * favicon 33 | 34 | ### Docs 35 | 36 | By default Dochameleon loads docs from `website/docs`. You may copy files over, or just change the config. For example, by GitHub convention it is the `docs` folder under your repo. Add this line to your `website/siteConfig.js` 37 | 38 | ``` 39 | docsDir: '../docs' 40 | ``` 41 | 42 | **Caution:** Do not set docsDir and buildDir to the same path, since building process would remove and add files. 43 | 44 | ### Assets 45 | 46 | In case you have assets like images, put them under `docs/assets` folder of your repo. Dochameleon will keep them so the references still work as usual. 47 | 48 | ### Home Page 49 | 50 | You may want to make README.md as home page instead of the default one. 51 | 52 | The library actually has already created a `readme.html` page with your README.md. You may just copy this source code 53 | 54 | ``` 55 | cp node_modules/dochameleon/lib/core/pages/readme.js pages/index.js 56 | ``` 57 | 58 | Now `npm run start`. 59 | -------------------------------------------------------------------------------- /website/docs/guide_i18n.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_i18n 3 | title: Multi-Language 4 | sidebar_label: Multi-Language 5 | --- 6 | 7 | 8 | 9 | For multi-language support, just create folder `website/i18n` then have language folders below it. Language code follow [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) standard. 10 | 11 | ### Configure 12 | 13 | Create `i18n` folder along with `siteConfig.js`, then language folders below it. 14 | 15 | ```bash 16 | website/ 17 | └── i18n/ 18 |     ├── zh/ 19 |    └── fr/ 20 | ``` 21 | 22 | ### Dictionary 23 | 24 | Under each language folder, create as many js file as you want, each exports an object of vocabulary map. Dochameleon will merge all objects into one big dictionary object to be used for translation. 25 | 26 | For example: 27 | 28 | `website/i18n/zh/blog.js` 29 | ``` 30 | const blog = { 31 | 'Why Dochameleon': '为什么Dochameleon', 32 | 'Staging Step': '准备步骤', 33 | 'Dochameleon.io is Live': 'Dochameleon.io上线了', 34 | 'Pages': '网页', 35 | 'Static Files': '静态文件', 36 | 'Configure Source Folders': '配置源文件路径' 37 | }; 38 | 39 | module.exports = blog; 40 | ``` 41 | 42 | ### Docs Translation 43 | 44 | A dictionary is not enough to translate a full document. Create `docs` folder under language folder and have translated documentation. Make sure to have same document id in metadata section of the file. 45 | 46 | For example look at [here](https://github.com/richardzcode/Dochameleon/tree/master/website/i18n/zh/docs) 47 | 48 | ### {lang} Parameter and Translation 49 | 50 | A {lang} parameter is passed down to all components for rendering. When developing your own component, make sure to get and pass down {lang} parameter as well. 51 | 52 | When render, call `site.i18n.translate(token, lang)` to get translation. 53 | -------------------------------------------------------------------------------- /website/docs/guide_installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_installation 3 | title: Installation 4 | sidebar_label: Installation 5 | --- 6 | 7 | 8 | 9 | ### Install via script 10 | 11 | The easiest way to install Dochameleon is by using script `dochameleon-init` 12 | 13 | First install the script 14 | 15 | ``` 16 | npm install --global dochameleon-init 17 | ``` 18 | 19 | Then, go to the folder that your'd like to create the documentation website, run 20 | 21 | ``` 22 | dochameleon-init 23 | cd website 24 | npm run start 25 | ``` 26 | 27 | Now check http://localhost:3000, the website is up running! 28 | 29 | The init script does three things: 30 | * create `website` folder; 31 | * npm install `dochameleon` package; 32 | * copy a basic example website to start from. 33 | 34 | 35 | ### Manual installation 36 | 37 | If you do not want `dochameleon-init`, rather create website manually. 38 | 39 | - Create and go into the `website` folder 40 | - Create package.json with content, 41 | ``` 42 | { 43 | "scripts": { 44 | "examples": "dochameleon-examples" 45 | } 46 | } 47 | ``` 48 | - Install Dochameleon, run 49 | ``` 50 | npm install dochameleon 51 | ``` 52 | 53 | - Init with basic example, run 54 | ``` 55 | npm run examples 56 | ``` 57 | 58 | ### Run Local Dev Server 59 | 60 | Once installation completed, a website with example content is ready to go. Just run 61 | 62 | ``` 63 | npm run start 64 | ``` 65 | 66 | ### File Structure 67 | 68 | Once installation successful, here is the file structure you'll have under the `website` folder 69 | 70 | ```bash 71 | website/ 72 | ├── blog/ 73 | │   ├── 2018-01-08-why-dochameleon.md 74 | │   └── 2018-01-10-staging-step.md 75 | ├── components/ 76 | ├── docs/ 77 | │   ├── doc1.md 78 | │   ├── doc2.md 79 | │   ├── doc3.md 80 | │   └── sidebars.json 81 | ├── pages/ 82 | │   ├── help.js 83 | │   ├── index.js 84 | │   └── users.js 85 | ├── siteConfig.js 86 | ├── static/ 87 | └── theme/ 88 | ``` 89 | -------------------------------------------------------------------------------- /website/docs/guide_search.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_search 3 | title: Search 4 | sidebar_label: Search 5 | --- 6 | 7 | 8 | 9 | Dochameleon support search by [Algolia](https://www.algolia.com). Algolia is super easy to setup, and its community plan is free. 10 | 11 | ### Configure 12 | 13 | After setup Algolia for your project. Add `searchConfig.js` along with `siteConfig.js` 14 | 15 | ```bash 16 | website/ 17 | └── searchConfig.js 18 | ``` 19 | 20 | Content similiar to this: 21 | ``` 22 | const searchConfig = { 23 | application_id: ..., 24 | search_api_key: ..., 25 | api_key: ..., 26 | js: 'https://cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js' 27 | } 28 | 29 | module.exports = searchConfig; 30 | ``` 31 | 32 | Make sure searchConfig.js is in your `.gitignore`. You want to keep api_key safe. 33 | 34 | ### Add to Menu 35 | 36 | Modify `siteConfig.js`, add this entry to headerLinks: 37 | ``` 38 | {type: 'search'}, 39 | ``` 40 | 41 | ### Indexing 42 | 43 | Indexing happens at site generation time. When you run `npm run build`, the latest docs are pushed to Algolia. 44 | -------------------------------------------------------------------------------- /website/docs/guide_site_creation.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_site_creation 3 | title: Site Creation 4 | sidebar_label: Site Creation 5 | --- 6 | 7 | 8 | 9 | ### Docs 10 | 11 | ```bash 12 | website/ 13 | └── docs/ 14 |     ├── doc1.md 15 |    ├── doc2.md 16 |    ├── doc3.md 17 |    └── sidebars.json 18 | ``` 19 | Write documentation under `website/docs` folder, with Markdown. 20 | 21 | At beginning of each doc file, add metadata like this: 22 | 23 | ``` 24 | --- 25 | id: doc1 26 | title: Latin-ish 27 | sidebar_label: Example Page 28 | --- 29 | ``` 30 | 31 | #### Sidebar 32 | 33 | Sidebar menu is defined in `sidebars.json` 34 | 35 | ### Blog 36 | 37 | ```bash 38 | website/ 39 | └── blog/ 40 |    ├── 2018-01-08-why-dochameleon.md 41 |    └── 2018-01-10-staging-step.md 42 | ``` 43 | Write blog posts under `website/blog` folder, with Markdown. 44 | 45 | File name must have the format of `yyyy-mm-dd-blog-file-name.md` 46 | 47 | At begging of each blog post, add metadata like this: 48 | 49 | ``` 50 | --- 51 | title: Why Dochameleon 52 | author: Richard Zhang 53 | authorUrl: https://github.com/richardzcode 54 | authorImage: https://github.com/richardzcode.png 55 | --- 56 | ``` 57 | 58 | authorUrl and authorImage can be set by name/id of GitHub, Facebook, or Twitter, for example: 59 | 60 | ``` 61 | authorGitHub: richardzcode 62 | ``` 63 | 64 | or 65 | 66 | ``` 67 | authorFBID: ... 68 | ``` 69 | 70 | or 71 | 72 | ``` 73 | authorTwitter: ... 74 | ``` 75 | 76 | ### Pages 77 | 78 | ```bash 79 | website/ 80 | ├── components/ 81 | ├── pages/ 82 | │   ├── help.js 83 | │   ├── index.js 84 | │   └── users.js 85 | └── theme/ 86 | ``` 87 | Create web pages under `website/pages` folder, by writing React. 88 | 89 | Create components under `website/components`, for pages to import. 90 | 91 | Write theme files under `website/theme`, to define styles. 92 | 93 | ### Static Files 94 | 95 | ```bash 96 | website/ 97 | └── static/ 98 | ``` 99 | 100 | Put static files under `website/static` folder. They will go to `${rootUrl}${baseUrl}/` 101 | 102 | You may refer to static files in docs as `/static/...`, for example `[Logo]( /static/img/logo.png)` 103 | 104 | ### Configure Source Folders 105 | 106 | By default Dochameleon looks up sources under `website` which is the current commandline directory. All source folders are configurable. For example you may want to change 'docs' folder to GitHub repo '/docs'. 107 | 108 | Set source folders in `siteConfig.js` 109 | 110 | ``` 111 | ... 112 | 113 | const siteConfig = { 114 | projectName: 'Dochameleon', 115 | title: 'Dochameleon', 116 | tagline: 'Open Source Documentation Site Generator', 117 | copyright: 'Copyright © ' + currentYear + ' Richard Zhang', 118 | 119 | docsDir: '../docs', 120 | pagesDir: ..., 121 | blogDir: ..., 122 | componentsDir: ..., 123 | staticDir: ..., 124 | themeDir: ..., 125 | 126 | ... 127 | ``` 128 | -------------------------------------------------------------------------------- /website/docs/guide_site_publishing.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_site_publishing 3 | title: Site Publishing 4 | sidebar_label: Site Publishing 5 | --- 6 | 7 | 8 | 9 | Dochameleon converts Markdown and React source files into static HTML files. Then you put them on any of the web hosting solution, GitHub, Amazon S3, etc. 10 | 11 | ### Build 12 | 13 | ``` 14 | npm run build 15 | ``` 16 | 17 | Then all HTML files will be generated into `website/build` folder. 18 | 19 | ### Hosting 20 | 21 | Now you may get all of the files inside `website/build` and copy over to web hosting. 22 | 23 | #### GitHub Pages 24 | 25 | [GitHub Pages](https://pages.github.com/) is a very natural hosting choice for open source projects. To publish to GitHub Pages, 26 | 27 | 1. Follow steps to create your GitHub Pages. Select "master branch /docs folder" as `Source` 28 | 29 | 2. Copy `website/build/{projectName}` folder to your project `/docs` folder. 30 | 3. Commit and push your git repo. 31 | 32 | That's it! 33 | 34 | ##### Skip copy step 35 | 36 | By default build command generates HTML files into `website/build/{projectName}` folder. You may configure the destination to another place. For example, to GitHub project 'docs' folder so no copy needed before publish. 37 | 38 | ``` 39 | ... 40 | 41 | const siteConfig = { 42 | projectName: 'Dochameleon', 43 | title: 'Dochameleon', 44 | tagline: 'Open Source Documentation Site Generator', 45 | copyright: 'Copyright © ' + currentYear + ' Richard Zhang', 46 | 47 | buildDir: '../docs', 48 | 49 | ... 50 | ``` 51 | 52 | **Caution:** During build Dochameleon will remove files in buildDir. Make sure `../docs` doesn't have files you needed for other purposes. 53 | -------------------------------------------------------------------------------- /website/docs/sidebars.json: -------------------------------------------------------------------------------- 1 | { 2 | "docs": { 3 | "Getting Started": [ 4 | "guide_installation", 5 | "guide_configuration", 6 | "guide_site_creation", 7 | "guide_site_publishing", 8 | "guide_github" 9 | ], 10 | "Customization": [ 11 | "guide_progressive", 12 | "guide_color_scheme", 13 | "guide_theme", 14 | "guide_react", 15 | "guide_core" 16 | ], 17 | "Advanced": [ 18 | "guide_search", 19 | "guide_analytics", 20 | "guide_i18n" 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /website/i18n/zh/blog.js: -------------------------------------------------------------------------------- 1 | const blog = { 2 | 'Why Dochameleon': '为什么Dochameleon', 3 | 'Staging Step': '准备步骤', 4 | 'Dochameleon.io is Live': 'Dochameleon.io上线了', 5 | 'Pages': '网页', 6 | 'Static Files': '静态文件', 7 | 'Configure Source Folders': '配置源文件路径' 8 | }; 9 | 10 | module.exports = blog; 11 | -------------------------------------------------------------------------------- /website/i18n/zh/docs.js: -------------------------------------------------------------------------------- 1 | const docs = { 2 | 'Configuration': '配置', 3 | 'Home Page': '主页' 4 | }; 5 | 6 | module.exports = docs; 7 | -------------------------------------------------------------------------------- /website/i18n/zh/docs/guide_installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: guide_installation 3 | title: 安装指南 4 | sidebar_label: 安装指南 5 | --- 6 | 7 | 8 | 9 | ### 用脚本安装 10 | 11 | 安装Dochameleon最简单的方式,运用脚本`dochameleon-init` 12 | 13 | 首先安装脚本 14 | 15 | ``` 16 | npm install --global dochameleon-init 17 | ``` 18 | 19 | 然后,进入想要创建文档网站的文件夹,运行 20 | 21 | ``` 22 | dochameleon-init 23 | cd website 24 | npm run start 25 | ``` 26 | 27 | 脚本完成三件任务 28 | * 创建`website`文件夹 29 | * npm安装`dochameleon`包 30 | * 拷贝样板网站 31 | 32 | 33 | ### 手工安装 34 | 35 | 如果你不想使用脚本,更愿意手工创建网站 36 | - 创建并进入`website`文件夹 37 | - 编写package.json,内容如下, 38 | 39 | ``` 40 | { 41 | "scripts": { 42 | "examples": "dochameleon-examples" 43 | } 44 | } 45 | ``` 46 | - 安装Dochameleon, 运行 47 | ``` 48 | npm install dochameleon 49 | ``` 50 | 51 | - 初始化基本样板, 运行 52 | ``` 53 | npm run examples 54 | ``` 55 | 56 | ### 运行本地开发服务器 57 | 58 | 安装完成之后,一个样板网站已经可以使用了。运行 59 | 60 | ``` 61 | npm run start 62 | ``` 63 | 64 | ### 文件结构 65 | 66 | 安装成功之后,`website`下文件结构是这样的, 67 | 68 | ```bash 69 | website/ 70 | ├── blog/ 71 | │   ├── 2018-01-08-why-dochameleon.md 72 | │   └── 2018-01-10-staging-step.md 73 | ├── components/ 74 | ├── docs/ 75 | │   ├── doc1.md 76 | │   ├── doc2.md 77 | │   ├── doc3.md 78 | │   └── sidebars.json 79 | ├── pages/ 80 | │   ├── help.js 81 | │   ├── index.js 82 | │   └── users.js 83 | ├── siteConfig.js 84 | ├── static/ 85 | └── theme/ 86 | ``` 87 | -------------------------------------------------------------------------------- /website/i18n/zh/features.js: -------------------------------------------------------------------------------- 1 | const features = { 2 | 'Powered by Markdown': 'Markdown编辑', 3 | 'Built Using React': '使用React构建', 4 | 'Progressive Customization': '渐进式定制', 5 | 'Quick Setup': '快速安装', 6 | 'Develop and Deploy': '开发与发布', 7 | 'markdown.content': [ 8 | '用[Markdown](https://guides.github.com/features/mastering-markdown/)编辑文档和博客。', 9 | 'Dochameleon转化文章为HTML文件' 10 | ], 11 | 'react.content': '编写自己的[React](https://reactjs.org/)构件,为你的项目延伸和定制网站', 12 | 'customization.content': '安装即可用,不同层级定制直至更换核心构件。', 13 | 'quick_setup.content': [ 14 | '快速安装,几分钟搞定', 15 | '\n', 16 | '\n```bash', 17 | '\n> npm install -g dochameleon-init', 18 | '\n> dochameleon-init', 19 | '\n> cd website', 20 | '\n> npm run start', 21 | '\n```' 22 | ], 23 | 'develop_deploy.content': [ 24 | '自带本地服务器帮助开发;', 25 | '发布到GitHub,或任何静态网站托管服务' 26 | ], 27 | 'Who\'s Using This?': '谁在使用', 28 | 'This project is used by all these people': '这个项目被用在这些项目中' 29 | } 30 | 31 | module.exports = features; 32 | -------------------------------------------------------------------------------- /website/i18n/zh/langConfig.js: -------------------------------------------------------------------------------- 1 | const langConfig = { 2 | label: '中文' 3 | }; 4 | 5 | module.exports = langConfig; 6 | -------------------------------------------------------------------------------- /website/i18n/zh/languages.js: -------------------------------------------------------------------------------- 1 | const languages = { 2 | 'en': '英文', 3 | 'zh': '中文' 4 | }; 5 | 6 | module.exports = languages; 7 | -------------------------------------------------------------------------------- /website/i18n/zh/menu.js: -------------------------------------------------------------------------------- 1 | const menu = { 2 | 'Docs': '文档', 3 | 'Help': '帮助', 4 | 'Blog': '博客', 5 | 'Getting Started': '从哪开始', 6 | 'Installation': '安装指南', 7 | 'Site Configuration': '配置网站', 8 | 'Site Creation': '充实网站', 9 | 'Site Publishing': '发布网站', 10 | 'Project on GitHub': 'GitHub上的项目', 11 | 'Customization': '定制', 12 | 'Customize': '定制', 13 | 'Progressive Customization': '渐进式定制', 14 | 'Progressive': '渐进式', 15 | 'Color Scheme': '色调', 16 | 'UI Theme': '主题', 17 | 'React Component': 'React构件', 18 | 'Core Component': '核心构件', 19 | 'More': '更多', 20 | 'Advanced': '更多', 21 | 'Search': '搜索', 22 | 'Analytics': '分析', 23 | 'Multi-Language': '多种语言', 24 | 'Recent Posts': '最新文章', 25 | 'Read More': '阅读更多', 26 | 'Community': '社区', 27 | 'Guides': '开发指南', 28 | 'User Showcase': '用户列表', 29 | 'Twitter': '推特' 30 | }; 31 | 32 | module.exports = menu; 33 | -------------------------------------------------------------------------------- /website/i18n/zh/project.js: -------------------------------------------------------------------------------- 1 | const project = { 2 | tagline: '开源文档静态网站生成工具' 3 | }; 4 | 5 | module.exports = project; 6 | -------------------------------------------------------------------------------- /website/i18n/zh/translate.js: -------------------------------------------------------------------------------- 1 | const translate = { 2 | 'Create pull request to add your logo': 3 | '创建一个pull request加入你的网站图标' 4 | }; 5 | 6 | module.exports = translate; 7 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "examples": "dochameleon-examples", 4 | "start": "dochameleon-start", 5 | "build": "dochameleon-build", 6 | "publish-gh-pages": "dochameleon-publish" 7 | }, 8 | "devDependencies": { 9 | "dochameleon": "file:../dochameleon-0.0.44.tgz" 10 | }, 11 | "dependencies": {} 12 | } 13 | -------------------------------------------------------------------------------- /website/pages/help.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | const HelpDetails = require('../components/HelpDetails.js'); 4 | 5 | class Help extends React.Component { 6 | render() { 7 | const { site, lang } = this.props; 8 | const { theme } = site; 9 | return ( 10 |
    11 |
    12 |

    Need help?

    13 |

    This project is maintained by a dedicated group of people.

    14 |
    15 | 16 |
    17 | ); 18 | } 19 | } 20 | 21 | module.exports = Help; 22 | -------------------------------------------------------------------------------- /website/pages/index.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | const HomeSplash = require('../components/HomeSplash.js'); 4 | const Features = require('../components/Features.js'); 5 | const Callout = require('../components/Callout.js'); 6 | const Showcase = require('../components/Showcase.js'); 7 | 8 | const callouts = require('../components/callouts.json'); 9 | 10 | class Index extends React.Component { 11 | render() { 12 | const { site, lang } = this.props; 13 | const { theme } = site; 14 | return ( 15 |
    16 | 17 |
    18 |
    19 | 20 |
    21 |
    22 | 23 |
    24 |
    25 | 26 |
    27 |
    28 | ); 29 | } 30 | } 31 | 32 | module.exports = Index; 33 | -------------------------------------------------------------------------------- /website/pages/languages.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { Row, Col } = require('fluid-react'); 3 | 4 | const MarkdownBlock = require('../components/MarkdownBlock.js'); 5 | 6 | const no_languages_content = ` 7 | To have multi-language, write translations under \`website/i18n/$lang_code$\` 8 | 9 | [Example](https://github.com/richardzcode/Dochameleon/tree/master/website/i18n) 10 | `; 11 | 12 | class Languages extends React.Component { 13 | render() { 14 | const { site, lang } = this.props; 15 | const { theme } = site; 16 | const langs = site.i18n.langs(); 17 | const languages = langs && langs.length > 0 18 | ? langs.map((language, i) => { 19 | return ( 20 | 21 | 22 | {site.i18n.translate(language, lang)} 23 | 24 | 25 | ); 26 | }) 27 | : {no_languages_content} 28 | 29 | return ( 30 |
    31 |
    32 | 33 | {languages} 34 | 35 |
    36 |
    37 | ); 38 | } 39 | } 40 | 41 | module.exports = Languages; 42 | -------------------------------------------------------------------------------- /website/pages/users.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { JS} = require('fsts'); 3 | 4 | const Showcase = require('../components/Showcase.js'); 5 | 6 | const user_json = 'https://github.com/richardzcode/Dochameleon/blob/master/website/components/users.json'; 7 | 8 | class Users extends React.Component { 9 | render() { 10 | const { site, lang } = this.props; 11 | const { theme } = site; 12 | 13 | const cta = site.i18n.translate('Create pull request to add your logo', lang); 14 | return ( 15 |
    16 |
    17 | 18 |
    19 |
    20 |
    21 | ); 22 | } 23 | } 24 | 25 | module.exports = Users; 26 | -------------------------------------------------------------------------------- /website/siteConfig.js: -------------------------------------------------------------------------------- 1 | const currentYear = new Date().getFullYear(); 2 | 3 | const siteConfig = { 4 | projectName: 'Dochameleon', 5 | title: 'Dochameleon', 6 | tagline: 'Open Source Documentation Site Generator', 7 | copyright: 'Copyright © ' + currentYear + ' Richard Zhang', 8 | 9 | rootUrl: 'https://richardzcode.github.io', 10 | baseUrl: '', 11 | 12 | icon: 'img/dochameleon.png', 13 | favicon: 'img/favicon.png', 14 | 15 | js: [ 16 | { src: 'https://buttons.github.io/buttons.js', async: true } 17 | ], 18 | 19 | repoDir: '../', 20 | buildDir: '../docs' 21 | }; 22 | 23 | module.exports = siteConfig; 24 | -------------------------------------------------------------------------------- /website/static/img/f-r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/website/static/img/f-r.png -------------------------------------------------------------------------------- /website/static/img/fsts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/website/static/img/fsts.png -------------------------------------------------------------------------------- /website/static/img/github_pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Dochameleon/18d221ab16288ae04e9bc6ae8feb735d0825b9cc/website/static/img/github_pages.png -------------------------------------------------------------------------------- /website/theme/pages.js: -------------------------------------------------------------------------------- 1 | const color = require('./color.js'); 2 | 3 | const pages = { 4 | block: { 5 | padding: '30px 10px' 6 | }, 7 | blockEven: { 8 | padding: '30px 10px', 9 | background: '#e9e9e9' 10 | }, 11 | homeSplash: { 12 | padding: '2em 10px', 13 | textAlign: 'center' 14 | }, 15 | promoSection: { 16 | display: 'flex', 17 | flexFlow: 'row wrap', 18 | justifyContent: 'center', 19 | fontSize: '125%', 20 | lineHeight: '1.6em', 21 | position: 'relative', 22 | zIndex: '99' 23 | }, 24 | featureImageContainer: { 25 | textAlign: 'center' 26 | }, 27 | featureImage: { 28 | maxHeight: '80px' 29 | }, 30 | calloutTitle: { 31 | textAlign: 'left', 32 | textDecoration: 'none', 33 | color: color.title, 34 | fontWeight: '300', 35 | fontSize: '180%', 36 | lineHeight: '1em' 37 | }, 38 | calloutImageContainer: { 39 | textAlign: 'center' 40 | }, 41 | calloutImage: { 42 | maxWidth: '80%' 43 | }, 44 | showcaseBox: { 45 | display: 'block', 46 | padding: '20px', 47 | width: '80px', 48 | textAlign: 'center' 49 | }, 50 | showcaseImage: { 51 | maxWidth: '100%', 52 | maxHeight: '80px' 53 | }, 54 | helpTitle: { 55 | textAlign: 'left', 56 | color: color.title, 57 | fontWeight: '300', 58 | fontSize: '200%', 59 | lineHeight: '1em' 60 | }, 61 | helpSection: { 62 | padding: '20px' 63 | }, 64 | helpSectionTitle: { 65 | textAlign: 'left', 66 | color: color.title, 67 | fontWeight: '300', 68 | fontSize: '150%', 69 | lineHeight: '1em' 70 | } 71 | }; 72 | 73 | module.exports = pages; 74 | --------------------------------------------------------------------------------