├── .gitattributes ├── .gitignore ├── .prettierignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets ├── badge.coverage.svg ├── badge.license.svg ├── badge.npm.svg └── badge.style.svg ├── docs ├── CHANGELOG.md ├── CONTRIBUTING.md ├── README.md ├── examples │ ├── index.md │ ├── more tests │ │ └── plain - text file.txt │ ├── readme2.md │ ├── subfolder │ │ └── more-infos.md │ ├── subfolder2 │ │ ├── autoincluded.html │ │ └── subsub │ │ │ └── furtherincluded.md │ └── test.md ├── installation.md ├── public │ ├── .gitkeep │ └── images │ │ └── narrative-794978_1920.jpg └── usage.md ├── front ├── lib │ ├── assets │ │ ├── css │ │ │ └── app.scss │ │ ├── images │ │ │ ├── favicon.afdesign │ │ │ ├── favicon.ico │ │ │ └── favicon.ico.png │ │ ├── templates │ │ │ └── layout.ejs.html │ │ └── vendor │ │ │ ├── highlight.js │ │ │ ├── highlight.css │ │ │ └── highlight.min.js │ │ │ └── modernizr.min.js │ ├── bootstrap.tsx │ ├── components │ │ ├── app.tsx │ │ ├── header.tsx │ │ ├── mobileMenuButton.tsx │ │ ├── nav │ │ │ ├── chapter.tsx │ │ │ ├── index.tsx │ │ │ └── toc.tsx │ │ └── observer.ts │ ├── main.ts │ ├── models │ │ └── tocItem.ts │ ├── store │ │ ├── computed.ts │ │ ├── index.ts │ │ ├── mutator.ts │ │ └── router │ │ │ ├── index.ts │ │ │ ├── state.ts │ │ │ └── toc.ts │ ├── util │ │ ├── history.ts │ │ └── pageTitle.ts │ └── vendor.ts └── package.json ├── lib ├── config.js ├── main.js ├── markdown.js ├── toc.js └── util.js ├── mallery.config.js ├── package-lock.json ├── package.json ├── scripts ├── badges.js ├── before-publish.js ├── build-core.js ├── build-front.js ├── build.js ├── check-gyp.js ├── check-license.js ├── cleanup.js ├── coverage.js ├── create-type-proxies.js ├── lib.js ├── module.js ├── reformat.js ├── site-subtree.js ├── site.js ├── start.js └── test.js ├── site ├── .gitkeep ├── CHANGELOG.html ├── CONTRIBUTING.html ├── assets │ ├── badge.coverage.svg │ ├── badge.license.svg │ ├── badge.npm.svg │ └── badge.style.svg ├── examples │ ├── index9.html │ ├── more-tests │ │ └── plain---text-file.html │ ├── readme2.html │ ├── subfolder │ │ └── more-infos.html │ ├── subfolder2 │ │ ├── autoincluded.html │ │ └── subsub │ │ │ └── furtherincluded.html │ └── test.html ├── favicon.ico ├── images │ └── narrative-794978_1920.jpg ├── index.html ├── installation.html ├── main.css ├── main.js ├── main.js.map ├── raw │ ├── CHANGELOG.html │ ├── CONTRIBUTING.html │ ├── examples │ │ ├── index9.html │ │ ├── more-tests │ │ │ └── plain---text-file.html │ │ ├── readme2.html │ │ ├── subfolder │ │ │ └── more-infos.html │ │ ├── subfolder2 │ │ │ ├── autoincluded.html │ │ │ └── subsub │ │ │ │ └── furtherincluded.html │ │ └── test.html │ ├── index.html │ ├── installation.html │ └── usage.html └── usage.html └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | docs/* linguist-documentation 2 | templates/* linguist-detectable=false 3 | coverage/* linguist-detectable=false 4 | scripts/* linguist-detectable=false 5 | dist/* linguist-generated=true 6 | site/* linguist-generated=true 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /node_modules 3 | /.vscode 4 | dist 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | /site 3 | /docs 4 | /coverage 5 | /CHANGELOG.md -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [1.0.0-alpha.7](https://github.com/malleryjs/mallery/compare/v1.0.0-alpha.6...v1.0.0-alpha.7) (2020-06-06) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * add hash bang to main entry ([2521697](https://github.com/malleryjs/mallery/commit/2521697b5705eb6f9bed386bea1724f3710ce86e)) 11 | 12 | ## [1.0.0-alpha.6](https://github.com/malleryjs/mallery/compare/v1.0.0-alpha.5...v1.0.0-alpha.6) (2020-06-06) 13 | 14 | ## [1.0.0-alpha.5](https://github.com/malleryjs/mallery/compare/v1.0.0-alpha.4...v1.0.0-alpha.5) (2020-06-06) 15 | 16 | 17 | ### Bug Fixes 18 | 19 | * reduce transparency of navigation item bottom border ([10b8e2c](https://github.com/malleryjs/mallery/commit/10b8e2cdc3a4a01b540dfaa2d2a089acf79fcc6f)) 20 | 21 | ## v1.0.0-alpha.4 22 | - BUGFIX: Escape element ids with leading numbers when querying in the dom 23 | 24 | ## v1.0.0-alpha.0 25 | - Rewrote most of the code to Typescript 26 | - Migrated from Redom to Preact 27 | - Upgraded alo to 4.0 28 | - Upgraded bootstrap to 4.4 29 | - Switched from handlebars to ejs 30 | - Migrated to [witney](https://github.com/witneyjs/witney) project template 31 | 32 | ## v0.0.3 33 | - [#5](https://github.com/malleryjs/mallery/issues/5): Fixed: Scroll top wasn't called, when the toc item changed while no chapter was selected 34 | 35 | ## v0.0.2 36 | - [#1](https://github.com/malleryjs/mallery/issues/1): Improved scrolling with mobile safari 37 | - Added [modernizr](https://modernizr.com/) 38 | 39 | ## v0.0.1 40 | - Initial release. Everything is new and shiny! 41 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Building 4 | 5 | This projects includes all its development scripts in the script folder. 6 | 7 | Mostly used scripts: 8 | 9 | - Build Code: `./scripts/build.js` 10 | - Reformat Code: `./scripts/reformat.js` 11 | - Run tests: `./scripts/test.js` 12 | - Build docs html: `./scripts/docs.js` 13 | 14 | ## Releasing 15 | 16 | Before releasing make sure that all changes for the release are committed and in the proper branch merged. 17 | 18 | To start the release workflow, run: `./scripts/before-publish.js` 19 | This workflow will run through: 20 | 21 | - Reformat 22 | - Build 23 | - Test & Coverage 24 | 25 | Afterwards you are notified that you should run: `npx standard-version -a` 26 | This increases the version in package.json, updates README.md CHANGELOG.md and docs and adds a new git tag with the proper version. 27 | 28 | Then push the tag (something like `git push --tags`) and if needed publish to npm with: `npm publish` 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Katja Lutz 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [🧢 Mallery](https://github.com/malleryjs/mallery) 2 | 3 | [![](assets/badge.license.svg)](https://opensource.org/licenses/MIT) 4 | [![](assets/badge.npm.svg)](https://www.npmjs.com/package/mallery) 5 | [![](assets/badge.style.svg)](https://prettier.io/) 6 | 7 | --- 8 | 9 | Static documentation site generator 10 | 11 | This is a free and opensource static website generator for 12 | project documentations. Mallery automatically creates a static documentation 13 | website from the contents of your choosen folder. The output will look 14 | similar to [this website](http://www.malleryjs.com). 15 | 16 | ## Features 17 | 18 | - A slim and general purpose theme 19 | - Flexible and responsive navigation 20 | - History control (last visited pages) 21 | - Previous & Next page buttons 22 | - Several options for individualization 23 | - The exported HTML of markdown files is extended with bootstrap annotation 24 | 25 | ## Ideas / Future plans 26 | 27 | - Just work on the website and test Mallery 28 | - Improving error handling 29 | - Loadbar 30 | - Non-webserver handling (open the html files locally without server) 31 | - Enable minification of the js and css files (they are just called `min` :P) 32 | - Serve mode: Add a CLI option which watches the source folder 33 | - Add a hide navigation button for large desktops 34 | - Search through the navigation 35 | (probably with a custom search index via configuration file) 36 | - Numbered headings 37 | 38 | ## The name 39 | 40 | The name was choosen randomly when I was reading through [Wikipedia](https://en.wikipedia.org/wiki/CL-HTTP). 41 | 42 | ## Creator 43 | 44 | Creator of this tool is [Katja Lutz](https://twitter.com/Katy_Wings) 45 | -------------------------------------------------------------------------------- /assets/badge.coverage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | coverage 14 | coverage 15 | 100% 16 | 100% 17 | 18 | 19 | -------------------------------------------------------------------------------- /assets/badge.license.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | license 14 | license 15 | MIT 16 | MIT 17 | 18 | 19 | -------------------------------------------------------------------------------- /assets/badge.npm.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | npm 14 | npm 15 | 1.0.0-alpha.7 16 | 1.0.0-alpha.7 17 | 18 | 19 | -------------------------------------------------------------------------------- /assets/badge.style.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | code style 14 | code style 15 | prettier 16 | prettier 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ../CHANGELOG.md -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ../CONTRIBUTING.md -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /docs/examples/index.md: -------------------------------------------------------------------------------- 1 | # Welcome to the cool place 2 | 3 | This is huge :) 4 | 5 | 6 | ## 1: Id starting with a number 7 | 8 | Commodo elit mollit incididunt veniam cillum laboris. Minim duis elit veniam esse tempor ea id fugiat consequat elit. Tempor voluptate aute dolor sunt ea eiusmod irure minim velit anim minim irure culpa. Proident duis laboris et veniam magna mollit laboris culpa eiusmod. Incididunt ea in pariatur laborum laborum amet elit incididunt voluptate cupidatat officia in eu dolor. Consequat velit dolor voluptate fugiat voluptate reprehenderit minim ut. 9 | 10 | Duis proident exercitation aute eiusmod dolore veniam incididunt fugiat enim aute laborum dolore proident. Esse reprehenderit exercitation sit consequat minim ea exercitation ea reprehenderit esse velit pariatur consectetur quis. Laborum veniam cillum sunt adipisicing anim do ea anim nulla pariatur dolore. Adipisicing irure proident enim irure incididunt laboris enim cillum enim voluptate veniam. Elit pariatur sunt id ex laboris anim irure ea laborum. Consectetur non incididunt aliquip nulla mollit. 11 | 12 | Dolore consequat non incididunt minim dolor in excepteur velit anim voluptate. Proident ullamco quis amet in Lorem aliquip. Ut occaecat aliquip do reprehenderit pariatur elit sunt tempor nulla excepteur eiusmod nostrud cillum reprehenderit. Consequat duis amet consectetur eu. 13 | 14 | ## 2: Second id starting with a number -------------------------------------------------------------------------------- /docs/examples/more tests/plain - text file.txt: -------------------------------------------------------------------------------- 1 |

This should be very plain :)

2 | -------------------------------------------------------------------------------- /docs/examples/readme2.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | ## A table 4 | | Tables | Are | Cool | 5 | | ------------- |:-------------:| -----:| 6 | | col 3 is | right-aligned | $1600 | 7 | | col 2 is | centered | $12 | 8 | | zebra stripes | are neat | $1 | 9 | 10 | ## A linebreak 11 | 12 | ## External link in new tab 13 | 14 | 15 | A external link 16 | 17 | 18 | --- 19 | -------------------------------------------------------------------------------- /docs/examples/subfolder/more-infos.md: -------------------------------------------------------------------------------- 1 | I am a file in a :file_folder: 😎 2 | -------------------------------------------------------------------------------- /docs/examples/subfolder2/autoincluded.html: -------------------------------------------------------------------------------- 1 |

Its me and i am autoincluded

2 | 3 |

A crazy world

4 |

5 | It is! 6 |

7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |

Really!

36 | -------------------------------------------------------------------------------- /docs/examples/subfolder2/subsub/furtherincluded.md: -------------------------------------------------------------------------------- 1 |

Included too :)

2 | -------------------------------------------------------------------------------- /docs/examples/test.md: -------------------------------------------------------------------------------- 1 | # Hello world 2 | 3 | ![Lovely pixabay image](/images/narrative-794978_1920.jpg "https://pixabay.com/photo-794978/") 4 | 5 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. 6 | ## Lets get started 7 | 8 | ```js 9 | console.log('test'); 10 | var test = 'test'; 11 | ``` 12 | 13 | ## Sweet smiles 14 | 15 | Just some funny :hatched_chick: :smile:! 16 | 17 | ### Now follows a link 18 | 19 | [I am a link](https://google.ch) 20 | 21 | ### A lot of text 22 | 23 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. 24 | 25 | Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. 26 | 27 | Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. 28 | 29 | Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. 30 | 31 | Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. 32 | 33 | At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. 34 | 35 | Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. 36 | 37 | Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. 38 | 39 | ## Should scroll down 40 | 41 | Blub 42 | 43 | ## same level 44 | 45 | #### very high heading 46 | 47 | Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus. 48 | 49 | #### same 50 | 51 | ##### deeply nested 52 | 53 | #### same again 54 | 55 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. 56 | 57 | ## here i come 58 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | Just type in the following command: 4 | 5 | ```bash 6 | npm install -g mallery 7 | ``` 8 | 9 | ## Requirements 10 | 11 | - Node & NPM 12 | - Its tested with Node v10.16.0 13 | -------------------------------------------------------------------------------- /docs/public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malleryjs/mallery/27aa6316342949ea0c3f0ae9024367d01816723d/docs/public/.gitkeep -------------------------------------------------------------------------------- /docs/public/images/narrative-794978_1920.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malleryjs/mallery/27aa6316342949ea0c3f0ae9024367d01816723d/docs/public/images/narrative-794978_1920.jpg -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | ## CLI 3 | Most of the config file [options](options) can also be used directly from 4 | the CLI. If paths are provided via CLI argument, they will be relative to the 5 | current working directory! 6 | 7 | ```bash 8 | mallery --title "Iron Man Suit" --colors-accent orange --brandIcon false 9 | --footer-html "© © © Copypasta" --paths-output="custom/output" 10 | ``` 11 | 12 | > Notice how recursive options like colors-accent or footer-html can be used! 13 | 14 | ### Additional options 15 | 16 | #### First argument 17 | The first CLI argument can be a path to the [config file](#configuration-file): 18 | 19 | ```bash 20 | mallery custom/path/to/config.js 21 | ``` 22 | 23 | 24 | ## Configuration file 25 | Mallery will search for a `mallery.config.js` file in the current working folder. 26 | A custom path to this file can be provided via [CLI](#first-argument)! 27 | 28 | ### Options 29 | | Name | Description | Default | 30 | | ---------------------- | ------------------------------------------------------------------------ | ----------- | 31 | |
brandIcon
| Copy the Mallory favicon to the root of the website | true | 32 | |
title
| A base title (postfixed to all page titles) | | 33 | |
colors
| Multible color settings | {...} | 34 | |
  accent
| Color of links in content, active menu items and mobile hamburger button | #00BF63 | 35 | |
paths
| Several path options (paths relative to config file
Paths in config file are relative to the file
Path arguments via CLI are relative to current working dir | {...} | 36 | |
  src
| Path to the folder with source files (markdown etc) | docs | 37 | |
  output
| Destination path of the compiled website | site | 38 | |
  public
| Folder which should be used (copied) as the root of the website | docs/public | 39 | |
includes
| Object - file content type controls | {...} | 40 | |
  plaintext
| Files with these associations will be interpreted as plaintext | [ .txt ] | 41 | |
  html
| Files with these associations will be interpreted as markdown | [ .html ] | 42 | |
  markdown
| Files with these associations will be interpreted as plaintext | [ .md ] | 43 | |
toc
| Table of contents (automatically generated if not provided)
Have a look at the example! | [...] | 44 | 45 | 46 | ### Example 47 | ```js 48 | module.exports = { 49 | brandIcon: false, 50 | title: 'Iron Man Suit', 51 | paths: { 52 | output: 'customFolder' 53 | }, 54 | colors: { 55 | accent: 'red' 56 | }, 57 | footer: { 58 | html: '"Copyright © 2017"' 59 | }, 60 | toc: [ 61 | // All files of this folder are automatically included 62 | { path: 'a_folder', title: 'A folder with custom title' }, 63 | // Title will be "A file" 64 | { path: 'a_file.md' } 65 | { path: 'a_second_folder', children: [ 66 | // Path of parent is automatically prepended! 67 | { path: 'file_in_second_folder.md' }, 68 | { path: 'file2_in_second_folder.md' }, 69 | // A child item with more own child items (... is just a placeholder) 70 | { path: 'folder_in_second_folder', children: [ '...' ] }, 71 | ]}, 72 | // Link item 73 | { href: 'http://wikipedia.org', title: 'Just a link' } 74 | ] 75 | }; 76 | ``` 77 | 78 | 79 | ## Tips 80 | 81 | ### Folder as root of another git branch 82 | Github Pages can host the ouput of Mallery! 83 | 84 | Use the following command after updating the site: 85 | 86 | ```bash 87 | git subtree push --prefix site origin gh-pages 88 | ``` 89 | 90 | This command will push the changes from the folder `site` to the 91 | branch `gh-pages`. 92 | 93 | ### Git updates were rejected 94 | 95 | ```bash 96 | git push origin `git subtree split --prefix site gh-pages`:gh-pages --force 97 | ``` 98 | -------------------------------------------------------------------------------- /front/lib/assets/css/app.scss: -------------------------------------------------------------------------------- 1 | $breakpoint-xs: 576px; 2 | $breakpoint-sm: 768px; 3 | $breakpoint-lg: 992px; 4 | $breakpoint-xl: 1200px; 5 | 6 | body, 7 | html, 8 | #app, 9 | .app, 10 | .main-wrapper, 11 | .fullscreen { 12 | height: 100%; 13 | max-height: 100%; 14 | } 15 | 16 | h1:not(:first-child), 17 | h2:not(:first-child), 18 | h3:not(:first-child) { 19 | margin-top: 45px; 20 | } 21 | 22 | h1 + h2, 23 | h2 + h3 { 24 | margin-top: 20px !important; 25 | } 26 | 27 | h4:not(:first-child), 28 | h5:not(:first-child) { 29 | margin-top: 30px; 30 | } 31 | 32 | h1 { 33 | font-size: 2.7rem; 34 | font-weight: 700; 35 | } 36 | h2 { 37 | font-size: 2.2rem; 38 | font-weight: 600; 39 | } 40 | h3 { 41 | font-size: 1.7rem; 42 | font-weight: 600; 43 | } 44 | h4 { 45 | font-size: 1.5rem; 46 | } 47 | h5 { 48 | font-size: 1.25rem; 49 | } 50 | h6 { 51 | font-size: 1.1rem; 52 | } 53 | 54 | .header__inner { 55 | max-width: 800px; 56 | margin: auto; 57 | padding: 10px; 58 | text-align: center; 59 | } 60 | .header__title { 61 | color: #adadad; 62 | font-weight: 200; 63 | font-size: 1.3em; 64 | } 65 | 66 | .header__title:focus, 67 | .header__title:active, 68 | .header__title:hover { 69 | color: #5c5c5c; 70 | text-decoration: none; 71 | } 72 | 73 | .toc-link { 74 | display: inline-block; 75 | transform: scale(0.7); 76 | } 77 | 78 | .left_nav { 79 | font-size: 0.9em; 80 | height: 100%; 81 | position: static; 82 | padding: 0px; 83 | } 84 | 85 | .left_nav__search { 86 | height: 40px; 87 | border-width: 0px; 88 | border-bottom: 1px solid rgba(0, 0, 0, 0.125); 89 | border-radius: 0px; 90 | padding: 12px 20px; 91 | } 92 | 93 | .left_nav__content { 94 | padding: 0px; 95 | } 96 | 97 | .left_nav__content_wrapper { 98 | background-color: #f7f7f7; 99 | border-radius: 0px; 100 | } 101 | 102 | .left_nav__menu--root { 103 | display: block; 104 | overflow-y: auto; 105 | -webkit-overflow-scrolling: touch; 106 | overflow-x: hidden; 107 | padding-bottom: 15px; 108 | } 109 | 110 | .left_nav__menu--root > .left_nav__menu_item { 111 | background-color: transparent; 112 | border-radius: 0px; 113 | border-left-width: 0px; 114 | border-right-width: 0px; 115 | margin-bottom: 0px; 116 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); 117 | } 118 | 119 | .left_nav__menu_item_link:link, 120 | .left_nav__menu_item_link { 121 | color: #555; 122 | text-decoration: none; 123 | } 124 | 125 | .left_nav__menu_item_link:active, 126 | .left_nav__menu_item_link:hover, 127 | .left_nav__menu_item--active > .left_nav__menu_item_link { 128 | text-decoration: none; 129 | } 130 | 131 | .left_nav__menu--sub .left_nav__menu_item_link { 132 | padding-top: 14px; 133 | } 134 | 135 | .left_nav__menu_item_link { 136 | display: block; 137 | width: 100%; 138 | text-overflow: ellipsis; 139 | } 140 | 141 | .left_nav__menu--sub { 142 | padding-left: 15px; 143 | width: 100%; 144 | } 145 | 146 | .left_nav__menu--root > .left_nav__menu_item:first-child { 147 | border-top-width: 0px; 148 | } 149 | 150 | .left_nav__menu--root > .left_nav__menu_item:last-child { 151 | border-bottom-width: 0px; 152 | } 153 | 154 | @media (min-width: $breakpoint-xl) { 155 | div.main-wrapper-wrapper a.navigation { 156 | display: none; 157 | } 158 | } 159 | 160 | @media (max-width: $breakpoint-sm - 1px) { 161 | .header__inner { 162 | text-align: left; 163 | } 164 | } 165 | 166 | @media (max-width: $breakpoint-xl - 1px) { 167 | .left_nav { 168 | position: fixed; 169 | display: none; 170 | padding-right: 0px; 171 | z-index: 2; 172 | overflow-y: auto; 173 | //-webkit-overflow-scrolling: touch; 174 | } 175 | 176 | .left_nav--active { 177 | display: block; 178 | } 179 | 180 | .left_nav { 181 | max-height: 100%; 182 | } 183 | 184 | .header { 185 | padding-top: 17px; 186 | height: 80px; 187 | } 188 | } 189 | 190 | .fixed_menu_button { 191 | opacity: 0.4; 192 | top: 20px; 193 | position: sticky; 194 | z-index: 1; 195 | height: 0; 196 | float: right; 197 | @media (min-width: $breakpoint-lg) { 198 | right: 20px; 199 | } 200 | } 201 | 202 | .left_nav .fixed_menu_button { 203 | position: fixed; 204 | z-index: 3; 205 | right: 20px; 206 | margin-right: 20px; 207 | } 208 | 209 | @media (min-width: $breakpoint-xl) { 210 | .left_nav__content_wrapper { 211 | width: 100%; 212 | border-top-color: transparent; 213 | border-bottom-color: transparent; 214 | } 215 | 216 | .left_nav, 217 | .left_nav__content_wrapper, 218 | .left_nav__content, 219 | .left_nav__menu--root { 220 | height: 100%; 221 | } 222 | } 223 | 224 | .main_col { 225 | padding: 0; 226 | height: 100%; 227 | } 228 | 229 | .main-wrapper-wrapper { 230 | position: relative; 231 | height: 100%; 232 | } 233 | 234 | .main-wrapper { 235 | max-height: 100%; 236 | overflow-y: auto; 237 | overflow-x: hidden; 238 | padding-bottom: 15px; 239 | -webkit-overflow-scrolling: touch; 240 | } 241 | 242 | .main-wrapper-wrapper a.navigation { 243 | position: absolute; 244 | width: 130px; 245 | text-align: center; 246 | height: 100%; 247 | font-size: 2em; 248 | display: flex; 249 | justify-content: center; 250 | align-content: center; 251 | flex-direction: column; 252 | top: 0px; 253 | } 254 | 255 | .main-wrapper-wrapper a.navigation span { 256 | color: gray; 257 | } 258 | 259 | .main-wrapper-wrapper a.navigation:hover span { 260 | color: black; 261 | } 262 | 263 | .main-wrapper-wrapper a.navigation.navigation-next { 264 | right: 20px; 265 | } 266 | 267 | main { 268 | min-height: 50%; 269 | margin-left: auto; 270 | margin-right: auto; 271 | margin-top: 10px; 272 | margin-bottom: 10px; 273 | @media (min-width: $breakpoint-lg) { 274 | max-width: 70%; 275 | } 276 | } 277 | 278 | main h1:first-child { 279 | display: none; 280 | } 281 | 282 | .inner-spacer { 283 | padding: 0 40px; 284 | @media (min-width: $breakpoint-lg) { 285 | padding: 0; 286 | } 287 | } 288 | 289 | .footer { 290 | margin-top: 20px; 291 | opacity: 0.6; 292 | font-size: 0.9em; 293 | font-weight: 200; 294 | } 295 | 296 | .footer__content { 297 | max-width: 800px; 298 | margin: auto; 299 | } 300 | 301 | @media (max-width: $breakpoint-sm - 1px) { 302 | header + button { 303 | margin-bottom: 20px; 304 | } 305 | main + button { 306 | margin-top: 20px; 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /front/lib/assets/images/favicon.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malleryjs/mallery/27aa6316342949ea0c3f0ae9024367d01816723d/front/lib/assets/images/favicon.afdesign -------------------------------------------------------------------------------- /front/lib/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malleryjs/mallery/27aa6316342949ea0c3f0ae9024367d01816723d/front/lib/assets/images/favicon.ico -------------------------------------------------------------------------------- /front/lib/assets/images/favicon.ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malleryjs/mallery/27aa6316342949ea0c3f0ae9024367d01816723d/front/lib/assets/images/favicon.ico.png -------------------------------------------------------------------------------- /front/lib/assets/templates/layout.ejs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= version %> 7 | 8 | 9 | 10 | 11 | 12 | 18 | 19 | 20 | 21 |
22 |
23 | <%- content %> 24 |
25 |
26 | 27 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /front/lib/assets/vendor/highlight.js/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | github.com style (c) Vasily Polovnyov 3 | */ 4 | 5 | .hljs { 6 | display: block; 7 | overflow-x: auto; 8 | padding: 0.5em; 9 | color: #333; 10 | background: #f8f8f8; 11 | } 12 | 13 | .hljs-comment, 14 | .hljs-quote { 15 | color: #998; 16 | font-style: italic; 17 | } 18 | 19 | .hljs-keyword, 20 | .hljs-selector-tag, 21 | .hljs-subst { 22 | color: #333; 23 | font-weight: bold; 24 | } 25 | 26 | .hljs-number, 27 | .hljs-literal, 28 | .hljs-variable, 29 | .hljs-template-variable, 30 | .hljs-tag .hljs-attr { 31 | color: #008080; 32 | } 33 | 34 | .hljs-string, 35 | .hljs-doctag { 36 | color: #d14; 37 | } 38 | 39 | .hljs-title, 40 | .hljs-section, 41 | .hljs-selector-id { 42 | color: #900; 43 | font-weight: bold; 44 | } 45 | 46 | .hljs-subst { 47 | font-weight: normal; 48 | } 49 | 50 | .hljs-type, 51 | .hljs-class .hljs-title { 52 | color: #458; 53 | font-weight: bold; 54 | } 55 | 56 | .hljs-tag, 57 | .hljs-name, 58 | .hljs-attribute { 59 | color: #000080; 60 | font-weight: normal; 61 | } 62 | 63 | .hljs-regexp, 64 | .hljs-link { 65 | color: #009926; 66 | } 67 | 68 | .hljs-symbol, 69 | .hljs-bullet { 70 | color: #990073; 71 | } 72 | 73 | .hljs-built_in, 74 | .hljs-builtin-name { 75 | color: #0086b3; 76 | } 77 | 78 | .hljs-meta { 79 | color: #999; 80 | font-weight: bold; 81 | } 82 | 83 | .hljs-deletion { 84 | background: #fdd; 85 | } 86 | 87 | .hljs-addition { 88 | background: #dfd; 89 | } 90 | 91 | .hljs-emphasis { 92 | font-style: italic; 93 | } 94 | 95 | .hljs-strong { 96 | font-weight: bold; 97 | } 98 | -------------------------------------------------------------------------------- /front/lib/assets/vendor/modernizr.min.js: -------------------------------------------------------------------------------- 1 | /*! modernizr 3.5.0 (Custom Build) | MIT * 2 | * https://modernizr.com/download/?-overflowscrolling-setclasses !*/ 3 | !(function (e, n, t) { 4 | function r(e, n) { 5 | return typeof e === n; 6 | } 7 | function o() { 8 | var e, n, t, o, s, i, l; 9 | for (var a in C) 10 | if (C.hasOwnProperty(a)) { 11 | if ( 12 | ((e = []), 13 | (n = C[a]), 14 | n.name && 15 | (e.push(n.name.toLowerCase()), 16 | n.options && n.options.aliases && n.options.aliases.length)) 17 | ) 18 | for (t = 0; t < n.options.aliases.length; t++) 19 | e.push(n.options.aliases[t].toLowerCase()); 20 | for (o = r(n.fn, "function") ? n.fn() : n.fn, s = 0; s < e.length; s++) 21 | (i = e[s]), 22 | (l = i.split(".")), 23 | 1 === l.length 24 | ? (Modernizr[l[0]] = o) 25 | : (!Modernizr[l[0]] || 26 | Modernizr[l[0]] instanceof Boolean || 27 | (Modernizr[l[0]] = new Boolean(Modernizr[l[0]])), 28 | (Modernizr[l[0]][l[1]] = o)), 29 | S.push((o ? "" : "no-") + l.join("-")); 30 | } 31 | } 32 | function s(e) { 33 | var n = _.className, 34 | t = Modernizr._config.classPrefix || ""; 35 | if ((x && (n = n.baseVal), Modernizr._config.enableJSClass)) { 36 | var r = new RegExp("(^|\\s)" + t + "no-js(\\s|$)"); 37 | n = n.replace(r, "$1" + t + "js$2"); 38 | } 39 | Modernizr._config.enableClasses && 40 | ((n += " " + t + e.join(" " + t)), 41 | x ? (_.className.baseVal = n) : (_.className = n)); 42 | } 43 | function i(e, n) { 44 | return !!~("" + e).indexOf(n); 45 | } 46 | function l() { 47 | return "function" != typeof n.createElement 48 | ? n.createElement(arguments[0]) 49 | : x 50 | ? n.createElementNS.call(n, "http://www.w3.org/2000/svg", arguments[0]) 51 | : n.createElement.apply(n, arguments); 52 | } 53 | function a() { 54 | var e = n.body; 55 | return e || ((e = l(x ? "svg" : "body")), (e.fake = !0)), e; 56 | } 57 | function f(e, t, r, o) { 58 | var s, 59 | i, 60 | f, 61 | u, 62 | c = "modernizr", 63 | d = l("div"), 64 | p = a(); 65 | if (parseInt(r, 10)) 66 | for (; r--; ) 67 | (f = l("div")), (f.id = o ? o[r] : c + (r + 1)), d.appendChild(f); 68 | return ( 69 | (s = l("style")), 70 | (s.type = "text/css"), 71 | (s.id = "s" + c), 72 | (p.fake ? p : d).appendChild(s), 73 | p.appendChild(d), 74 | s.styleSheet 75 | ? (s.styleSheet.cssText = e) 76 | : s.appendChild(n.createTextNode(e)), 77 | (d.id = c), 78 | p.fake && 79 | ((p.style.background = ""), 80 | (p.style.overflow = "hidden"), 81 | (u = _.style.overflow), 82 | (_.style.overflow = "hidden"), 83 | _.appendChild(p)), 84 | (i = t(d, e)), 85 | p.fake 86 | ? (p.parentNode.removeChild(p), (_.style.overflow = u), _.offsetHeight) 87 | : d.parentNode.removeChild(d), 88 | !!i 89 | ); 90 | } 91 | function u(e) { 92 | return e 93 | .replace(/([A-Z])/g, function (e, n) { 94 | return "-" + n.toLowerCase(); 95 | }) 96 | .replace(/^ms-/, "-ms-"); 97 | } 98 | function c(n, t, r) { 99 | var o; 100 | if ("getComputedStyle" in e) { 101 | o = getComputedStyle.call(e, n, t); 102 | var s = e.console; 103 | if (null !== o) r && (o = o.getPropertyValue(r)); 104 | else if (s) { 105 | var i = s.error ? "error" : "log"; 106 | s[i].call( 107 | s, 108 | "getComputedStyle returning null, its possible modernizr test results are inaccurate" 109 | ); 110 | } 111 | } else o = !t && n.currentStyle && n.currentStyle[r]; 112 | return o; 113 | } 114 | function d(n, r) { 115 | var o = n.length; 116 | if ("CSS" in e && "supports" in e.CSS) { 117 | for (; o--; ) if (e.CSS.supports(u(n[o]), r)) return !0; 118 | return !1; 119 | } 120 | if ("CSSSupportsRule" in e) { 121 | for (var s = []; o--; ) s.push("(" + u(n[o]) + ":" + r + ")"); 122 | return ( 123 | (s = s.join(" or ")), 124 | f( 125 | "@supports (" + s + ") { #modernizr { position: absolute; } }", 126 | function (e) { 127 | return "absolute" == c(e, null, "position"); 128 | } 129 | ) 130 | ); 131 | } 132 | return t; 133 | } 134 | function p(e) { 135 | return e 136 | .replace(/([a-z])-([a-z])/g, function (e, n, t) { 137 | return n + t.toUpperCase(); 138 | }) 139 | .replace(/^-/, ""); 140 | } 141 | function m(e, n, o, s) { 142 | function a() { 143 | u && (delete E.style, delete E.modElem); 144 | } 145 | if (((s = r(s, "undefined") ? !1 : s), !r(o, "undefined"))) { 146 | var f = d(e, o); 147 | if (!r(f, "undefined")) return f; 148 | } 149 | for ( 150 | var u, c, m, v, y, g = ["modernizr", "tspan", "samp"]; 151 | !E.style && g.length; 152 | 153 | ) 154 | (u = !0), (E.modElem = l(g.shift())), (E.style = E.modElem.style); 155 | for (m = e.length, c = 0; m > c; c++) 156 | if ( 157 | ((v = e[c]), 158 | (y = E.style[v]), 159 | i(v, "-") && (v = p(v)), 160 | E.style[v] !== t) 161 | ) { 162 | if (s || r(o, "undefined")) return a(), "pfx" == n ? v : !0; 163 | try { 164 | E.style[v] = o; 165 | } catch (h) {} 166 | if (E.style[v] != y) return a(), "pfx" == n ? v : !0; 167 | } 168 | return a(), !1; 169 | } 170 | function v(e, n) { 171 | return function () { 172 | return e.apply(n, arguments); 173 | }; 174 | } 175 | function y(e, n, t) { 176 | var o; 177 | for (var s in e) 178 | if (e[s] in n) 179 | return t === !1 180 | ? e[s] 181 | : ((o = n[e[s]]), r(o, "function") ? v(o, t || n) : o); 182 | return !1; 183 | } 184 | function g(e, n, t, o, s) { 185 | var i = e.charAt(0).toUpperCase() + e.slice(1), 186 | l = (e + " " + P.join(i + " ") + i).split(" "); 187 | return r(n, "string") || r(n, "undefined") 188 | ? m(l, n, o, s) 189 | : ((l = (e + " " + N.join(i + " ") + i).split(" ")), y(l, n, t)); 190 | } 191 | function h(e, n, r) { 192 | return g(e, t, t, n, r); 193 | } 194 | var C = [], 195 | w = { 196 | _version: "3.5.0", 197 | _config: { 198 | classPrefix: "", 199 | enableClasses: !0, 200 | enableJSClass: !0, 201 | usePrefixes: !0, 202 | }, 203 | _q: [], 204 | on: function (e, n) { 205 | var t = this; 206 | setTimeout(function () { 207 | n(t[e]); 208 | }, 0); 209 | }, 210 | addTest: function (e, n, t) { 211 | C.push({ name: e, fn: n, options: t }); 212 | }, 213 | addAsyncTest: function (e) { 214 | C.push({ name: null, fn: e }); 215 | }, 216 | }, 217 | Modernizr = function () {}; 218 | (Modernizr.prototype = w), (Modernizr = new Modernizr()); 219 | var S = [], 220 | _ = n.documentElement, 221 | x = "svg" === _.nodeName.toLowerCase(), 222 | b = "Moz O ms Webkit", 223 | P = w._config.usePrefixes ? b.split(" ") : []; 224 | w._cssomPrefixes = P; 225 | var z = { elem: l("modernizr") }; 226 | Modernizr._q.push(function () { 227 | delete z.elem; 228 | }); 229 | var E = { style: z.elem.style }; 230 | Modernizr._q.unshift(function () { 231 | delete E.style; 232 | }); 233 | var N = w._config.usePrefixes ? b.toLowerCase().split(" ") : []; 234 | (w._domPrefixes = N), 235 | (w.testAllProps = g), 236 | (w.testAllProps = h), 237 | Modernizr.addTest("overflowscrolling", h("overflowScrolling", "touch", !0)), 238 | o(), 239 | s(S), 240 | delete w.addTest, 241 | delete w.addAsyncTest; 242 | for (var T = 0; T < Modernizr._q.length; T++) Modernizr._q[T](); 243 | e.Modernizr = Modernizr; 244 | })(window, document); 245 | -------------------------------------------------------------------------------- /front/lib/bootstrap.tsx: -------------------------------------------------------------------------------- 1 | import "./vendor"; 2 | import "./assets/css/app.scss"; 3 | import { render, h, createRef, Ref } from "preact"; 4 | import { observe } from "alo"; 5 | import { dispatchThunk, dispatchPromise } from "alo/store"; 6 | 7 | import store from "./store"; 8 | import "./store/router"; 9 | import { setConfig, toggleNavActive } from "./store"; 10 | import { gotoItemById } from "./store/router"; 11 | import { 12 | getTocItemById, 13 | setTocItemId, 14 | initToc, 15 | setTocChapterId, 16 | } from "./store/router/toc"; 17 | import { App as AppComponent } from "./components/app"; 18 | import { addNewHistoryEntry } from "./util/history"; 19 | import { computed } from "./store/computed"; 20 | 21 | const idWithLeadingDigitRegex = new RegExp("^#\\d"); 22 | const $ = function (query: string) { 23 | if (query.match(idWithLeadingDigitRegex)) { 24 | // Escape id queries with leading digit 25 | // https://stackoverflow.com/a/20306237 26 | query = `#\\3${query[1]} ${query.slice(2)}`; 27 | } 28 | 29 | return document.querySelectorAll(query); 30 | }; 31 | 32 | export class App { 33 | mainWrapperRef = createRef(); 34 | 35 | constructor(appEl, config) { 36 | render( 37 | , 44 | appEl 45 | ); 46 | 47 | window.onpopstate = async (evt) => { 48 | if (evt.state != null && evt.state.itemId != null) { 49 | if (evt.state.title != null) { 50 | document.title = evt.state.title; 51 | } 52 | 53 | await dispatchThunk(store, setTocItemId(evt.state.itemId, computed)); 54 | 55 | var chapterId = null; 56 | 57 | if (evt.state.hash != null && evt.state.hash != "") { 58 | var item = getTocItemById(evt.state.itemId); 59 | if (item.chapters != null && item.chapters.items.length > 0) { 60 | var idx = 0; 61 | var length = item.chapters.items.length; 62 | let hash = evt.state.hash.slice(1); 63 | 64 | while (idx < length) { 65 | let chapter = item.chapters.items[idx]; 66 | if (chapter.hash === hash) { 67 | chapterId = chapter.id; 68 | break; 69 | } 70 | idx++; 71 | } 72 | } 73 | } else { 74 | this.mainWrapperRef.current.scrollTop = 0; 75 | } 76 | 77 | return store.dispatch(setTocChapterId(chapterId)); 78 | } else { 79 | /* 80 | * This case should only ever happen when an anchor link 81 | * in the body was clicked 82 | */ 83 | var prom = this.setActiveChapterByLocationHash(); 84 | addNewHistoryEntry(true); 85 | } 86 | }; 87 | 88 | this.initStores(appEl, config); 89 | 90 | // Changes to current focus based on specific router changes 91 | observe(() => { 92 | computed.tocItem; 93 | if (computed.tocChapter != null) { 94 | this.gotoChapter(computed.tocChapter); 95 | } else { 96 | this.focusElementByHash("#header"); 97 | } 98 | }); 99 | } 100 | 101 | async initStores(appEl, config) { 102 | history.replaceState( 103 | { itemId: config.toc.activeItemId, hash: window.location.hash }, 104 | "", 105 | window.location.href 106 | ); 107 | await dispatchPromise(store, initToc(config.toc.items, config.toc.root)); 108 | 109 | const mainEl = appEl.querySelector("main:not(.main)") as HTMLElement | null; 110 | if (mainEl) { 111 | await dispatchThunk( 112 | store, 113 | setTocItemId(config.toc.activeItemId, computed, false, mainEl.innerHTML) 114 | ); 115 | mainEl.parentNode!.removeChild(mainEl); 116 | } 117 | 118 | store.dispatch(setConfig(config.config)); 119 | this.setActiveChapterByLocationHash(); 120 | addNewHistoryEntry(true); 121 | } 122 | 123 | setActiveChapterByLocationHash() { 124 | if (window.location.hash != "") { 125 | let hashEl = $(window.location.hash)[0]; 126 | if (hashEl != null) { 127 | const aHashEl: any = hashEl; 128 | window.scrollTo(0, aHashEl.offsetTop); 129 | this.mainWrapperRef.current.scrollTop = aHashEl.offsetTop; 130 | aHashEl.focus(); 131 | } 132 | var item = computed.tocItem; 133 | if (item != null) { 134 | if (item.chapters != null && item.chapters.items.length > 0) { 135 | var idx = 0; 136 | var length = item.chapters.items.length; 137 | let hash = window.location.hash.slice(1); 138 | while (idx < length) { 139 | let chapter = item.chapters.items[idx]; 140 | if (chapter.hash === hash) { 141 | return store.dispatch(setTocChapterId(chapter.id)); 142 | } 143 | idx++; 144 | } 145 | } 146 | } 147 | } 148 | 149 | store.dispatch(setTocChapterId(0)); 150 | } 151 | 152 | focusElementByHash(hash) { 153 | var hashEl = $(hash)[0]; 154 | if (hashEl != null) { 155 | const aHashEl: any = hashEl; 156 | window.scrollTo(0, aHashEl.offsetTop); 157 | this.mainWrapperRef.current.scrollTop = aHashEl.offsetTop; 158 | aHashEl.focus(); 159 | } 160 | } 161 | 162 | async gotoPrevTocItem() { 163 | let item = computed.tocItem; 164 | const newItemId = this.getPreviousItemId(); 165 | if (newItemId != null && (!item || item.id !== newItemId)) { 166 | await dispatchThunk(store, gotoItemById(newItemId, computed)); 167 | let newItem = computed.tocItem; 168 | if (!newItem) return; 169 | var newChapterId = null as any; 170 | if (newItem.chapters != null && newItem.chapters.items.length > 0) { 171 | let chapters = newItem.chapters.items; 172 | newChapterId = chapters.length - 1; 173 | } 174 | 175 | store.dispatch(setTocChapterId(newChapterId)); 176 | } 177 | } 178 | 179 | onClickMobileNavPrev = async (evt) => { 180 | evt.preventDefault(); 181 | await this.gotoPrevTocItem(); 182 | }; 183 | 184 | onClickNavPrev = async (evt) => { 185 | evt.preventDefault(); 186 | 187 | if (store.getState().localMode) { 188 | await this.gotoPrevTocItem(); 189 | return; 190 | } 191 | 192 | const chapterId = this.getPreviousChapterId(computed.tocItem); 193 | 194 | if (chapterId !== false) { 195 | store.dispatch(setTocChapterId(chapterId)); 196 | } else { 197 | await this.gotoPrevTocItem(); 198 | } 199 | 200 | addNewHistoryEntry(); 201 | }; 202 | 203 | async gotoNextTocItem() { 204 | const item = computed.tocItem; 205 | const nextItemId = this.getNextItemId(); 206 | if (nextItemId != null && (!item || nextItemId !== item.id)) { 207 | await dispatchThunk(store, gotoItemById(nextItemId, true)); 208 | } 209 | } 210 | 211 | onClickMobileNavNext = async (evt) => { 212 | evt.preventDefault(); 213 | await this.gotoNextTocItem(); 214 | }; 215 | 216 | onClickNavNext = async (evt) => { 217 | evt.preventDefault(); 218 | 219 | if (store.getState().localMode) { 220 | await this.gotoNextTocItem(); 221 | return; 222 | } 223 | 224 | const chapterId = this.getNextChapterId(computed.tocItem); 225 | if (chapterId !== false) { 226 | store.dispatch(setTocChapterId(chapterId)); 227 | } else { 228 | await this.gotoNextTocItem(); 229 | } 230 | 231 | addNewHistoryEntry(); 232 | }; 233 | 234 | gotoChapter(chapter) { 235 | var newHash; 236 | if (chapter.id === 0) { 237 | newHash = "#header"; 238 | } else if (chapter.hash != null) { 239 | newHash = `#${chapter.hash}`; 240 | } 241 | 242 | if (newHash != null) { 243 | this.focusElementByHash(newHash); 244 | } 245 | } 246 | 247 | getPreviousItemId(idxMove = -1) { 248 | let routerState = store.getState().router; 249 | let items = routerState.toc.items; 250 | let itemsCount = items.length; 251 | let activeId = routerState.toc.itemId; 252 | let newId = activeId; 253 | 254 | if (newId == null) return null; 255 | 256 | while (true) { 257 | newId = newId + idxMove; 258 | if (newId < 0) { 259 | newId = 0; 260 | break; 261 | } else if (newId > itemsCount) { 262 | newId = activeId; 263 | break; 264 | } 265 | 266 | let newItem = items[newId]; 267 | if (newItem != null) { 268 | if (newItem.path != null && newItem.hasContent === true) { 269 | break; 270 | } 271 | } 272 | } 273 | 274 | return newId; 275 | } 276 | 277 | getNextItemId() { 278 | return this.getPreviousItemId(1); 279 | } 280 | 281 | getPreviousChapterId(item, idxMove = -1) { 282 | let newIdx = -1; 283 | 284 | let currentChapter = computed.tocChapter; 285 | if (currentChapter) { 286 | newIdx = currentChapter.id + idxMove; 287 | } 288 | 289 | if (newIdx >= 0 && item.chapters.items[newIdx] != null) { 290 | return newIdx; 291 | } 292 | 293 | return false; 294 | } 295 | 296 | getNextChapterId(item) { 297 | return this.getPreviousChapterId(item, 1); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /front/lib/components/app.tsx: -------------------------------------------------------------------------------- 1 | import { h, Fragment } from "preact"; 2 | import { observerHOC } from "./observer"; 3 | import { css, cx } from "emotion"; 4 | // mport { Devtools } from "alo/dist/alo/dev"; 5 | 6 | import store from "../store"; 7 | import { Nav } from "./nav"; 8 | import { Header } from "./header"; 9 | import { computed } from "../store/computed"; 10 | import { MobileMenuButton } from "./mobileMenuButton"; 11 | 12 | /* 13 | let devtoolsMounted = false; 14 | const attachDevTools = function() { 15 | if (process.env.NODE_ENV === "development") { 16 | if (!devtoolsMounted) new Devtools({ targetElSelector: "#dev" }); 17 | devtoolsMounted = true; 18 | } 19 | }; 20 | */ 21 | 22 | const App_ = function ({ 23 | mainWrapperRef, 24 | onClickNavPrev, 25 | onClickNavNext, 26 | onClickMobileNavPrev, 27 | onClickMobileNavNext, 28 | }) { 29 | const state = store.getState(); 30 | const routerState = state.router; 31 | if (!state) return null; 32 | 33 | const linkColor = state.config.colors && state.config.colors.accent; 34 | 35 | /* 36 | el('a.', { href: '#', onclick: }, 37 | el('span', el('span.icono-caretLeft')) 38 | ), 39 | */ 40 | 41 | return ( 42 | 43 |
.left_nav__menu_item_link { 60 | color: ${linkColor}; 61 | } 62 | ` 63 | } 64 | > 65 |
66 |
67 |
137 |
138 |
144 | 145 | ); 146 | }; 147 | export const App = observerHOC(App_); 148 | -------------------------------------------------------------------------------- /front/lib/components/header.tsx: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { observerHOC } from "./observer"; 3 | import { getUrl } from "../models/tocItem"; 4 | import { getCurrentTitle } from "../util/pageTitle"; 5 | import { computed } from "../store/computed"; 6 | 7 | const Header_ = function () { 8 | const tocItem = computed.tocItem; 9 | const href = tocItem ? getUrl(tocItem, computed) : "#"; 10 | 11 | return ( 12 | 21 | ); 22 | }; 23 | 24 | export const Header = observerHOC(Header_); 25 | -------------------------------------------------------------------------------- /front/lib/components/mobileMenuButton.tsx: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | 3 | import store, { toggleNavActive } from "../store"; 4 | 5 | export const MobileMenuButton = function () { 6 | return ( 7 |
8 | 16 |
17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /front/lib/components/nav/chapter.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | import { h } from "preact"; 3 | 4 | import * as Item from "../../models/tocItem"; 5 | import { observerHOC } from "../observer"; 6 | import { dispatchThunk } from "alo/store"; 7 | import { setTocItemId, setTocChapterId } from "../../store/router/toc"; 8 | import store, { setNavActive } from "../../store"; 9 | import { addNewHistoryEntry } from "../../util/history"; 10 | import { computed } from "../../store/computed"; 11 | 12 | const onNavChapterItemClick = async function (itemId, chapterId, evt) { 13 | evt.preventDefault(); 14 | 15 | await dispatchThunk(store, setTocItemId(itemId, computed)); 16 | store.dispatch(setTocChapterId(chapterId)); 17 | addNewHistoryEntry(); 18 | store.dispatch(setNavActive(false)); 19 | }; 20 | 21 | const ChapterItem_ = function ({ item, level, tocItem }) { 22 | const toc = store.getState().router.toc; 23 | const classes = clsx({ 24 | "left_nav__menu_item--active": toc.chapterId == item.id, 25 | }); 26 | 27 | const baseUrl = Item.getUrl(tocItem, computed); 28 | const href = `${baseUrl}#${item.hash}`; 29 | 30 | return ( 31 |
  • 32 | 40 | 41 |
  • 42 | ); 43 | }; 44 | const ChapterItem = observerHOC(ChapterItem_); 45 | 46 | const ChapterList_ = function ({ 47 | tocItem, 48 | item, 49 | level = 1, 50 | }: { 51 | tocItem; 52 | item?; 53 | level?: number; 54 | }) { 55 | const activeTocItem = computed.tocItem; 56 | if (!activeTocItem) return null; 57 | 58 | let chapterIds; 59 | if (item) { 60 | if (!item.children) return null; 61 | chapterIds = item.children; 62 | } else { 63 | if (tocItem.id !== activeTocItem.id) return null; 64 | if (tocItem.chapters == null) return null; 65 | chapterIds = tocItem.chapters.root; 66 | if (chapterIds.length === 1) { 67 | chapterIds = tocItem.chapters.items[chapterIds[0]].children; 68 | } 69 | } 70 | 71 | if (!chapterIds) return null; 72 | 73 | const chapters = chapterIds.map(function (chapterId) { 74 | return tocItem.chapters.items[chapterId]; 75 | }); 76 | 77 | return ( 78 |
      79 | {chapters.map((item, index) => ( 80 | 81 | ))} 82 |
    83 | ); 84 | }; 85 | 86 | export const ChapterList = observerHOC(ChapterList_); 87 | -------------------------------------------------------------------------------- /front/lib/components/nav/index.tsx: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import clsx from "clsx"; 3 | 4 | import store from "../../store"; 5 | import { observerHOC } from "../observer"; 6 | import { getTocItemsById } from "../../store/router/toc"; 7 | import { TocItem } from "./toc"; 8 | import { MobileMenuButton } from "../mobileMenuButton"; 9 | 10 | export const Nav = observerHOC(function () { 11 | const state = store.getState(); 12 | const routerState = state.router; 13 | const tocItems = getTocItemsById(routerState.toc.root); 14 | const navClasses = clsx({ 15 | "left_nav--active": state.navActive, 16 | }); 17 | 18 | return ( 19 | 39 | ); 40 | }); 41 | -------------------------------------------------------------------------------- /front/lib/components/nav/toc.tsx: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import clsx from "clsx"; 3 | 4 | import { ChapterList } from "./chapter"; 5 | import { observerHOC } from "../observer"; 6 | import * as Item from "../../models/tocItem"; 7 | import store, { setNavActive } from "../../store"; 8 | import { getTocItemsById } from "../../store/router/toc"; 9 | import { addNewHistoryEntry } from "../../util/history"; 10 | import { dispatchThunk } from "alo/store"; 11 | import { gotoItemById } from "../../store/router"; 12 | import { computed } from "../../store/computed"; 13 | 14 | const TocItemList_ = function ({ item }) { 15 | if (item.children == null) return null; 16 | 17 | const tocItem = computed.tocItem; 18 | const parentItemIds = computed.parentTocItemIds; 19 | const appState = store.getState(); 20 | 21 | const childrenVisible = 22 | (tocItem && item.id === tocItem.id) || appState.navActive === true; 23 | if (!childrenVisible && parentItemIds.indexOf(item.id) == -1) return null; 24 | 25 | const items = getTocItemsById(item.children); 26 | 27 | return ( 28 |
      29 | {items.map((item, index) => ( 30 | 31 | ))} 32 |
    33 | ); 34 | }; 35 | const TocItemList = observerHOC(TocItemList_); 36 | 37 | const isTocItemActive = function (item) { 38 | const toc = store.getState().router.toc; 39 | const isCurrentItem = toc.itemId === item.id; 40 | const hasNoChildren = item.chapters == null && item.children == null; 41 | const hasOnlyOneChapter = 42 | item.chapters != null && 43 | item.chapters.root.length === 1 && 44 | toc.chapterId == 0; 45 | const noChapterIsActive = computed.tocChapter == null; 46 | const isActive = 47 | isCurrentItem && (hasNoChildren || hasOnlyOneChapter || noChapterIsActive); 48 | 49 | return isActive; 50 | }; 51 | 52 | const onNavTocItemClick = function (itemId, evt) { 53 | evt.preventDefault(); 54 | dispatchThunk(store, gotoItemById(itemId, computed, true)) 55 | .then(() => { 56 | addNewHistoryEntry(); 57 | }) 58 | .then(() => { 59 | return store.dispatch(setNavActive(false)); 60 | }); 61 | }; 62 | const TocItem_ = function ({ item, listGroupItem = false }) { 63 | const title = Item.getTitle(item); 64 | const href = item.href != null ? item.href : Item.getUrl(item, computed); 65 | const onLinkClick = 66 | item.href == null ? onNavTocItemClick.bind(null, item.id) : null; 67 | const itemClasses = clsx({ 68 | "left_nav__menu_item--active": isTocItemActive(item), 69 | "list-group-item": listGroupItem, 70 | }); 71 | 72 | return ( 73 |
  • 74 | 81 | {title} 82 | 83 | 84 | 85 |
  • 86 | ); 87 | }; 88 | 89 | export const TocItem = observerHOC(TocItem_); 90 | -------------------------------------------------------------------------------- /front/lib/components/observer.ts: -------------------------------------------------------------------------------- 1 | import { observe } from "alo"; 2 | import { h, Component, FunctionalComponent, FunctionComponent } from "preact"; 3 | import { PureComponent } from "preact/compat"; 4 | 5 | export const observerHOC = function

    ( 6 | TheComponent: FunctionalComponent

    7 | ): FunctionComponent

    { 8 | return function (props) { 9 | props["view"] = TheComponent; 10 | 11 | return h(Observer as any, props); 12 | }; 13 | }; 14 | 15 | export class Observer extends Component<{ view? }> { 16 | updating = false; 17 | unobserve; 18 | renderedVnode; 19 | oldProps; 20 | oldState; 21 | 22 | startObserver() { 23 | if (this.unobserve) this.unobserve(); 24 | this.unobserve = null; 25 | this.unobserve = observe(this.observer); 26 | } 27 | 28 | observer = () => { 29 | const viewParent = (this as any).view ? (this as any) : this.props; 30 | if (!viewParent) return; 31 | this.renderedVnode = viewParent.view(this.props, this.state); 32 | if (!this.unobserve) { 33 | return; 34 | } 35 | 36 | this.updating = true; 37 | this.forceUpdate(); 38 | }; 39 | 40 | componentwillUnmount() { 41 | if (this.unobserve) this.unobserve(); 42 | this.unobserve = null; 43 | } 44 | 45 | render(props, state) { 46 | if ( 47 | !this.updating && 48 | (this.unobserve == null || 49 | (this as any)._shouldComponentUpdate(this.oldProps, this.oldState)) 50 | ) { 51 | this.startObserver(); 52 | } 53 | 54 | this.updating = false; 55 | this.oldProps = props; 56 | this.oldState = state; 57 | 58 | return this.renderedVnode; 59 | } 60 | } 61 | 62 | (Observer.prototype as any)._shouldComponentUpdate = 63 | PureComponent.prototype.shouldComponentUpdate; 64 | -------------------------------------------------------------------------------- /front/lib/main.ts: -------------------------------------------------------------------------------- 1 | export { App } from "./bootstrap"; 2 | -------------------------------------------------------------------------------- /front/lib/models/tocItem.ts: -------------------------------------------------------------------------------- 1 | import store from "../store"; 2 | 3 | export const getEnclosedChildren = function (itemId, items?) { 4 | if (items == null) { 5 | let routerState = store.getState().router; 6 | items = routerState.toc.items; 7 | } 8 | 9 | let item = items[itemId]; 10 | 11 | let children = [] as any[]; 12 | if (item.children != null) { 13 | item.children.forEach(function (childId) { 14 | children.push(childId); 15 | children = [...children, ...getEnclosedChildren(childId)]; 16 | }); 17 | } 18 | 19 | return children; 20 | }; 21 | 22 | export const isViewableItem = function (item) { 23 | let result = item.path != null && item.hasContent === true; 24 | return result; 25 | }; 26 | 27 | export const getTitle = function (item) { 28 | return item.title || item.path; 29 | }; 30 | 31 | export const getUrl = function (item, computed, html?) { 32 | let self = this; 33 | 34 | item = item || computed.tocItem(); 35 | if (html == null) html = false; 36 | 37 | let state = store.getState().router; 38 | var rootUrl = state.rootUrl; 39 | 40 | if (html) { 41 | return rootUrl + item.htmlPath; 42 | } else { 43 | return rootUrl + item.path; 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /front/lib/store/computed.ts: -------------------------------------------------------------------------------- 1 | import { computation } from "alo"; 2 | 3 | import store from "."; 4 | import * as Item from "../models/tocItem"; 5 | 6 | export const computed = computation({ 7 | tocItem: function () { 8 | const state = store.getState().router; 9 | const itemId = state.toc.itemId; 10 | 11 | if (itemId == null) return; 12 | 13 | return state.toc.items[itemId]; 14 | }, 15 | parentTocItemIds: function (obj: any) { 16 | const state = store.getState().router; 17 | 18 | var parentIds: number[] = []; 19 | if (obj.tocItem) { 20 | var id = obj.tocItem.id; 21 | 22 | state.toc.items.forEach(function (item) { 23 | // TODO: This could probably be done faster! 24 | var childIds = Item.getEnclosedChildren(item.id); 25 | if (childIds.indexOf(id) >= 0) { 26 | parentIds.push(item.id); 27 | } 28 | }); 29 | } 30 | 31 | return parentIds; 32 | }, 33 | parentTocItems: function (obj) { 34 | const state = store.getState().router; 35 | 36 | return obj.parentTocItemIds.map((id) => state.toc.items[id]); 37 | }, 38 | tocChapter: function (obj) { 39 | const state = store.getState().router; 40 | 41 | if ( 42 | obj.tocItem != null && 43 | obj.tocItem.chapters && 44 | state.toc.chapterId != null 45 | ) { 46 | var items = obj.tocItem.chapters.items; 47 | if (items != null) { 48 | return items[state.toc.chapterId]; 49 | } 50 | } 51 | }, 52 | viewableTocItems: function (obj) { 53 | const tocItems = store.getState().router.toc.items; 54 | if (tocItems != null) { 55 | return tocItems.filter((item) => { 56 | return Item.isViewableItem(item); 57 | }); 58 | } 59 | }, 60 | readingProgress: function (obj) { 61 | var result: { 62 | firstEntry?; 63 | lastEntry?; 64 | } = {}; 65 | if (obj.viewableTocItems != null && obj.tocItem != null) { 66 | if (obj.viewableTocItems[0] != null) { 67 | result.firstEntry = obj.viewableTocItems[0].id === obj.tocItem.id; 68 | } 69 | if (obj.viewableTocItems[0] != null) { 70 | result.lastEntry = 71 | obj.viewableTocItems[obj.viewableTocItems.length - 1].id === 72 | obj.tocItem.id; 73 | } 74 | } 75 | 76 | return result; 77 | }, 78 | })[0]; 79 | -------------------------------------------------------------------------------- /front/lib/store/index.ts: -------------------------------------------------------------------------------- 1 | import { Store } from "alo/store"; 2 | // import { attachStoreToDevtools } from "alo/dist/alo/dev"; 3 | import { mutator } from "./mutator"; 4 | 5 | export const setConfig = mutator.setWithPayload( 6 | "setConfig", 7 | (state, action) => { 8 | state.config = action.payload; 9 | 10 | return state; 11 | } 12 | ); 13 | 14 | export const toggleNavActive = mutator.set( 15 | "toggleNavActive", 16 | (state, action) => { 17 | state.navActive = !state.navActive; 18 | 19 | return state; 20 | } 21 | ); 22 | 23 | export const setNavActive = mutator.setWithPayload( 24 | "setNavActive", 25 | (state, action) => { 26 | state.navActive = action.payload != null ? action.payload : true; 27 | 28 | return state; 29 | } 30 | ); 31 | 32 | const store = new Store({ 33 | mutator, 34 | }); 35 | export default store; 36 | 37 | /* 38 | if (process.env.NODE_ENV === "development") { 39 | attachStoreToDevtools({ store: store, name: "app" }); 40 | } 41 | */ 42 | -------------------------------------------------------------------------------- /front/lib/store/mutator.ts: -------------------------------------------------------------------------------- 1 | import { Mutator } from "alo/store"; 2 | 3 | import { createState as createRouterState } from "./router/state"; 4 | 5 | export const mutator = new Mutator({ 6 | createState: function () { 7 | return { 8 | localMode: location.protocol === "file:", 9 | navActive: false, 10 | config: { 11 | colors: {} as any, 12 | title: null as string | null, 13 | footer: {} as any, 14 | }, 15 | router: createRouterState(), 16 | }; 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /front/lib/store/router/index.ts: -------------------------------------------------------------------------------- 1 | import { dispatchThunk, typeThunk, typeMutation } from "alo/store"; 2 | 3 | import * as Item from "../../models/tocItem"; 4 | import { reduceToc, getTocItemById, setTocItemId } from "./toc"; 5 | import { mutator } from "../mutator"; 6 | 7 | const routerMutator = typeMutation((state, action) => { 8 | state = reduceToc(state, action); 9 | 10 | return state; 11 | }); 12 | 13 | mutator.set( 14 | "router", 15 | (state, action) => { 16 | state.router = routerMutator(state.router, action); 17 | 18 | return state; 19 | }, 20 | true 21 | ); 22 | 23 | export const gotoItemById = ( 24 | itemId: number, 25 | computed, 26 | resetChapterId = false 27 | ) => { 28 | return typeThunk( 29 | async (ds): Promise => { 30 | var items = ds.getState().router.toc.items; 31 | var item = getTocItemById(itemId, items); 32 | 33 | if (item != null) { 34 | if (!Item.isViewableItem(item)) { 35 | let enclosedChildrenIds = Item.getEnclosedChildren(itemId); 36 | let enclosedChildren = enclosedChildrenIds.map((childId) => 37 | getTocItemById(childId, items) 38 | ); 39 | let viewableChildren = enclosedChildren.filter((child) => 40 | Item.isViewableItem(child) 41 | ); 42 | 43 | if (viewableChildren.length > 0) { 44 | return dispatchThunk( 45 | ds, 46 | gotoItemById(viewableChildren[0].id, resetChapterId) 47 | ); 48 | } else { 49 | let newItemId = itemId + 1; 50 | return dispatchThunk(ds, gotoItemById(newItemId, resetChapterId)); 51 | } 52 | } else { 53 | return dispatchThunk( 54 | ds, 55 | setTocItemId(itemId, computed, resetChapterId) 56 | ); 57 | } 58 | } 59 | } 60 | ); 61 | }; 62 | -------------------------------------------------------------------------------- /front/lib/store/router/state.ts: -------------------------------------------------------------------------------- 1 | type Item = { 2 | id: number; 3 | title: string; 4 | path: string; 5 | hasContent: boolean; 6 | chapters: any; 7 | }; 8 | 9 | export const createState = function () { 10 | return { 11 | rootUrl: "", 12 | toc: { 13 | items: [] as Item[], 14 | root: [], 15 | itemId: null as number | null, 16 | chapterId: null as number | null, 17 | itemContent: "", 18 | }, 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /front/lib/store/router/toc.ts: -------------------------------------------------------------------------------- 1 | import { observable } from "alo"; 2 | const $ = document.querySelectorAll.bind(document); 3 | import * as Item from "../../models/tocItem"; 4 | import store from ".."; 5 | import { typeThunk } from "alo/store"; 6 | 7 | export const setTocItemId = (id, computed, resetChapterId = false, body?) => 8 | typeThunk(async function (ds) { 9 | const state = ds.getState().router; 10 | 11 | // Push history only, if there already was a previous item id set 12 | if (state.toc.itemId != null && state.toc.itemId !== id) { 13 | const item = getTocItemById(id); 14 | 15 | if (item.hasContent === true) { 16 | if (body == null) { 17 | var contentUrl = Item.getUrl(item, computed, true); 18 | let response; 19 | try { 20 | response = await fetch(contentUrl); 21 | } catch (err) { 22 | location.href = Item.getUrl(item, computed, false); 23 | return; 24 | } 25 | 26 | const body = await response.text(); 27 | ds.dispatch(setTocItemContent(body)); 28 | } 29 | } 30 | } 31 | 32 | if (body) { 33 | ds.dispatch(setTocItemContent(body)); 34 | } 35 | 36 | return ds.dispatch({ 37 | type: "setTocItemId", 38 | payload: { 39 | id, 40 | resetChapterId, 41 | }, 42 | }); 43 | }); 44 | 45 | const reduceSetTocItemId = function (state, action) { 46 | if (action.type === "setTocItemId") { 47 | state.itemId = action.payload.id; 48 | if (action.payload.resetChapterId === true) { 49 | state.chapterId = 0; 50 | } 51 | } 52 | 53 | return state; 54 | }; 55 | 56 | export const setTocItemContent = function (content) { 57 | return { 58 | type: "setTocItemContent", 59 | payload: { 60 | content, 61 | }, 62 | }; 63 | }; 64 | 65 | const reduceSetTocItemContent = function (state, action) { 66 | if (action.type === "setTocItemContent") { 67 | state.itemContent = action.payload.content; 68 | } 69 | 70 | return state; 71 | }; 72 | 73 | export const setTocChapterId = function (id) { 74 | return { 75 | type: "setTocChapterId", 76 | payload: { 77 | id, 78 | }, 79 | }; 80 | }; 81 | 82 | const reduceSetTocChapterId = function (state, action) { 83 | if (action.type === "setTocChapterId") { 84 | state.chapterId = action.payload.id; 85 | } 86 | 87 | return state; 88 | }; 89 | 90 | export const initToc = async function (items, root) { 91 | return { 92 | type: "initToc", 93 | payload: { 94 | items, 95 | root, 96 | itemId: null, 97 | chapterId: 0, 98 | itemContent: null, 99 | }, 100 | }; 101 | }; 102 | const reduceInitToc = function (state, action) { 103 | if (action.type === "initToc") { 104 | state.toc = observable(action.payload); 105 | } 106 | 107 | return state; 108 | }; 109 | 110 | export const reduceToc = function (state, action) { 111 | if (action.type === "setTocItemId") { 112 | if (state.toc.itemId == null) { 113 | let item = state.toc.items[action.payload.id]; 114 | let pathName = location.pathname; 115 | // Certain static file servers remove the .html (looking at you zeit/serve) 116 | if (pathName.length > 1 && !pathName.toLowerCase().endsWith(".html")) { 117 | pathName += ".html"; 118 | } 119 | const pathIdx = pathName.indexOf(item.path.replace(".html", "")); 120 | pathName = pathName.slice(0, pathIdx); 121 | if (pathName[pathName.length - 1] != "/") { 122 | pathName += "/"; 123 | } 124 | state.rootUrl = 125 | (location.protocol === "file:" ? "file://" : location.origin) + 126 | pathName; 127 | 128 | // Make sure that the favicon always targets the root of the website 129 | var faviconEl = $(".favicon")[0]; 130 | if (faviconEl != null) { 131 | var faviconUrl = state.rootUrl + "favicon.ico"; 132 | faviconEl.href = faviconUrl; 133 | } 134 | } 135 | } 136 | 137 | let reducers = [ 138 | reduceSetTocItemId, 139 | reduceSetTocItemContent, 140 | reduceSetTocChapterId, 141 | ]; 142 | 143 | state = reduceInitToc(state, action); 144 | 145 | state.toc = reducers.reduce((acc, reducer) => { 146 | return reducer(acc, action); 147 | }, state.toc); 148 | 149 | return state; 150 | }; 151 | 152 | export const getTocItemById = function (id, items?) { 153 | if (items == null) { 154 | var state = store.getState().router; 155 | items = state.toc.items; 156 | } 157 | 158 | return items[id]; 159 | }; 160 | 161 | export const getTocItemsById = function (itemIds) { 162 | const routerState = store.getState().router; 163 | 164 | return itemIds.map((itemId) => { 165 | return routerState.toc.items[itemId]; 166 | }); 167 | }; 168 | -------------------------------------------------------------------------------- /front/lib/util/history.ts: -------------------------------------------------------------------------------- 1 | import { computed } from "../store/computed"; 2 | import { getCurrentTitle } from "./pageTitle"; 3 | import * as Item from "../models/tocItem"; 4 | 5 | export const addNewHistoryEntry = function (replaceHistory = false) { 6 | var hash = ""; 7 | if (computed.tocChapter != null && computed.tocChapter.hash != null) { 8 | hash = "#" + computed.tocChapter.hash; 9 | } 10 | var item = computed.tocItem; 11 | if (!item) return; 12 | 13 | var title = getCurrentTitle(); 14 | document.title = title; 15 | 16 | var historyState = { 17 | itemId: item.id, 18 | hash, 19 | title, 20 | }; 21 | 22 | var url = Item.getUrl(item, computed) + hash; 23 | 24 | if (replaceHistory) { 25 | history.replaceState(historyState, title, url); 26 | } else { 27 | history.pushState(historyState, title, url); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /front/lib/util/pageTitle.ts: -------------------------------------------------------------------------------- 1 | import store from "../store"; 2 | import { computed } from "../store/computed"; 3 | 4 | export const getCurrentTitle = function () { 5 | var appState = store.getState(); 6 | var item = computed.tocItem; 7 | 8 | var title = ""; 9 | if (appState != null && item != null) { 10 | if (item.title != null) title = item.title; 11 | const titleLower = title.toLowerCase(); 12 | const pageTitle = 13 | appState.config.title != null ? appState.config.title : ""; 14 | const pageTitleLower = pageTitle.toLowerCase(); 15 | if (titleLower !== pageTitleLower) { 16 | if (title.length > 0 && pageTitle.length > 0) { 17 | title += " - "; 18 | } 19 | title += pageTitle; 20 | } 21 | } 22 | 23 | return title; 24 | }; 25 | -------------------------------------------------------------------------------- /front/lib/vendor.ts: -------------------------------------------------------------------------------- 1 | import "bootstrap/dist/css/bootstrap.min.css"; 2 | import "icono/dist/icono.min.css"; 3 | import "./assets/vendor/highlight.js/highlight.css"; 4 | import "./assets/vendor/highlight.js/highlight.min.js"; 5 | // import "@lib/front/assets/vendor/modernizr.min.js"; 6 | -------------------------------------------------------------------------------- /front/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mallery-front", 3 | "amdName": "malleryFront", 4 | "source": "lib/main.ts", 5 | "main": "dist/main.js", 6 | "module": "dist/main.m.js", 7 | "umd:main": "dist/main.umd.js", 8 | "esmodule": "dist/main.modern.js", 9 | "types": "dist/main.d.ts" 10 | } 11 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | const yargs = require("yargs"); 2 | const path = require("path"); 3 | const cwd = process.cwd(); 4 | import * as util from "./util"; 5 | 6 | const loadConfigFile = function (configPath) { 7 | if (!path.isAbsolute(configPath)) { 8 | configPath = path.join(cwd, configPath); 9 | } 10 | 11 | return util 12 | .lstatP(configPath) 13 | .catch((err) => { 14 | return false; 15 | }) 16 | .then((res) => { 17 | return res !== false 18 | ? require("./" + path.relative(__dirname, configPath)) 19 | : false; 20 | }) 21 | .catch((err) => { 22 | console.error(err); 23 | console.error( 24 | `Something went wrong while reading the config file: "${configPath}"` 25 | ); 26 | process.exit(); 27 | }); 28 | }; 29 | 30 | const overwriteFromYargs = function (config, onlyLong, keyPrefix) { 31 | if (keyPrefix == null) keyPrefix = ""; 32 | if (onlyLong == null) onlyLong = false; 33 | Object.keys(config).forEach((k) => { 34 | if (["includes", "toc"].indexOf(k) >= 0) { 35 | return; 36 | } 37 | var yk = keyPrefix + k; 38 | var v = config[k]; 39 | var isBoolean = v === true || v === false; 40 | if (util.isObject(v)) { 41 | overwriteFromYargs(config[k], true, k + "-"); 42 | } else { 43 | var k1 = k[0]; 44 | if (yargs.argv[yk] !== undefined) { 45 | v = yargs.argv[yk]; 46 | } else if (onlyLong && yargs.argv[k1] !== undefined) { 47 | v = yargs.argv[k1]; 48 | } 49 | if (isBoolean) { 50 | config[k] = v === true || v === "true"; 51 | } else { 52 | config[k] = v; 53 | } 54 | } 55 | }); 56 | }; 57 | 58 | export const get = function () { 59 | let config = { 60 | brandIcon: true, 61 | colors: { 62 | accent: "#00BF63", 63 | }, 64 | title: "", 65 | footer: { 66 | html: "", 67 | }, 68 | paths: { 69 | config: "mallery.config.js", 70 | src: "docs", 71 | output: "site", 72 | public: "docs/public", 73 | }, 74 | includes: { 75 | plaintext: [".txt"], 76 | html: [".html"], 77 | markdown: [".md"], 78 | }, 79 | // TODO: Implement a server and watcher feature 80 | serve: false, 81 | }; 82 | 83 | // Several options can be provided with cli arguments 84 | if (yargs.argv._[0] != null) { 85 | config.paths.config = yargs.argv._[0]; 86 | } 87 | overwriteFromYargs(config.paths); 88 | overwriteFromYargs(config); 89 | 90 | return loadConfigFile(config.paths.config).then((fileConfig) => { 91 | if (fileConfig !== false) { 92 | if (fileConfig.paths != null) { 93 | var basename = path.basename(path.dirname(config.paths.config)); 94 | Object.keys(fileConfig.paths).forEach(function (k) { 95 | var v = fileConfig.paths[k]; 96 | if (v != null) { 97 | if (!path.isAbsolute(v)) { 98 | fileConfig.paths[k] = path.join(basename, v); 99 | } 100 | } 101 | }); 102 | } 103 | 104 | config = util.merge(config, fileConfig); 105 | } 106 | 107 | overwriteFromYargs(config.paths); 108 | overwriteFromYargs(config); 109 | 110 | // Transform paths to absolute paths 111 | Object.keys(config.paths).forEach(function (k) { 112 | var v = config.paths[k]; 113 | if (v != null) { 114 | if (!path.isAbsolute(v)) { 115 | config.paths[k] = path.join(cwd, v); 116 | } 117 | } 118 | }); 119 | 120 | return config; 121 | }); 122 | }; 123 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import * as util from "./util"; 4 | import path from "path"; 5 | import { compileFileP as markdownP } from "./markdown"; 6 | import ejs from "ejs"; 7 | import * as Toc from "./toc"; 8 | import * as config from "./config"; 9 | const pkg = require("../package.json"); 10 | 11 | config.get().then((config) => { 12 | const pkgPath = path.resolve(__dirname, ".."); 13 | const jsSrcPath = path.resolve(pkgPath, "front", "dist"); 14 | const jsDestPath = path.join(config.paths.output); 15 | const cssSrcPath = path.join(__dirname, "dist", "mallery.css"); 16 | const cssDestPath = path.join(config.paths.output, "app.min.css"); 17 | const assetsPath = path.resolve(pkgPath, "front", "lib", "assets"); 18 | const layoutSrcPath = path.join(assetsPath, "templates", "layout.ejs.html"); 19 | const faviconSrcPath = path.join(assetsPath, "images", "favicon.ico"); 20 | const faviconDestPath = path.join(config.paths.output, "favicon.ico"); 21 | const indexDestPath = path.join(config.paths.output, "index.html"); 22 | const pathCache = {}; 23 | const prepareOutput = function (config) { 24 | return util 25 | .clearDirP(config.paths.output) 26 | .catch((err) => { 27 | // console.error(err); 28 | }) 29 | .then(() => { 30 | var prom = Promise.resolve(); 31 | if (config.paths.public != null) { 32 | prom = prom 33 | .then(() => { 34 | return util.pathExists(config.paths.public); 35 | }) 36 | .then((publicExists) => { 37 | if (publicExists) { 38 | return util.copyP(config.paths.public, config.paths.output); 39 | } 40 | }); 41 | } 42 | 43 | return prom.then(() => { 44 | return util.mkdirP(config.paths.output); 45 | }); 46 | }) 47 | .then(() => { 48 | if (config.brandIcon === true) { 49 | return util.pathExists(faviconDestPath).then((faviconExists) => { 50 | if (!faviconExists) { 51 | return util.copyP(faviconSrcPath, faviconDestPath); 52 | } 53 | }); 54 | } 55 | }) 56 | .then(() => { 57 | return util.copyP(jsSrcPath, jsDestPath, { recursive: true }); 58 | }) 59 | .then(() => { 60 | // return util.copyP(cssSrcPath, cssDestPath); 61 | }); 62 | }; 63 | 64 | if (config.serve) { 65 | } else { 66 | prepareOutput(config) 67 | .then(() => { 68 | // Load the basic toc from the filesystem 69 | return Toc.createTocFromFolderP(config.paths.src); 70 | }) 71 | .then((toc) => { 72 | if (config.toc != null) { 73 | // Overwrite the filesystem toc with the config toc 74 | return Toc.mergeTocConfigWithToc(toc, config.toc); 75 | } else { 76 | return toc; 77 | } 78 | }) 79 | .then((toc) => { 80 | toc = Toc.calculateItemLevels(toc); 81 | var indexItemId; 82 | toc.items.forEach((item, idx) => { 83 | item.id = idx; 84 | if (item.path != null) { 85 | // The item title gets generated from this copy 86 | var itemOrigPath = item.path; 87 | 88 | // Several things happen here because we need to make sure, that 89 | // the first entry file gets renamed to index 90 | /* 91 | * TODO: when the first possible entry is in a sub folder 92 | * we need to create a copy index in the root! This is missing 93 | */ 94 | if (indexItemId == null) indexItemId = idx; 95 | if (indexItemId === idx) { 96 | // TODO: Is this really fine? :) 97 | item.path = item.path.replace(item.name, "index.html"); 98 | } else { 99 | // TODO: Better toLowercase item name here! (Should match case insensitive) 100 | if (item.name.indexOf("index") >= 0) { 101 | var newPath = item.path.replace(item.name, ""); 102 | var newPath = 103 | newPath + item.name.replace("index", "index" + idx); 104 | item.path = newPath; 105 | } 106 | } 107 | 108 | if (item.ext != null && item.ext != "") { 109 | item.path = item.path.replace(item.ext, ".html"); 110 | } 111 | item.path = util.replace(" ", "-", item.path); 112 | item.htmlPath = path.join("raw", item.path); 113 | const itemPathCache = (pathCache[item.htmlPath] = {}); 114 | itemPathCache.absoluteHtmlOutputPath = path.join( 115 | config.paths.output, 116 | "raw", 117 | item.path 118 | ); 119 | itemPathCache.absolutePath = item.absolutePath; 120 | delete item.absolutePath; 121 | itemPathCache.absoluteOutputPath = path.join( 122 | config.paths.output, 123 | item.path 124 | ); 125 | let baseUrl = ""; 126 | let levelIdx = 0; 127 | while (levelIdx < item.level) { 128 | baseUrl += "../"; 129 | levelIdx++; 130 | } 131 | if (baseUrl === "") baseUrl = "./"; 132 | item.baseUrl = baseUrl; 133 | } 134 | if (item.title == null && itemOrigPath != null) { 135 | item.title = path.basename(itemOrigPath).replace(item.ext, ""); 136 | item.title = util.replace("-", " ", item.title); 137 | item.title = util.replace("_", " ", item.title); 138 | item.title = util.replace(" ", " - ", item.title); 139 | item.title = item.title 140 | .split(" ") 141 | .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) 142 | .join(" "); 143 | } 144 | // Remember: Link items doesnt have paths 145 | if (!item.isDir && item.path != null) { 146 | } 147 | }); 148 | // TODO: Find better place for this 149 | return util.readFileP(layoutSrcPath, "utf-8").then((layoutStr) => { 150 | return { 151 | layoutStr, 152 | toc, 153 | }; 154 | }); 155 | }) 156 | .then((res) => { 157 | let toc = res.toc; 158 | // Add this to the appropriate section 159 | config.layoutStr = res.layoutStr; 160 | let prom = Promise.resolve(); 161 | toc.items.forEach((item) => { 162 | if (item.path != null) { 163 | prom = prom 164 | // Creating directories in the ouput path 165 | .then(() => { 166 | if (!item.isDir) { 167 | // For files we need to create the file's parent dir 168 | return util 169 | .mkdirP( 170 | path.dirname(pathCache[item.htmlPath].absoluteOutputPath) 171 | ) 172 | .then(() => { 173 | return util.mkdirP( 174 | path.dirname( 175 | pathCache[item.htmlPath].absoluteHtmlOutputPath 176 | ) 177 | ); 178 | }); 179 | } 180 | }); 181 | } 182 | }); 183 | 184 | prom = prom.then(() => { 185 | var promises = []; 186 | toc.items.forEach((item) => { 187 | let prom2 = Promise.resolve(); 188 | if (!item.isDir) { 189 | Object.keys(config.includes).forEach(function (type) { 190 | const extensions = config.includes[type]; 191 | extensions.forEach((extension) => { 192 | if (extension === item.ext) { 193 | prom2 = prom2.then(() => { 194 | switch (type) { 195 | case "markdown": 196 | return markdownP( 197 | pathCache[item.htmlPath].absolutePath 198 | ); 199 | break; 200 | case "html": 201 | return util.readFileP( 202 | pathCache[item.htmlPath].absolutePath, 203 | "utf-8" 204 | ); 205 | break; 206 | case "plaintext": 207 | return util 208 | .readFileP( 209 | pathCache[item.htmlPath].absolutePath, 210 | "utf-8" 211 | ) 212 | .then((content) => { 213 | return util.escapeHtml(content); 214 | }); 215 | break; 216 | default: 217 | return false; 218 | break; 219 | } 220 | }); 221 | } 222 | }); 223 | }); 224 | 225 | // Read chapters of html 226 | prom2 = prom2.then((content) => { 227 | if (content !== false && content != null) { 228 | item.chapters = Toc.getHtmlChapters(content); 229 | } 230 | 231 | return content; 232 | }); 233 | 234 | prom2 = prom2.then((content) => { 235 | if (content !== false && content != null) { 236 | item.hasContent = true; 237 | return util 238 | .writeFileP( 239 | pathCache[item.htmlPath].absoluteHtmlOutputPath, 240 | content 241 | ) 242 | .then(() => { 243 | return util.writeFileP( 244 | pathCache[item.htmlPath].absoluteOutputPath, 245 | content 246 | ); 247 | }); 248 | } else { 249 | item.hasContent = false; 250 | } 251 | }); 252 | 253 | promises.push(prom2); 254 | } 255 | }); 256 | 257 | return Promise.all(promises); 258 | }); 259 | 260 | prom = prom.then(() => { 261 | config.toc = toc; 262 | return config; 263 | }); 264 | 265 | return prom; 266 | }) 267 | .then((config) => { 268 | let fileCopyProm = Promise.resolve(); 269 | const tpl = ejs.compile(config.layoutStr); 270 | config.toc.items.forEach((item, itemId) => { 271 | if (item.hasContent) { 272 | fileCopyProm = fileCopyProm 273 | .then(() => { 274 | return util.readFileP( 275 | pathCache[item.htmlPath].absoluteOutputPath, 276 | "utf-8" 277 | ); 278 | }) 279 | .then((content) => { 280 | config.toc.activeItemId = itemId; 281 | var itemTitle = item.title != null ? item.title : ""; 282 | const itemTitleLower = itemTitle.toLowerCase(); 283 | const pageTitle = config.title != null ? config.title : ""; 284 | const pageTitleLower = pageTitle.toLowerCase(); 285 | if (itemTitleLower !== pageTitleLower) { 286 | if (itemTitle.length > 0 && pageTitle.length > 0) { 287 | itemTitle += " - "; 288 | } 289 | itemTitle += pageTitle; 290 | } 291 | 292 | const result = tpl({ 293 | version: pkg.version, 294 | baseUrl: item.baseUrl, 295 | content, 296 | title: itemTitle, 297 | options: JSON.stringify( 298 | { 299 | config: { 300 | colors: config.colors, 301 | title: config.title, 302 | footer: config.footer, 303 | }, 304 | toc: config.toc, 305 | }, 306 | null, 307 | " " 308 | ), 309 | }); 310 | 311 | return util.writeFileP( 312 | pathCache[item.htmlPath].absoluteOutputPath, 313 | result 314 | ); 315 | }); 316 | } 317 | }); 318 | 319 | return fileCopyProm; 320 | }); 321 | 322 | return; 323 | } 324 | }); 325 | -------------------------------------------------------------------------------- /lib/markdown.js: -------------------------------------------------------------------------------- 1 | const promisify = require("util").promisify; 2 | const fs = require("fs"); 3 | const readFileP = promisify(fs.readFile); 4 | const writeFileP = promisify(fs.writeFile); 5 | 6 | const remark = require("remark"); 7 | const remarkHtml = require("remark-html"); 8 | const remarkToc = require("remark-toc"); 9 | const remarkHighlight = require("remark-highlight.js"); 10 | const remarkHeadings = require("remark-autolink-headings"); 11 | const remarkGithub = require("remark-github"); 12 | const remarkGemoji = require("remark-gemoji-to-emoji"); 13 | const remarkSlug = require("remark-slug"); 14 | 15 | // Found from https://github.com/wooorm/remark-slug/blob/master/index.js#L32 16 | function patch(context, key, value) { 17 | if (!context[key]) { 18 | context[key] = value; 19 | } 20 | 21 | return context[key]; 22 | } 23 | 24 | const remarkWithPlugins = remark() 25 | .use(remarkToc) 26 | .use(remarkHtml) 27 | .use(remarkHighlight) 28 | .use(function (opts) { 29 | return function (root, file) { 30 | var mutateItem = function (item) { 31 | if (item != null) { 32 | if (item.type == "blockquote") { 33 | var data = patch(item, "data", {}); 34 | var props = patch(data, "hProperties", {}); 35 | props.class = "blockquote"; 36 | if (item.children != null) { 37 | item.children.forEach((child) => { 38 | if (child.type == "paragraph") { 39 | var data = patch(child, "data", {}); 40 | var props = patch(data, "hProperties", {}); 41 | props.class = "mb-0"; 42 | } 43 | }); 44 | } 45 | } 46 | // Images should be responsive 47 | if (item.type == "image") { 48 | var data = patch(item, "data", {}); 49 | var props = patch(data, "hProperties", {}); 50 | props.class = "img-fluid"; 51 | } 52 | // Links will be opened in new tabs 53 | if (item.type == "link") { 54 | if (item.url != null && item.url[0] != "#") { 55 | var data = patch(item, "data", {}); 56 | var props = patch(data, "hProperties", {}); 57 | props.target = "_blank"; 58 | } 59 | } 60 | // Tables are formated for bootstrap 61 | if (item.type == "table") { 62 | var data = patch(item, "data", {}); 63 | var props = patch(data, "hProperties", {}); 64 | props.class = "table table-hover table-bordered"; 65 | } 66 | if (item.children != null) { 67 | mutateChildren(item.children); 68 | } 69 | } 70 | }; 71 | var mutateChildren = function (children) { 72 | children.forEach((item) => mutateItem(item)); 73 | }; 74 | mutateChildren(root.children); 75 | }; 76 | }) 77 | .use(remarkSlug) 78 | // TODO: These links doesnt work when the heading begins with a number 79 | .use(remarkHeadings, { 80 | content: { 81 | type: "element", 82 | tagName: "span", 83 | properties: { 84 | className: ["toc-link"], 85 | }, 86 | children: [ 87 | { 88 | type: "element", 89 | tagName: "span", 90 | properties: { 91 | className: ["icono-chain"], 92 | }, 93 | }, 94 | ], 95 | }, 96 | }) 97 | .use(remarkGemoji); 98 | 99 | const remarkWithPluginsP = promisify( 100 | remarkWithPlugins.process.bind(remarkWithPlugins) 101 | ); 102 | 103 | export const compileFileP = function (srcPath, destPath) { 104 | return readFileP(srcPath) 105 | .then(remarkWithPluginsP) 106 | .then((res) => { 107 | if (destPath != null) { 108 | return writeFileP(destPath, res.contents).then(() => res.contents); 109 | } else { 110 | return res.contents; 111 | } 112 | }); 113 | }; 114 | -------------------------------------------------------------------------------- /lib/toc.js: -------------------------------------------------------------------------------- 1 | import * as util from "./util"; 2 | const path = require("path"); 3 | 4 | /** 5 | * Creates a toc object 6 | * @param {Array} items All items of the toc 7 | * @param {Array} root Only the root items 8 | * @return {Object} Toc object 9 | */ 10 | const createToc = function (items, root) { 11 | if (items == null) items = []; 12 | if (root == null) root = []; 13 | 14 | return { 15 | items, 16 | root, 17 | }; 18 | }; 19 | 20 | const findByPath = util.findBy("path"); 21 | 22 | export const createTocFromFolderP = function (pathParam, toc, rootPath) { 23 | if (toc == null) toc = createToc(); 24 | if (rootPath == null) rootPath = pathParam; 25 | 26 | return util.readdirP(pathParam).then((files) => { 27 | var localItemIds = []; 28 | var prom = Promise.resolve(); 29 | files.forEach((f) => { 30 | var fullPath = path.join(pathParam, f); 31 | 32 | prom = prom 33 | .then(() => { 34 | return util.lstatP(fullPath); 35 | }) 36 | .then((stats) => { 37 | var id = 38 | toc.items.push({ 39 | ext: path.extname(f), 40 | isDir: stats.isDirectory(), 41 | absolutePath: fullPath, 42 | path: fullPath.replace(rootPath + path.sep, ""), 43 | name: f, 44 | }) - 1; 45 | localItemIds.push(id); 46 | 47 | if (toc.items[id].isDir) { 48 | return createTocFromFolderP(fullPath, toc, rootPath).then( 49 | (newToc) => { 50 | toc = newToc; 51 | toc.items[id].children = toc.root; 52 | toc.root = []; 53 | } 54 | ); 55 | } 56 | }); 57 | }); 58 | 59 | prom = prom.then(() => { 60 | toc.root = localItemIds; 61 | return toc; 62 | }); 63 | 64 | return prom; 65 | }); 66 | }; 67 | 68 | export const mergeTocConfigWithToc = function (toc, tocConfig) { 69 | var newToc = createToc(); 70 | 71 | var addChildren = function (children) { 72 | var ids = []; 73 | children.forEach(function (childId) { 74 | var childItem = toc.items[childId]; 75 | var id = newToc.items.push(childItem) - 1; 76 | ids.push(id); 77 | 78 | if (childItem.children != null) { 79 | childItem.children = addChildren(childItem.children); 80 | } 81 | }); 82 | 83 | return ids; 84 | }; 85 | var mergeItems = function (items, basePath) { 86 | if (basePath == null) basePath = ""; 87 | 88 | var localItemIds = []; 89 | 90 | items.forEach((tocItem) => { 91 | var item = {}; 92 | 93 | if (tocItem.path != null) { 94 | tocItem.path = path.join(basePath, tocItem.path); 95 | var origId = findByPath(toc.items, tocItem.path); 96 | if (origId !== false) { 97 | item = toc.items[origId]; 98 | } 99 | } 100 | 101 | item.href = tocItem.href; 102 | if (tocItem.title != null) { 103 | item.title = tocItem.title; 104 | } 105 | 106 | var id = newToc.items.push(item) - 1; 107 | 108 | if (tocItem.children != null) { 109 | newToc.items[id].children = mergeItems(tocItem.children, tocItem.path); 110 | newToc.items[id].isDir = true; 111 | } else if (item.children != null) { 112 | newToc.items[id].children = addChildren(item.children); 113 | } 114 | 115 | localItemIds.push(id); 116 | }); 117 | 118 | return localItemIds; 119 | }; 120 | 121 | newToc.root = mergeItems(tocConfig); 122 | return newToc; 123 | }; 124 | 125 | export const calculateItemLevels = function (toc, items, level) { 126 | if (level == null) level = 0; 127 | if (items == null) items = toc.root; 128 | items.forEach(function (itemId) { 129 | let item = toc.items[itemId]; 130 | item.level = level; 131 | if (item.children != null) { 132 | calculateItemLevels(toc, item.children, level + 1); 133 | } 134 | }); 135 | 136 | return toc; 137 | }; 138 | 139 | export const getHtmlChapters = function (html) { 140 | const chapters = { 141 | root: [], 142 | items: [], 143 | }; 144 | 145 | const headingRegexStr = 146 | '([\\s\\S]*?)<\\/h\\1(?: .*?)?>'; 147 | const headingGlobalRegex = new RegExp(headingRegexStr, "g"); 148 | const headingRegex = new RegExp(headingRegexStr); 149 | const htmlTagsRegex = /<(?:.|\n)*?>/gm; 150 | 151 | let matches = html.match(headingGlobalRegex); 152 | 153 | if (matches != null && matches.length > 0) { 154 | matches = matches.map(function (match) { 155 | const subMatches = match.match(headingRegex); 156 | const hash = subMatches[2]; 157 | const heading = subMatches[3].replace(htmlTagsRegex, ""); 158 | return { level: subMatches[1], heading: heading, hash }; 159 | }); 160 | 161 | const pushHeadingsRecursive = function (matches) { 162 | var head = matches[0]; 163 | var tail = matches.slice(1); 164 | var rest = []; 165 | var broken = false; 166 | var children = []; 167 | tail.forEach(function (item) { 168 | if (item.level === head.level) { 169 | rest.push(item); 170 | broken = true; 171 | } else if (broken === false && head.level < item.level) { 172 | children.push(item); 173 | } else { 174 | rest.push(item); 175 | } 176 | }); 177 | 178 | if (children.length > 0) { 179 | children = pushHeadingsRecursive(children); 180 | } 181 | 182 | head.children = children; 183 | 184 | if (rest.length > 0) { 185 | rest = pushHeadingsRecursive(rest); 186 | } 187 | 188 | return [head, ...rest]; 189 | }; 190 | 191 | const recursiveHeadings = pushHeadingsRecursive(matches); 192 | 193 | const normalizeRecursive = function (items) { 194 | return items.map(function (chapter) { 195 | const idx = chapters.items.push(chapter) - 1; 196 | chapter.id = idx; 197 | chapter.children = normalizeRecursive(chapter.children); 198 | return idx; 199 | }); 200 | }; 201 | 202 | chapters.root = normalizeRecursive(recursiveHeadings); 203 | } 204 | 205 | return chapters; 206 | }; 207 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const promisify = require("util").promisify; 3 | const fs = require("fs-extra"); 4 | export const readdirP = promisify(fs.readdir); 5 | export const rmdirP = promisify(fs.rmdir); 6 | export const unlinkP = promisify(fs.unlink); 7 | export const mkdirP = promisify(fs.ensureDir); 8 | export const writeFileP = promisify(fs.writeFile); 9 | export const readFileP = promisify(fs.readFile); 10 | export const lstatP = promisify(fs.lstat); 11 | export const copyP = fs.copy; 12 | export const merge = require("lodash.merge"); 13 | export const pathExists = promisify(fs.pathExists); 14 | 15 | export const isObject = function (value) { 16 | const type = typeof value; 17 | return value != null && (type == "object" || type == "function"); 18 | }; 19 | 20 | export const clearDirP = function (dirPath) { 21 | return readdirP(dirPath) 22 | .catch((err) => { 23 | throw err; 24 | }) 25 | .then((files) => { 26 | var unlinkPromises = files.map((f) => { 27 | const fullPath = path.join(dirPath, f); 28 | return lstatP(fullPath).then((stat) => { 29 | if (stat.isDirectory()) { 30 | return clearDirP(fullPath).then(() => { 31 | return rmdirP(fullPath); 32 | }); 33 | } else { 34 | return unlinkP(fullPath); 35 | } 36 | }); 37 | }); 38 | return Promise.all(unlinkPromises); 39 | }); 40 | }; 41 | 42 | /** 43 | * Creates a new function which returns idx of first entry by prop. 44 | * -1 if nothing found. 45 | * @param {string} propName Which prop to compare 46 | * @return {integer} Idx of found item 47 | */ 48 | export const findBy = function (propName) { 49 | return function (arr, value) { 50 | const len = arr.length; 51 | let idx = 0; 52 | let id = -1; 53 | while (idx <= len) { 54 | if (arr[idx] && arr[idx][propName]) { 55 | if (arr[idx][propName] === value) { 56 | id = idx; 57 | idx = len; 58 | } 59 | } 60 | idx++; 61 | } 62 | 63 | return id; 64 | }; 65 | }; 66 | 67 | export const escapeHtml = function (html) { 68 | return html 69 | .replace(/&/g, "&") 70 | .replace(//g, ">") 72 | .replace(/"/g, """) 73 | .replace(/'/g, "'"); 74 | }; 75 | 76 | export const escapeRegex = function (regex) { 77 | return regex.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"); 78 | }; 79 | 80 | export const replace = function (searchValue, replaceValue, target) { 81 | searchValue = escapeRegex(searchValue); 82 | var regex = new RegExp(searchValue, "g"); 83 | return target.replace(regex, replaceValue); 84 | }; 85 | -------------------------------------------------------------------------------- /mallery.config.js: -------------------------------------------------------------------------------- 1 | const pkg = require("./package"); 2 | 3 | let toc = [ 4 | { title: pkg.name, path: "README.md" }, 5 | { 6 | path: "installation.md", 7 | title: "Installation", 8 | }, 9 | { 10 | path: "usage.md", 11 | title: "Usage", 12 | }, 13 | { title: "Changelog", path: "CHANGELOG.md" }, 14 | { title: "Contributing", path: "CONTRIBUTING.md" }, 15 | ]; 16 | 17 | if (pkg.repository && pkg.repository.url) { 18 | toc.push({ title: "Repository", href: pkg.repository.url }); 19 | } 20 | 21 | toc.push({ 22 | path: "examples", 23 | title: "Demo", 24 | children: [ 25 | { 26 | path: "test.md", 27 | }, 28 | { 29 | path: "readme2.md", 30 | title: "README", 31 | }, 32 | { 33 | title: "Hello world", 34 | path: "index.md", 35 | }, 36 | { 37 | path: "subfolder2", 38 | }, 39 | { 40 | title: "Subfolder", 41 | path: "subfolder", 42 | children: [ 43 | { 44 | path: "more-infos.md", 45 | }, 46 | ], 47 | }, 48 | { 49 | title: "Google", 50 | href: "https://google.ch", 51 | }, 52 | { 53 | path: "more tests", 54 | }, 55 | ], 56 | }); 57 | 58 | module.exports = { 59 | title: pkg.name, 60 | paths: { 61 | src: "./docs", 62 | output: "./site", 63 | public: "./docs/public", 64 | }, 65 | toc, 66 | }; 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mallery", 3 | "amdName": "mallery", 4 | "version": "1.0.0-alpha.7", 5 | "description": "Static documentation site generator", 6 | "source": "lib/main.js", 7 | "main": "dist/main.js", 8 | "module": "dist/main.m.js", 9 | "umd:main": "dist/main.umd.js", 10 | "esmodule": "dist/main.modern.js", 11 | "types": "dist/main.d.ts", 12 | "authors": [ 13 | "Katja Lutz " 14 | ], 15 | "homepage": "https://github.com/malleryjs/mallery", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/malleryjs/mallery.git" 19 | }, 20 | "license": "MIT", 21 | "bin": { 22 | "mallery": "./dist/main.js" 23 | }, 24 | "devDependencies": { 25 | "@types/chai": "^4.2.11", 26 | "@types/fs-extra": "^9.0.1", 27 | "@types/jest": "^25.2.3", 28 | "@types/node": "^14.0.11", 29 | "alo": "^4.0.0-alpha.25", 30 | "badgen": "^3.1.0", 31 | "bootstrap": "^4.4.1", 32 | "chai": "^4.2.0", 33 | "clsx": "^1.0.4", 34 | "emotion": "^10.0.27", 35 | "fs-extra": "^9.0.1", 36 | "jest": "^26.0.1", 37 | "mallery": "1.0.0-alpha.4", 38 | "microbundle": "github:developit/microbundle#master", 39 | "icono": "^1.3.2", 40 | "preact": "^10.4.4", 41 | "prettier": "^2.0.5", 42 | "standard-version": "^8.0.0", 43 | "ts-jest": "^26.1.0", 44 | "ejs": "^3.0.1", 45 | "lodash.merge": "^4.6.2", 46 | "sass": "^1.26.5", 47 | "yargs": "^14.2.2", 48 | "typescript": "^3.9.3", 49 | "ts-node": "^8.10.2" 50 | }, 51 | "dependencies": { 52 | "remark": "^11.0.2", 53 | "remark-autolink-headings": "^5.2.1", 54 | "remark-gemoji-to-emoji": "^1.1.0", 55 | "remark-github": "^8.0.0", 56 | "remark-highlight.js": "^5.2.0", 57 | "remark-html": "^10.0.0", 58 | "remark-slug": "^5.1.2", 59 | "remark-toc": "^6.0.0", 60 | "yargs": "^14.2.2" 61 | }, 62 | "files": [ 63 | "lib", 64 | "dist", 65 | "front/package.json", 66 | "front/lib", 67 | "front/dist" 68 | ], 69 | "standard-version": { 70 | "scripts": { 71 | "postchangelog": "node scripts/badges.js && node scripts/site.js && git add assets site README.md" 72 | } 73 | }, 74 | "jest": { 75 | "preset": "ts-jest/presets/js-with-ts", 76 | "coverageReporters": [ 77 | "json", 78 | "lcov", 79 | "text", 80 | "clover", 81 | "json-summary" 82 | ], 83 | "coveragePathIgnorePatterns": [ 84 | "main.(?:tsx|ts|jsx|js)", 85 | "/lib/tpl-lib/", 86 | "/node_modules/" 87 | ], 88 | "collectCoverageFrom": [ 89 | "lib/**/*.{ts,tsx,js,jsx}", 90 | "front/lib/**/*.{ts,tsx,js,jsx}" 91 | ] 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /scripts/badges.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require("fs"); 4 | const { assetsDir } = require("./lib"); 5 | const { badgen } = require("badgen"); 6 | 7 | const pkg = require("./../package"); 8 | 9 | fs.writeFileSync( 10 | assetsDir("badge.style.svg"), 11 | badgen({ 12 | label: "code style", 13 | status: "prettier", 14 | }) 15 | ); 16 | 17 | fs.writeFileSync( 18 | assetsDir("badge.license.svg"), 19 | badgen({ 20 | label: "license", 21 | status: pkg.license, 22 | }) 23 | ); 24 | 25 | fs.writeFileSync( 26 | assetsDir("badge.npm.svg"), 27 | badgen({ 28 | label: "npm", 29 | status: pkg.version, 30 | }) 31 | ); 32 | 33 | const getCoveragePct = function () { 34 | const coverage = require("./../coverage/coverage-summary"); 35 | 36 | let pcts = []; 37 | for (const v of Object.values(coverage.total)) { 38 | if (v.total === 0) { 39 | continue; 40 | } 41 | pcts.push(v.pct); 42 | } 43 | 44 | return pcts.length 45 | ? pcts.reduce((acc, pct) => acc + pct, 0) / pcts.length 46 | : 100; 47 | }; 48 | 49 | fs.writeFileSync( 50 | assetsDir("badge.coverage.svg"), 51 | badgen({ 52 | label: "coverage", 53 | status: Math.round(getCoveragePct()) + "%", 54 | }) 55 | ); 56 | -------------------------------------------------------------------------------- /scripts/before-publish.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { spawnScript } = require("./lib"); 4 | 5 | spawnScript("reformat"); 6 | spawnScript("build"); 7 | spawnScript("create-type-proxies"); 8 | spawnScript("coverage"); 9 | 10 | console.log("Manual action: `npx standard-version -a -r major|minor|patch`"); 11 | -------------------------------------------------------------------------------- /scripts/build-core.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { spawn, args, buildArgs, rmDir, pkgDir } = require("./lib"); 4 | 5 | (async () => { 6 | await rmDir(pkgDir("dist"), { recursive: true }); 7 | spawn("npx", ["microbundle", "--raw", ...buildArgs(), ...args]); 8 | })(); 9 | -------------------------------------------------------------------------------- /scripts/build-front.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { spawn, args, buildArgs, rmDir, pkgDir } = require("./lib"); 4 | 5 | (async () => { 6 | await rmDir(pkgDir("front", "dist"), { recursive: true }); 7 | spawn("npx", [ 8 | "microbundle", 9 | "--cwd", 10 | "front", 11 | "--css-modules", 12 | "false", 13 | "--raw", 14 | ...buildArgs(), 15 | ...args, 16 | ]); 17 | })(); 18 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { spawnScripts } = require("./lib"); 4 | 5 | spawnScripts("build"); 6 | -------------------------------------------------------------------------------- /scripts/check-gyp.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Checks for modules which are not pure javascript 5 | * 6 | * Afterwards use "npm ls" to find the parent dependencies 7 | * As example fsevents has a binding.gyp 8 | * "npm ls fsevents" will show, that at the root it was required by nodemon 9 | * └─┬ nodemon@1.17.4 10 | * └─┬ chokidar@2.0.3 11 | * └── fsevents@1.2.4 12 | * 13 | * Pure bash alternative of purify.js is "find node_modules/ | grep binding.gyp || echo pure" 14 | */ 15 | 16 | const path = require("path"); 17 | const { pkgDir } = require("./lib"); 18 | const fs = require("fs"); 19 | 20 | const nodeModulesDir = pkgDir("node_modules"); 21 | const unfilteredModuleDirs = fs.readdirSync(nodeModulesDir); 22 | const moduleBindings = unfilteredModuleDirs.map((moduleDir) => 23 | path.resolve(nodeModulesDir, moduleDir, "binding.gyp") 24 | ); 25 | const existingBindings = moduleBindings.filter((file) => fs.existsSync(file)); 26 | const modules = existingBindings.map((file) => { 27 | let module = { 28 | requiredBy: [], 29 | }; 30 | 31 | module.dir = path.dirname(file); 32 | module.name = path.basename(module.dir); 33 | 34 | const pkgFile = path.resolve(module.dir, "package.json"); 35 | const pkg = require(pkgFile); 36 | 37 | if (pkg._requiredBy) { 38 | module.requiredBy = pkg._requiredBy; 39 | } 40 | 41 | return module; 42 | }); 43 | 44 | modules.forEach((module) => { 45 | console.log(module.name, module); 46 | }); 47 | -------------------------------------------------------------------------------- /scripts/check-license.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const { pkgDir } = require("./lib"); 6 | 7 | const nodeModulesPath = pkgDir("node_modules"); 8 | const licenses = fs 9 | .readdirSync(nodeModulesPath) 10 | .map((moduleName) => { 11 | const modulePkg = path.join(nodeModulesPath, moduleName, "package.json"); 12 | if (!fs.existsSync(modulePkg)) { 13 | return; 14 | } 15 | 16 | const pkg = require(modulePkg); 17 | if (!pkg.license) { 18 | return; 19 | } 20 | 21 | return { moduleName, license: pkg.license }; 22 | }) 23 | .reduce((licenses, module) => { 24 | if (!module) return licenses; 25 | 26 | let { moduleName, license } = module; 27 | 28 | if (Array.isArray(license)) { 29 | license = license.join(", "); 30 | } 31 | 32 | if (typeof license === "object") { 33 | if (license.type) { 34 | license = license.type; 35 | } else { 36 | license = JSON.stringify(license); 37 | } 38 | } 39 | 40 | licenses[license] = licenses[license] || []; 41 | licenses[license].push(moduleName); 42 | 43 | return licenses; 44 | }, {}); 45 | 46 | console.log(licenses); 47 | -------------------------------------------------------------------------------- /scripts/cleanup.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { pkgDir, rmDir } = require("./lib"); 4 | 5 | (async () => { 6 | await rmDir(pkgDir("node_modules", ".cache"), { recursive: true }); 7 | await rmDir(pkgDir("coverage"), { recursive: true }); 8 | })(); 9 | -------------------------------------------------------------------------------- /scripts/coverage.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { spawnScript, args } = require("./lib"); 4 | 5 | spawnScript("test", ["--coverage", "--passWithNoTests", ...args]); 6 | -------------------------------------------------------------------------------- /scripts/create-type-proxies.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { writeFile, pkgDir } = require("./lib"); 4 | 5 | const createProxy = async ( 6 | module, 7 | exportDefault = false, 8 | exportNamed = true 9 | ) => { 10 | const mainPath = `./${module}/lib/main`; 11 | let proxySrc = ""; 12 | if (exportNamed) { 13 | proxySrc += `export * from "${mainPath}";\n`; 14 | } 15 | 16 | if (exportDefault) { 17 | proxySrc += `import DefaultProxy from "${mainPath}"; 18 | export default DefaultProxy;\n`; 19 | } 20 | 21 | await writeFile(pkgDir(module, "dist", "main.d.ts"), proxySrc); 22 | }; 23 | 24 | (async () => { 25 | // await createProxy("some-module"); 26 | })(); 27 | -------------------------------------------------------------------------------- /scripts/lib.js: -------------------------------------------------------------------------------- 1 | const { spawnSync } = require("child_process"); 2 | const path = require("path"); 3 | const fs = require("fs-extra"); 4 | const pkg = require("../package.json"); 5 | 6 | exports.upperFirst = (str) => str.charAt(0).toUpperCase() + str.slice(1); 7 | exports.exists = fs.exists; 8 | exports.resolve = path.resolve; 9 | exports.extname = path.extname; 10 | exports.basename = path.basename; 11 | exports.readDir = fs.readdir; 12 | exports.mkDir = fs.mkdir; 13 | exports.writeFile = fs.writeFile; 14 | exports.readFile = fs.readFile; 15 | exports.writeJson = fs.writeJson; 16 | exports.rmDir = fs.rmdir; 17 | exports.copy = fs.copy; 18 | exports.chmod = fs.chmod; 19 | exports.pkgDir = (...suffix) => exports.resolve(__dirname, "..", ...suffix); 20 | exports.scriptsDir = (...suffix) => exports.pkgDir("scripts", ...suffix); 21 | exports.assetsDir = (...suffix) => exports.pkgDir("assets", ...suffix); 22 | exports.args = process.argv.slice(2); 23 | exports.spawn = ( 24 | command, 25 | args, 26 | options = { stdio: "inherit", encoding: "utf8" } 27 | ) => { 28 | return spawnSync(command, args, options); 29 | }; 30 | exports.spawnScript = (script, args = [], options) => { 31 | return exports.spawn( 32 | "node", 33 | [exports.scriptsDir(`${script}.js`), ...args], 34 | options 35 | ); 36 | }; 37 | exports.spawnScripts = async (prefix, args = [], options) => { 38 | const files = await exports.readDir(this.scriptsDir()); 39 | for (const file of files) { 40 | if (!file.startsWith(prefix + "-")) { 41 | continue; 42 | } 43 | 44 | const fileExtension = exports.extname(file); 45 | const script = exports.basename(file, fileExtension); 46 | exports.spawnScript(script, args, options); 47 | } 48 | }; 49 | exports.buildArgs = () => { 50 | const args = []; 51 | 52 | const externals = [pkg.name]; 53 | args.push("--external", externals.join(",")); 54 | 55 | args.push("--define", "process.env.NODE_ENV=production"); 56 | 57 | return args; 58 | }; 59 | -------------------------------------------------------------------------------- /scripts/module.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { 4 | args, 5 | exists, 6 | pkgDir, 7 | mkDir, 8 | writeJson, 9 | resolve, 10 | upperFirst, 11 | writeFile, 12 | scriptsDir, 13 | chmod, 14 | } = require("./lib"); 15 | 16 | const updatePkg = async function (moduleName, pkg) { 17 | pkg.files.push( 18 | `${moduleName}/package.json`, 19 | `${moduleName}/lib`, 20 | `${moduleName}/dist` 21 | ); 22 | 23 | pkg.jest.collectCoverageFrom.push(`${moduleName}/lib/**/*.{ts,tsx,js,jsx}`); 24 | 25 | await writeJson(pkgDir("package.json"), pkg, { 26 | spaces: " ", 27 | }); 28 | }; 29 | 30 | const updateTsconfig = async function (moduleName, pkg) { 31 | const tsConfig = require("../tsconfig"); 32 | const pathAlias = `${pkg.name}/${moduleName}`; 33 | 34 | if (tsConfig.compilerOptions.paths[pathAlias]) { 35 | return; 36 | } 37 | 38 | tsConfig.compilerOptions.paths[pathAlias] = [`../${moduleName}/lib/main.ts`]; 39 | const tsConfigPath = resolve(pkgDir("tsconfig.json")); 40 | await writeJson(tsConfigPath, tsConfig, { 41 | spaces: " ", 42 | }); 43 | }; 44 | 45 | const createModulePkg = async function (moduleDir, moduleName, pkg) { 46 | const modulePkg = resolve(moduleDir, "package.json"); 47 | await writeJson( 48 | modulePkg, 49 | { 50 | name: `${pkg.name}-${moduleName}`, 51 | amdName: `${pkg.name}${upperFirst(moduleName)}`, 52 | source: "lib/main.ts", 53 | main: "dist/main.js", 54 | module: "dist/main.m.js", 55 | "umd:main": "dist/main.umd.js", 56 | esmodule: "dist/main.modern.js", 57 | types: "dist/main.d.ts", 58 | }, 59 | { 60 | spaces: " ", 61 | } 62 | ); 63 | }; 64 | 65 | const createModuleLib = async function (moduleDir) { 66 | const moduleLibDir = resolve(moduleDir, "lib"); 67 | await mkDir(moduleLibDir); 68 | await writeFile( 69 | resolve(moduleLibDir, "main.ts"), 70 | 'export const hello = "world";' + "\n" 71 | ); 72 | }; 73 | 74 | const createBuildScript = async function (moduleName) { 75 | const buildScript = `#!/usr/bin/env node 76 | 77 | const { spawn, args, buildArgs, rmDir, pkgDir } = require("./lib"); 78 | 79 | (async () => { 80 | await rmDir(pkgDir("${moduleName}", "dist"), { recursive: true }); 81 | spawn("npx", ["microbundle", "--cwd", "${moduleName}", "--raw", ...buildArgs(), ...args]); 82 | })(); 83 | `; 84 | 85 | const scriptPath = scriptsDir(`build-${moduleName}.js`); 86 | await writeFile(scriptPath, buildScript); 87 | await chmod(scriptPath, "755"); 88 | }; 89 | 90 | (async () => { 91 | try { 92 | const moduleName = (args[0] && args[0].trim()) || undefined; 93 | 94 | if (!moduleName) { 95 | console.error("Module name missing in arguments."); 96 | return; 97 | } 98 | 99 | const moduleDir = pkgDir(moduleName); 100 | 101 | if (await exists(moduleDir)) { 102 | console.error(`"${moduleDir}" already exists.`); 103 | return; 104 | } 105 | 106 | await mkDir(moduleDir); 107 | 108 | const pkg = require("./../package"); 109 | 110 | await updatePkg(moduleName, pkg); 111 | await updateTsconfig(moduleName, pkg); 112 | await createModulePkg(moduleDir, moduleName, pkg); 113 | await createModuleLib(moduleDir); 114 | await createBuildScript(moduleName); 115 | } catch (err) { 116 | console.error(err); 117 | } 118 | })(); 119 | -------------------------------------------------------------------------------- /scripts/reformat.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { spawn, args } = require("./lib"); 4 | 5 | spawn("npx", ["prettier", "--write", ...args, "."]); 6 | -------------------------------------------------------------------------------- /scripts/site-subtree.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { spawn } = require("./lib"); 4 | 5 | // https://gist.github.com/tduarte/eac064b4778711b116bb827f8c9bef7b 6 | 7 | spawn("git", ["checkout", "master"]); 8 | spawn("git", ["subtree", "split", "--prefix", "site", "-b", "gh-pages"]); 9 | spawn("git", ["push", "-f", "origin", "gh-pages:gh-pages"]); 10 | spawn("git", ["branch", "-D", "gh-pages"]); 11 | -------------------------------------------------------------------------------- /scripts/site.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { spawn, copy, pkgDir } = require("./lib"); 4 | 5 | (async () => { 6 | spawn("npx", ["mallery"]); 7 | await copy(pkgDir("assets"), pkgDir("site/assets")); 8 | 9 | console.log( 10 | "Manual docs action: Commit and run: `node ./scripts/site-subtree.js`" 11 | ); 12 | })(); 13 | -------------------------------------------------------------------------------- /scripts/start.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { spawn } = require("./lib"); 4 | 5 | spawn("npx", ["ts-node", "lib/main.js"]); 6 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { spawn, args } = require("./lib"); 4 | 5 | spawn("npx", ["jest", "spec", "--notify", ...args]); 6 | -------------------------------------------------------------------------------- /site/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malleryjs/mallery/27aa6316342949ea0c3f0ae9024367d01816723d/site/.gitkeep -------------------------------------------------------------------------------- /site/assets/badge.coverage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | coverage 14 | coverage 15 | 100% 16 | 100% 17 | 18 | 19 | -------------------------------------------------------------------------------- /site/assets/badge.license.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | license 14 | license 15 | MIT 16 | MIT 17 | 18 | 19 | -------------------------------------------------------------------------------- /site/assets/badge.npm.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | npm 14 | npm 15 | 1.0.0-alpha.7 16 | 1.0.0-alpha.7 17 | 18 | 19 | -------------------------------------------------------------------------------- /site/assets/badge.style.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | code style 14 | code style 15 | prettier 16 | prettier 17 | 18 | 19 | -------------------------------------------------------------------------------- /site/examples/more-tests/plain---text-file.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 1.0.0-alpha.4 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

    17 |
    18 | <h1>This should be very plain :)</h1> 19 | 20 |
    21 |
    22 | 23 | 812 | 813 | 814 | -------------------------------------------------------------------------------- /site/examples/subfolder/more-infos.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 1.0.0-alpha.4 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
    17 |
    18 |

    I am a file in a 📁 😎

    19 | 20 |
    21 |
    22 | 23 | 812 | 813 | 814 | -------------------------------------------------------------------------------- /site/examples/subfolder2/subsub/furtherincluded.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 1.0.0-alpha.4 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
    17 |
    18 |

    Included too :)

    19 | 20 |
    21 |
    22 | 23 | 812 | 813 | 814 | -------------------------------------------------------------------------------- /site/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malleryjs/mallery/27aa6316342949ea0c3f0ae9024367d01816723d/site/favicon.ico -------------------------------------------------------------------------------- /site/images/narrative-794978_1920.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malleryjs/mallery/27aa6316342949ea0c3f0ae9024367d01816723d/site/images/narrative-794978_1920.jpg -------------------------------------------------------------------------------- /site/raw/CHANGELOG.html: -------------------------------------------------------------------------------- 1 |

    Changelog

    2 |

    All notable changes to this project will be documented in this file. See standard-version for commit guidelines.

    3 |

    1.0.0-alpha.7 (2020-06-06)

    4 |

    Bug Fixes

    5 |
      6 |
    • add hash bang to main entry (2521697)
    • 7 |
    8 |

    1.0.0-alpha.6 (2020-06-06)

    9 |

    1.0.0-alpha.5 (2020-06-06)

    10 |

    Bug Fixes

    11 |
      12 |
    • reduce transparency of navigation item bottom border (10b8e2c)
    • 13 |
    14 |

    v1.0.0-alpha.4

    15 |
      16 |
    • BUGFIX: Escape element ids with leading numbers when querying in the dom
    • 17 |
    18 |

    v1.0.0-alpha.0

    19 |
      20 |
    • Rewrote most of the code to Typescript
    • 21 |
    • Migrated from Redom to Preact
    • 22 |
    • Upgraded alo to 4.0
    • 23 |
    • Upgraded bootstrap to 4.4
    • 24 |
    • Switched from handlebars to ejs
    • 25 |
    • Migrated to witney project template
    • 26 |
    27 |

    v0.0.3

    28 |
      29 |
    • #5: Fixed: Scroll top wasn't called, when the toc item changed while no chapter was selected
    • 30 |
    31 |

    v0.0.2

    32 |
      33 |
    • #1: Improved scrolling with mobile safari
    • 34 |
    • Added modernizr
    • 35 |
    36 |

    v0.0.1

    37 |
      38 |
    • Initial release. Everything is new and shiny!
    • 39 |
    40 | -------------------------------------------------------------------------------- /site/raw/CONTRIBUTING.html: -------------------------------------------------------------------------------- 1 |

    Contributing

    2 |

    Building

    3 |

    This projects includes all its development scripts in the script folder.

    4 |

    Mostly used scripts:

    5 |
      6 |
    • Build Code: ./scripts/build.js
    • 7 |
    • Reformat Code: ./scripts/reformat.js
    • 8 |
    • Run tests: ./scripts/test.js
    • 9 |
    • Build docs html: ./scripts/docs.js
    • 10 |
    11 |

    Releasing

    12 |

    Before releasing make sure that all changes for the release are committed and in the proper branch merged.

    13 |

    To start the release workflow, run: ./scripts/before-publish.js 14 | This workflow will run through:

    15 |
      16 |
    • Reformat
    • 17 |
    • Build
    • 18 |
    • Test & Coverage
    • 19 |
    20 |

    Afterwards you are notified that you should run: npx standard-version -a 21 | This increases the version in package.json, updates README.md CHANGELOG.md and docs and adds a new git tag with the proper version.

    22 |

    Then push the tag (something like git push --tags) and if needed publish to npm with: npm publish

    23 | -------------------------------------------------------------------------------- /site/raw/examples/index9.html: -------------------------------------------------------------------------------- 1 |

    Welcome to the cool place

    2 |

    This is huge :)

    3 |

    1: Id starting with a number

    4 |

    Commodo elit mollit incididunt veniam cillum laboris. Minim duis elit veniam esse tempor ea id fugiat consequat elit. Tempor voluptate aute dolor sunt ea eiusmod irure minim velit anim minim irure culpa. Proident duis laboris et veniam magna mollit laboris culpa eiusmod. Incididunt ea in pariatur laborum laborum amet elit incididunt voluptate cupidatat officia in eu dolor. Consequat velit dolor voluptate fugiat voluptate reprehenderit minim ut.

    5 |

    Duis proident exercitation aute eiusmod dolore veniam incididunt fugiat enim aute laborum dolore proident. Esse reprehenderit exercitation sit consequat minim ea exercitation ea reprehenderit esse velit pariatur consectetur quis. Laborum veniam cillum sunt adipisicing anim do ea anim nulla pariatur dolore. Adipisicing irure proident enim irure incididunt laboris enim cillum enim voluptate veniam. Elit pariatur sunt id ex laboris anim irure ea laborum. Consectetur non incididunt aliquip nulla mollit.

    6 |

    Dolore consequat non incididunt minim dolor in excepteur velit anim voluptate. Proident ullamco quis amet in Lorem aliquip. Ut occaecat aliquip do reprehenderit pariatur elit sunt tempor nulla excepteur eiusmod nostrud cillum reprehenderit. Consequat duis amet consectetur eu.

    7 |

    2: Second id starting with a number

    8 | -------------------------------------------------------------------------------- /site/raw/examples/more-tests/plain---text-file.html: -------------------------------------------------------------------------------- 1 | <h1>This should be very plain :)</h1> 2 | -------------------------------------------------------------------------------- /site/raw/examples/readme2.html: -------------------------------------------------------------------------------- 1 |

    README

    2 |

    A table

    3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
    TablesAreCool
    col 3 isright-aligned$1600
    col 2 iscentered$12
    zebra stripesare neat$1
    29 |

    A linebreak

    30 | 31 | 32 | A external link 33 | 34 |
    35 | -------------------------------------------------------------------------------- /site/raw/examples/subfolder/more-infos.html: -------------------------------------------------------------------------------- 1 |

    I am a file in a 📁 😎

    2 | -------------------------------------------------------------------------------- /site/raw/examples/subfolder2/autoincluded.html: -------------------------------------------------------------------------------- 1 |

    Its me and i am autoincluded

    2 | 3 |

    A crazy world

    4 |

    5 | It is! 6 |

    7 |
    8 |
    9 |
    10 |
    11 |
    12 |
    13 |
    14 |
    15 |
    16 |
    17 |
    18 |
    19 |
    20 |
    21 |
    22 |
    23 |
    24 |
    25 |
    26 |
    27 |
    28 |
    29 |
    30 |
    31 |
    32 |
    33 |
    34 |
    35 |

    Really!

    36 | -------------------------------------------------------------------------------- /site/raw/examples/subfolder2/subsub/furtherincluded.html: -------------------------------------------------------------------------------- 1 |

    Included too :)

    2 | -------------------------------------------------------------------------------- /site/raw/examples/test.html: -------------------------------------------------------------------------------- 1 |

    Hello world

    2 |

    Lovely pixabay image

    3 |

    Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

    4 |

    Lets get started

    5 |
    console.log('test');
     6 | var test = 'test';
    7 |

    Sweet smiles

    8 |

    Just some funny 🐥 😄!

    9 | 10 |

    I am a link

    11 |

    A lot of text

    12 |

    Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

    13 |

    Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.

    14 |

    Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.

    15 |

    Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.

    16 |

    Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis.

    17 |

    At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.

    18 |

    Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.

    19 |

    Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.

    20 |

    Should scroll down

    21 |

    Blub

    22 |

    same level

    23 |

    very high heading

    24 |

    Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus.

    25 |

    same

    26 |
    deeply nested
    27 |

    same again

    28 |

    Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

    29 |

    here i come

    30 | -------------------------------------------------------------------------------- /site/raw/index.html: -------------------------------------------------------------------------------- 1 |

    🧢 Mallery

    2 |

    3 | 4 |

    5 |
    6 |

    Static documentation site generator

    7 |

    This is a free and opensource static website generator for 8 | project documentations. Mallery automatically creates a static documentation 9 | website from the contents of your choosen folder. The output will look 10 | similar to this website.

    11 |

    Features

    12 |
      13 |
    • A slim and general purpose theme
    • 14 |
    • Flexible and responsive navigation
    • 15 |
    • History control (last visited pages)
    • 16 |
    • Previous & Next page buttons
    • 17 |
    • Several options for individualization
    • 18 |
    • The exported HTML of markdown files is extended with bootstrap annotation
    • 19 |
    20 |

    Ideas / Future plans

    21 |
      22 |
    • Just work on the website and test Mallery
    • 23 |
    • Improving error handling
    • 24 |
    • Loadbar
    • 25 |
    • Non-webserver handling (open the html files locally without server)
    • 26 |
    • Enable minification of the js and css files (they are just called min :P)
    • 27 |
    • Serve mode: Add a CLI option which watches the source folder
    • 28 |
    • Add a hide navigation button for large desktops
    • 29 |
    • Search through the navigation 30 | (probably with a custom search index via configuration file)
    • 31 |
    • Numbered headings
    • 32 |
    33 |

    The name

    34 |

    The name was choosen randomly when I was reading through Wikipedia.

    35 |

    Creator

    36 |

    Creator of this tool is Katja Lutz

    37 | -------------------------------------------------------------------------------- /site/raw/installation.html: -------------------------------------------------------------------------------- 1 |

    Installation

    2 |

    Just type in the following command:

    3 |
    npm install -g mallery
    4 |

    Requirements

    5 |
      6 |
    • Node & NPM
        7 |
      • Its tested with Node v10.16.0
      • 8 |
    • 9 |
    10 | -------------------------------------------------------------------------------- /site/raw/usage.html: -------------------------------------------------------------------------------- 1 |

    Usage

    2 |

    CLI

    3 |

    Most of the config file options can also be used directly from 4 | the CLI. If paths are provided via CLI argument, they will be relative to the 5 | current working directory!

    6 |
    mallery --title "Iron Man Suit" --colors-accent orange --brandIcon false
      7 |   --footer-html "&copy; &copy; &copy; Copypasta" --paths-output="custom/output"
    8 |
    9 |

    Notice how recursive options like colors-accent or footer-html can be used!

    10 |
    11 |

    Additional options

    12 |

    First argument

    13 |

    The first CLI argument can be a path to the config file:

    14 |
    mallery custom/path/to/config.js
    15 |

    Configuration file

    16 |

    Mallery will search for a mallery.config.js file in the current working folder. 17 | A custom path to this file can be provided via CLI!

    18 |

    Options

    19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |
    NameDescriptionDefault
    brandIcon
    Copy the Mallory favicon to the root of the websitetrue
    title
    A base title (postfixed to all page titles)
    colors
    Multible color settings{...}
      accent
    Color of links in content, active menu items and mobile hamburger button#00BF63
    paths
    Several path options (paths relative to config file
    Paths in config file are relative to the file
    Path arguments via CLI are relative to current working dir
    {...}
      src
    Path to the folder with source files (markdown etc)docs
      output
    Destination path of the compiled websitesite
      public
    Folder which should be used (copied) as the root of the websitedocs/public
    includes
    Object - file content type controls{...}
      plaintext
    Files with these associations will be interpreted as plaintext[ .txt ]
      html
    Files with these associations will be interpreted as markdown[ .html ]
      markdown
    Files with these associations will be interpreted as plaintext[ .md ]
    toc
    Table of contents (automatically generated if not provided)
    Have a look at the example!
    [...]
    95 |

    Example

    96 |
    module.exports = {
     97 |   brandIcon: false,
     98 |   title: 'Iron Man Suit',
     99 |   paths: {
    100 |     output: 'customFolder'
    101 |   },
    102 |   colors: {
    103 |     accent: 'red'
    104 |   },
    105 |   footer: {
    106 |     html: '"Copyright &copy; 2017"'
    107 |   },
    108 |   toc: [
    109 |     // All files of this folder are automatically included
    110 |     { path: 'a_folder', title: 'A folder with custom title' },
    111 |     // Title will be "A file"
    112 |     { path: 'a_file.md' }
    113 |     { path: 'a_second_folder', children: [
    114 |       // Path of parent is automatically prepended!
    115 |       { path: 'file_in_second_folder.md' },
    116 |       { path: 'file2_in_second_folder.md' },
    117 |       // A child item with more own child items (... is just a placeholder)
    118 |       { path: 'folder_in_second_folder', children: [ '...' ] },
    119 |     ]},
    120 |     // Link item
    121 |     { href: 'http://wikipedia.org', title: 'Just a link' }
    122 |   ]
    123 | };
    124 |

    Tips

    125 |

    Folder as root of another git branch

    126 |

    Github Pages can host the ouput of Mallery!

    127 |

    Use the following command after updating the site:

    128 |
    git subtree push --prefix site origin gh-pages
    129 |

    This command will push the changes from the folder site to the 130 | branch gh-pages.

    131 |

    Git updates were rejected

    132 |
    git push origin `git subtree split --prefix site gh-pages`:gh-pages --force
    133 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "jsxFactory": "h", 5 | "noEmit": true, 6 | "allowJs": true, 7 | "baseUrl": "./node_modules", 8 | "esModuleInterop": true, 9 | "target": "es5", 10 | "paths": { 11 | "mallery": ["../lib/main.ts"], 12 | "mallery/front": ["../front/lib/main.ts"] 13 | } 14 | } 15 | } 16 | --------------------------------------------------------------------------------