diff --git a/.github/workflows/antmarky.yml b/.github/workflows/antmarky.yml index 7340f1d..f7e888e 100644 --- a/.github/workflows/antmarky.yml +++ b/.github/workflows/antmarky.yml @@ -121,11 +121,3 @@ jobs: push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.docker_meta.outputs.tags }} labels: ${{ steps.docker_meta.outputs.labels }} - - - name: 🐋 📝 DockerHub description - uses: peter-evans/dockerhub-description@v3 - with: - username: ${{ secrets.DOCKER_HUB_USER }} - password: ${{ secrets.DOCKER_HUB_ALT_TOKEN }} - repository: bandantonio/antmarky - short-description: ${{ github.event.repository.description }} diff --git a/Dockerfile b/Dockerfile index dbfc6ad..0e84d2f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,10 @@ FROM node:17.4.0-alpine3.14 as build -LABEL VERSION="1.0.0" +LABEL VERSION="1.1.0" LABEL MAINTAINER="Anton Zolotukhin" -LABEL NAME="Antmarky is a static-site generator for Markdown" +LABEL NAME="Antmarky is a static-site generator for Asciidoctor" WORKDIR /antmarky -COPY package.json server.js README.md ./ +COPY package.json server.js README.adoc ./ RUN npm i COPY . . diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..ffe1252 --- /dev/null +++ b/README.adoc @@ -0,0 +1,64 @@ +== Antmarky + +Antmarky is a static-site generator for https://docs.asciidoctor.org/asciidoc/latest[`Asciidoctor`^] based on Node.js and EJS. + +https://github.com/bandantonio/antmarky/actions/workflows/antmarky.yml[image:https://github.com/bandantonio/antmarky/actions/workflows/antmarky.yml/badge.svg?branch=main[antmarky]^] +https://coveralls.io/github/bandantonio/antmarky?branch=main[image:https://coveralls.io/repos/github/bandantonio/antmarky/badge.svg?branch=main[Coverage +Status]^] +image:https://img.shields.io/docker/pulls/bandantonio/antmarky[Docker +Pulls] +https://github.com/standard/semistandard[image:https://img.shields.io/badge/code%20style-semistandard-f7df1e.svg[js-semistandard-style]^] + +The main idea behind creating Antmarky was to have a generator with _zero configuration_ that can serve your Asciidoctor files +in the documentation directory. + +Consider Antmarky as a lightweight alternative to https://docs.antora.org/antora/latest/[Antora^]. + +Currently, Antmarky flattens out the directory structure and displays all the files at the root level under the +corresponding directory. + +=== Features + +* Zero configuration +* Fully responsive layout +* Fully static (doesn't require a web server to work) +* No language frameworks included +* Support of major Asciidoctor features: +** https://docs.asciidoctor.org/asciidoc/latest/directives/include/[Includes^] +** https://docs.asciidoctor.org/asciidoc/latest/directives/conditionals/[Conditionals^] +** https://docs.asciidoctor.org/asciidoc/latest/tables/build-a-basic-table/[Tables^] +** https://docs.asciidoctor.org/asciidoc/latest/blocks/admonitions/[Admonitions^] +* xref:features.adoc#fontawesome[FontAwesome] +* Copy code block + +=== Quickstart + +==== Prerequisites + +* https://docs.docker.com/get-docker/[Docker^] + +==== Serve content + +[source,sh] +---- +docker run --rm \ + --name antmarky-ssg \ + -v ${PWD}/docs:/antmarky/docs \ + -p 8000:8000 \ + bandantonio/antmarky +---- + +Local server will be launched at http://localhost:8000[^] + +==== Build content + +[source,sh] +---- +docker run --rm \ + --name antmarky-ssg \ + -v ${PWD}/docs:/antmarky/docs \ + -v ${PWD}/public:/antmarky/public \ + bandantonio/antmarky build +---- + +Website static files will be generated in the `public` directory. diff --git a/README.md b/README.md deleted file mode 100644 index af931e0..0000000 --- a/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# Antmarky - -Antmarky is a static-site generator for Markdown based on Node.js/EJS. - -[![antmarky](https://github.com/bandantonio/antmarky/actions/workflows/antmarky.yml/badge.svg?branch=main)](https://github.com/bandantonio/antmarky/actions/workflows/antmarky.yml) [![Coverage Status](https://coveralls.io/repos/github/bandantonio/antmarky/badge.svg?branch=main)](https://coveralls.io/github/bandantonio/antmarky?branch=main) ![Docker Pulls](https://img.shields.io/docker/pulls/bandantonio/antmarky) [![js-semistandard-style](https://img.shields.io/badge/code%20style-semistandard-f7df1e.svg)](https://github.com/standard/semistandard) - -The main idea behind creating Antmarky was to have a generator with *zero configuration* that can serve your Markdown files in the documentation directory. Currently, Antmarky flattens out the directory structure and displays all the files at the root level under the corresponding directory. - -## Features - -* Zero configuration -* Fully responsive layout -* Fully static (doesn't require a web server to work) -* No language frameworks included -* [Include remote Markdown files from GitHub and BitBucket][remote-md-files] -* [Markdown][markdown] flavor: `GitHub`. Supported syntax: - * Heading ids - * Emojis :tada: - * Images with inline dimensions attributes - * Reference links - * Strikethrough - * Tables -* [Admonitions][admonitions] -* [Syntax highlighting][syntax-highlight] -* [FontAwesome][fa] -* [Task lists][tasks-list] - -[remote-md-files]: features.md#remote-markdown-files -[markdown]: markdown.md -[admonitions]: features.md#admonitions -[syntax-highlight]: features.md#syntax-highlighting -[fa]: features.md#fontawesome -[tasks-list]: features.md#task-lists - -## Quickstart - -### Prerequisites - -* [Docker](https://docs.docker.com/get-docker/) - -### Serve content - -```sh -docker run --rm \ - --name antmarky-ssg \ - -v ${PWD}/docs:/antmarky/docs \ - -p 8000:8000 \ - bandantonio/antmarky -``` - -Local server will be launched at `http://localhost:8000`. - -### Build content - -```sh -docker run --rm \ - --name antmarky-ssg \ - -v ${PWD}/docs:/antmarky/docs \ - -v ${PWD}/public:/antmarky/public \ - bandantonio/antmarky build -``` - -Website static files will be generated in the `public` directory. diff --git a/docs/features.adoc b/docs/features.adoc new file mode 100644 index 0000000..1fea98f --- /dev/null +++ b/docs/features.adoc @@ -0,0 +1,58 @@ +== Features + +=== Admonitions + +Supported admonitions: `NOTE`, `TIP`, `IMPORTANT`, `CAUTION`, `WARNING`. + +[NOTE] +==== +[source, md] +---- +NOTE: Note admonition body +---- +==== + +[TIP] +==== +[source, md] +---- +TIP: Tip admonition body +---- +==== + +[IMPORTANT] +==== +[source, md] +---- +IMPORTANT: Important admonition body +---- +==== + +[CAUTION] +==== +[source, md] +---- +CAUTION: Caution admonition body +---- +==== + +[WARNING] +==== +[source, md] +---- +WARNING: Warning admonition body +---- +==== + +=== FontAwesome + +Antmarky supports https://fontawesome.com/v4/cheatsheet/[FontAwesome v4.7^] for compatibility reasons. + +To use icons in the document, use the following syntax: + +[source, plaintext] +---- +icon:bicycle[] is good for your icon:heart[] +---- + +This will be rendered as: icon:bicycle[] is good for your icon:heart[] diff --git a/docs/features.md b/docs/features.md deleted file mode 100644 index 10be660..0000000 --- a/docs/features.md +++ /dev/null @@ -1,160 +0,0 @@ -# Features - -## Remote markdown files - -You can include remote Markdown files in **raw** format from **GitHub** and **BitBucket** public repositories using `!!+` directive: - -```md -!!+ github.com/link/to/your/raw/markdown/file.md -!!+ bitbucket.org/link/to/your/raw/markdown/file.md -``` - -::: tip "What is raw format" -**GitHub raw format** - -``` -https://github.com///raw//filename.md -``` - -**BitBucket raw format** - -``` -https://bitbucket.org///raw//filename.md -``` -::: - -## Admonitions - -Supported admonitions: `info`, `warning`, `danger`, `tip`, `example`, `quote`. - -::: info "Test info" - -```md -::: info "Test info" -Info admonition body -::: -``` - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean interdum ullamcorper nibh sed bibendum. Fusce consectetur, velit eu tempus consequat, enim libero consequat sem, `vitae interdum` velit urna eget nunc. - -`test code line` - -```js -let greeting = 'Hello World!'; - -console.log(greeting); -``` -::: - -::: warning "Test warning" - -```md -::: warning "Test warning" -Warning admonition body -::: -``` - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean interdum ullamcorper nibh sed bibendum. Fusce consectetur, velit eu tempus consequat, enim libero consequat sem, `vitae interdum` velit urna eget nunc. - -`test code line` - -```js -let greeting = 'Hello World!'; - -console.log(greeting); -``` -::: - -::: danger "Test danger" - -```md -::: danger "Test danger" -Danger admonition body -::: -``` - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean interdum ullamcorper nibh sed bibendum. Fusce consectetur, velit eu tempus consequat, enim libero consequat sem, `vitae interdum` velit urna eget nunc. - -`test code line` - -```js -let greeting = 'Hello World!'; - -console.log(greeting); -``` -::: - -::: tip "Test tip" - -```md -::: tip "Test tip" -Tip admonition body -::: -``` - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean interdum ullamcorper nibh sed bibendum. Fusce consectetur, velit eu tempus consequat, enim libero consequat sem, `vitae interdum` velit urna eget nunc. - -`test code line` - -```js -let greeting = 'Hello World!'; - -console.log(greeting); -``` -::: - -::: example "Test example" - -```md -::: example "Test example" -Example admonition body -::: -``` - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean interdum ullamcorper nibh sed bibendum. Fusce consectetur, velit eu tempus consequat, enim libero consequat sem, `vitae interdum` velit urna eget nunc. - -`test code line` - -```js -let greeting = 'Hello World!'; - -console.log(greeting); -``` -::: - -::: quote "Test quote" - -```md -::: quote "Test quote" -Quote admonition body -::: -``` - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean interdum ullamcorper nibh sed bibendum. Fusce consectetur, velit eu tempus consequat, enim libero consequat sem, `vitae interdum` velit urna eget nunc. - -`test code line` - -```js -let greeting = 'Hello World!'; - -console.log(greeting); -``` -::: - -## Syntax highlighting - -Antmarky uses [Highlight.js](https://highlightjs.org) for syntax highlighting. The bundle includes a set of common languages. Other languages can be added on request. - -## FontAwesome - -Antmarky supports the latest version of [FontAwesome icon set](https://fontawesome.com/v6.0/icons?m=free). - -## Task lists - -```md - - [x] checked list item - - [ ] unchecked list item -``` - - - [x] checked list item - - [ ] unchecked list item \ No newline at end of file diff --git a/docs/markdown.md b/docs/markdown.md deleted file mode 100644 index e556442..0000000 --- a/docs/markdown.md +++ /dev/null @@ -1,70 +0,0 @@ -# Markdown support - -Antmarky uses `GitHub` flavor for the Markdown parser. The parser supports the following syntax: - -* [Heading ids](#heading-ids) -* [Emojis](#emojis) :tada: -* [Images with inline dimensions attributes](#image-dimensions) -* [Reference links](#reference-links) -* [Strikethrough](#strikethrough) -* [Tables](#tables) - -## Heading ids - -The following heading: - -```md -# Heading ids -``` - -will automatically be transformed into the following HTML: - -```html -Heading ids -``` - -## Emojis - -```md -Learning JavaScript is :no_entry_sign: a :rocket: :man_scientist: -``` - -Learning JavaScript is :no_entry_sign: a :rocket: :man_scientist: - -## Image dimensions - -```md -![Alt text](url/to/image =250x250 "Optional title") -``` - -## Reference links - -```md -[Link to google][refer] - -[refer]: https://google.com -``` - -## Strikethrough - -```md -a ~~strikethrough~~ element -``` - -a ~~strikethrough~~ element - -## Tables - -```md -| Tables | Are | Cool | -|-----------|:--------------:|------:| -| **row 1** | center-aligned | $1000 | -| row 2 | *centered* | $10 | -| row 3 | ~~centered~~ | $1 | -``` - -| Tables | Are | Cool | -|-----------|:--------------:|------:| -| **row 1** | center-aligned | $1000 | -| row 2 | *centered* | $10 | -| row 3 | ~~centered~~ | $1 | \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 50fbd91..f71cdbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { "name": "antmarky", - "version": "0.10.0", + "version": "1.0.0-alpha.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "antmarky", - "version": "0.10.0", + "version": "1.0.0-alpha.1", "license": "ISC", "devDependencies": { "@types/jest": "^28.1.4", + "asciidoctor": "^2.2.6", "auto-changelog": "^2.4.0", "axios": "^0.27.2", "ejs": "^3.1.6", @@ -18,8 +19,7 @@ "jest": "^28.1.2", "joi": "^17.6.0", "mock-fs": "^5.1.2", - "semistandard": "^16.0.1", - "showdown": "^2.1.0" + "semistandard": "^16.0.1" } }, "node_modules/@ampproject/remapping": { @@ -35,6 +35,68 @@ "node": ">=6.0.0" } }, + "node_modules/@asciidoctor/cli": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@asciidoctor/cli/-/cli-3.5.0.tgz", + "integrity": "sha512-/VMHXcZBnZ9vgWfmqk9Hu0x0gMjPLup0YGq/xA8qCQuk11kUIZNMVQwgSsIUzOEwJqIUD7CgncJdtfwv1Ndxuw==", + "dev": true, + "dependencies": { + "yargs": "16.2.0" + }, + "bin": { + "asciidoctor": "bin/asciidoctor", + "asciidoctorjs": "bin/asciidoctor" + }, + "engines": { + "node": ">=8.11", + "npm": ">=5.0.0" + }, + "peerDependencies": { + "@asciidoctor/core": "^2.0.0-rc.1" + } + }, + "node_modules/@asciidoctor/cli/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@asciidoctor/cli/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@asciidoctor/core": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@asciidoctor/core/-/core-2.2.6.tgz", + "integrity": "sha512-TmB2K5UfpDpSbCNBBntXzKHcAk2EA3/P68jmWvmJvglVUdkO9V6kTAuXVe12+h6C4GK0ndwuCrHHtEVcL5t6pQ==", + "dev": true, + "dependencies": { + "asciidoctor-opal-runtime": "0.3.3", + "unxhr": "1.0.1" + }, + "engines": { + "node": ">=8.11", + "npm": ">=5.0.0", + "yarn": ">=1.1.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -1467,6 +1529,55 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asciidoctor": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/asciidoctor/-/asciidoctor-2.2.6.tgz", + "integrity": "sha512-EXG3+F2pO21B+COfQmV/WgEgGiy7nG/mJiS/o5DXpaT2q82FRZWPVkbMZrpDvpu4pjXe5c754RbZR9Vz0L0Vtw==", + "dev": true, + "dependencies": { + "@asciidoctor/cli": "3.5.0", + "@asciidoctor/core": "2.2.6" + }, + "bin": { + "asciidoctor": "bin/asciidoctor", + "asciidoctorjs": "bin/asciidoctor" + }, + "engines": { + "node": ">=8.11", + "npm": ">=5.0.0", + "yarn": ">=1.1.0" + } + }, + "node_modules/asciidoctor-opal-runtime": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/asciidoctor-opal-runtime/-/asciidoctor-opal-runtime-0.3.3.tgz", + "integrity": "sha512-/CEVNiOia8E5BMO9FLooo+Kv18K4+4JBFRJp8vUy/N5dMRAg+fRNV4HA+o6aoSC79jVU/aT5XvUpxSxSsTS8FQ==", + "dev": true, + "dependencies": { + "glob": "7.1.3", + "unxhr": "1.0.1" + }, + "engines": { + "node": ">=8.11" + } + }, + "node_modules/asciidoctor-opal-runtime/node_modules/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -5654,31 +5765,6 @@ "node": ">=8" } }, - "node_modules/showdown": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz", - "integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==", - "dev": true, - "dependencies": { - "commander": "^9.0.0" - }, - "bin": { - "showdown": "bin/showdown.js" - }, - "funding": { - "type": "individual", - "url": "https://www.paypal.me/tiviesantos" - } - }, - "node_modules/showdown/node_modules/commander": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.3.0.tgz", - "integrity": "sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw==", - "dev": true, - "engines": { - "node": "^12.20.0 || >=14" - } - }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -6215,6 +6301,15 @@ "node": ">= 0.8" } }, + "node_modules/unxhr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unxhr/-/unxhr-1.0.1.tgz", + "integrity": "sha512-MAhukhVHyaLGDjyDYhy8gVjWJyhTECCdNsLwlMoGFoNJ3o79fpQhtQuzmAE4IxCMDwraF4cW8ZjpAV0m9CRQbg==", + "dev": true, + "engines": { + "node": ">=8.11" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz", @@ -6458,6 +6553,48 @@ "@jridgewell/trace-mapping": "^0.3.9" } }, + "@asciidoctor/cli": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@asciidoctor/cli/-/cli-3.5.0.tgz", + "integrity": "sha512-/VMHXcZBnZ9vgWfmqk9Hu0x0gMjPLup0YGq/xA8qCQuk11kUIZNMVQwgSsIUzOEwJqIUD7CgncJdtfwv1Ndxuw==", + "dev": true, + "requires": { + "yargs": "16.2.0" + }, + "dependencies": { + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true + } + } + }, + "@asciidoctor/core": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@asciidoctor/core/-/core-2.2.6.tgz", + "integrity": "sha512-TmB2K5UfpDpSbCNBBntXzKHcAk2EA3/P68jmWvmJvglVUdkO9V6kTAuXVe12+h6C4GK0ndwuCrHHtEVcL5t6pQ==", + "dev": true, + "requires": { + "asciidoctor-opal-runtime": "0.3.3", + "unxhr": "1.0.1" + } + }, "@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -7585,6 +7722,42 @@ "es-shim-unscopables": "^1.0.0" } }, + "asciidoctor": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/asciidoctor/-/asciidoctor-2.2.6.tgz", + "integrity": "sha512-EXG3+F2pO21B+COfQmV/WgEgGiy7nG/mJiS/o5DXpaT2q82FRZWPVkbMZrpDvpu4pjXe5c754RbZR9Vz0L0Vtw==", + "dev": true, + "requires": { + "@asciidoctor/cli": "3.5.0", + "@asciidoctor/core": "2.2.6" + } + }, + "asciidoctor-opal-runtime": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/asciidoctor-opal-runtime/-/asciidoctor-opal-runtime-0.3.3.tgz", + "integrity": "sha512-/CEVNiOia8E5BMO9FLooo+Kv18K4+4JBFRJp8vUy/N5dMRAg+fRNV4HA+o6aoSC79jVU/aT5XvUpxSxSsTS8FQ==", + "dev": true, + "requires": { + "glob": "7.1.3", + "unxhr": "1.0.1" + }, + "dependencies": { + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -10676,23 +10849,6 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "showdown": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz", - "integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==", - "dev": true, - "requires": { - "commander": "^9.0.0" - }, - "dependencies": { - "commander": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.3.0.tgz", - "integrity": "sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw==", - "dev": true - } - } - }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -11091,6 +11247,12 @@ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", "dev": true }, + "unxhr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unxhr/-/unxhr-1.0.1.tgz", + "integrity": "sha512-MAhukhVHyaLGDjyDYhy8gVjWJyhTECCdNsLwlMoGFoNJ3o79fpQhtQuzmAE4IxCMDwraF4cW8ZjpAV0m9CRQbg==", + "dev": true + }, "update-browserslist-db": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz", diff --git a/package.json b/package.json index 9a07bca..9ec8669 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "antmarky", - "version": "0.10.0", - "description": "Antmarky is a static-site generator for Markdown based on Node.js/EJS", + "version": "1.0.0-beta.1", + "description": "Antmarky is a static-site generator for Asciidoctor based on Node.js and EJS", "main": "server.js", "scripts": { "build": "node src/commands/build.js", @@ -34,15 +34,14 @@ "license": "ISC", "devDependencies": { "@types/jest": "^28.1.4", + "asciidoctor": "^2.2.6", "auto-changelog": "^2.4.0", - "axios": "^0.27.2", "ejs": "^3.1.6", "express": "^4.17.1", "fs-extra": "^10.0.0", "jest": "^28.1.2", "joi": "^17.6.0", "mock-fs": "^5.1.2", - "semistandard": "^16.0.1", - "showdown": "^2.1.0" + "semistandard": "^16.0.1" } } diff --git a/server.js b/server.js index 2035a2e..ed73f24 100644 --- a/server.js +++ b/server.js @@ -4,7 +4,7 @@ const path = require('path'); const app = express(); const PORT = 8000; const { serveContent } = require('./src/commands/serve'); -const { md } = require('./src/common/md-parser'); +const { adoc, asciidoctorDefaultConfig } = require('./src/common/parsers'); const { errorPage } = require('./src/data/defaults'); app.use(serveContent); @@ -17,7 +17,7 @@ app.get('/', (req, res) => { const specificPageData = res.locals.files_data.find(page => page.name === 'README'); renderData.name = (specificPageData) ? specificPageData.name : '/'; renderData.title = (specificPageData) ? specificPageData.title : 'Home'; - renderData.content = (specificPageData) ? specificPageData.html : md.makeHtml(fs.readFileSync(path.resolve('README.md'), 'utf-8')); + renderData.content = (specificPageData) ? specificPageData.html : adoc.convert(fs.readFileSync(path.resolve('README.adoc'), 'utf-8'), asciidoctorDefaultConfig); renderData.pages = res.locals.all_pages.filter(page => page.name !== 'README'); res.render('index', renderData); }); diff --git a/src/assets/css/styles.css b/src/assets/css/styles.css index 0d83252..b62d2c8 100644 --- a/src/assets/css/styles.css +++ b/src/assets/css/styles.css @@ -1,510 +1,486 @@ -@font-face { - font-family: SegoeUI; - src: - local("Segoe UI"), - url(https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.woff2) format("woff2"), - url(https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.woff) format("woff"), - url(https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/normal/latest.ttf) format("truetype"); - font-weight: 400; -} - -@font-face { - font-family: SegoeUI; - src: - local("Segoe UI Semibold"), - url(https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.woff2) format("woff2"), - url(https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.woff) format("woff"), - url(https://c.s-microsoft.com/static/fonts/segoe-ui/west-european/semibold/latest.ttf) format("truetype"); - font-weight: 700; -} - -body { - font-family: 'SegoeUI', sans-serif; - color: #3b454e; - min-height: 100vh; - position: relative; - padding: 3em 0; -} - -.navbar-brand { - margin-left: 1.5em; -} - -.navbar-brand:hover { - color: #fad000; -} - -.list_title { - font-family: 'SegoeUI', sans-serif; - font-size: 12px; - font-weight: 700; - text-transform: uppercase; - margin-bottom: 1em; - margin-left: 1em; - color: #2d2b57; -} - -.list_heading { - font-size: 13px; - font-weight: 700; - margin: .8em 0 0 0; -} - -[aria-expanded="false"]::before { - display: inline-block; - width: 1em; - font-family: 'Font Awesome 6 Free'; - content: '\f105'; - font-size: 80%; -} - -[aria-expanded="true"]::before { - display: inline-block; - width: 1em; - font-family: 'Font Awesome 6 Free'; - content: '\f107'; - font-size: 80%; -} - -.list_files { - list-style: none; -} - -.list_files > li { - margin-left: -.5em; -} - -.sidebar, .toc { - position: fixed; - overflow: scroll; -} - -.powered-by a { - text-decoration: none; -} - -.powered-by a:hover { - color: #DB504A; - font-weight: 500; -} - -.toc { - right: 0; -} - -.list_item.ind-3 { - padding-left: 1em; -} - -.list_item.ind-4 { - padding-left: 2em; -} - -.list_item.ind-5 { - padding-left: 3em; -} - -.list_item.ind-6 { - padding-left: 4em; -} - -.sidebar_list, .toc_list { - font-size: 80%; - list-style: none; - max-height: 80vh; -} - -.sidebar_list a.active { - color: #DB504A; - font-weight: 700; - text-decoration: underline; -} - -.sidebar_list a:hover, .toc_list a:hover { - color: #DB504A; -} - -.sidebar_list a, .toc_list a { - color: #2d2b57; - text-decoration: none; -} - -.toc_list a.active { - color: #DB504A; - font-weight: 700; -} - -.sidebar_list { - margin: 0; - padding-left: .5em; -} - -/* Override default Bootstrap styling */ -.btn { - display: inherit; -} - -.nav-link { - display: inherit; - padding: inherit; -} - -.full_content { - margin-top: 2em; - margin-bottom: 2em; -} - -.content_area { - margin: 0 auto; -} - -.content_area a { - color: #DB504A; - text-decoration: underline; -} - -.content_area a:hover { - color: #DB504A; - text-decoration: none; -} - -.content_area a code { - color: #fad000; - text-decoration: underline; -} - -.content_area a:hover code { - color: #DB504A; - text-decoration: none; -} - -.navbar, pre, code { - background-color: #2d2b57; -} - -.navbar-brand { - color: #fad000; -} - -h1, h2, h3, h4, h5, h5, h6 { - font-weight: 700; - color: #3b454e; -} - -h1 { font-size: 2em; } -h2 { font-size: 1.6em; } -h3 { font-size: 1.4em; } -h4 { font-size: 1.2em; } -h5 { font-size: 1em; } -h6 { font-size: .8em; } - -a.anchor-link { - font-family: 'Font Awesome 6 Free'; - font-size: 70%; - opacity: 0; - display: inline-block; - padding-left: .5em; - cursor: pointer; - text-decoration: none; -} - -.h-anchor:hover .anchor-link { - opacity: .5; - transition:ease-in -} - -.h-anchor .anchor-link:hover { - opacity: 1; - } - -.content_area li, p { - font-size: 95%; -} - -strong { - color: #2d2b57; -} - -code { - padding: 1px 2px; -} - -code { - color: #fad000; -} - -pre { - border-radius: 5px; - position: relative; -} - -pre::before { - content: attr(data-language); - font-family: Arial, Helvetica, sans-serif; - font-weight: 700; - font-size: .7rem; - position: absolute; - float: right; - top: .3rem; - right: .5rem; - color: #f7df1e; -} - -pre:hover::before { - right: 1.5rem; -} - -.copy-block { - display: none; - position: absolute; - float: right; - right: .3em; - top: .1em; - color: #f7df1e; - cursor: pointer; -} - -.copy-block svg { - width: 1rem; - height: 1rem; -} - -pre:hover > .copy-block { - display: block; -} - -pre:not(:hover) > .copy-block { - display: none; -} - -li p { - margin: 0; -} - -blockquote { - border-left: #6c757d solid 7px; - margin-left: 1em; - padding: 10px; - color: #343a40; - background-color: #dee2e6; -} - -blockquote p { - margin: 0 5px; -} - -.callout { - padding: .5rem 1.25rem; - margin-top: 1.25rem; - margin-bottom: 1.25rem; - border-left: 1px solid #e9ecef; - border-left-width: .25rem; - border-radius:.25rem; - box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px; -} - -.callout-info { - border-left-color: rgb(51, 153, 255); - background-color: rgba(51, 153, 255, .1); -} - -.callout-warning { - border-left-color: rgb(249, 177, 21); - background-color: rgba(249, 177, 21, .1); -} - -.callout-danger { - border-left-color: rgb(222, 22, 22); - background-color: rgba(222, 22, 22, .1); -} - -.callout-tip { - border-left-color: rgb(14, 181, 70); - background-color: rgba(14, 181, 70, .1); -} - -.callout-example { - border-left-color: rgb(50, 31, 219); - background-color: rgba(50, 31, 219, .1); -} - -.callout-secondary, .callout-quote { - border-left-color: rgb(61, 61, 61); - background-color: rgba(61, 61, 61, .1); -} - -.callout-info .callout-title { - color: rgb(51, 153, 255); -} - -.callout-warning .callout-title { - color: rgb(249, 177, 21); -} - -.callout-danger .callout-title { - color: rgb(222, 22, 22); -} - -.callout-tip .callout-title { - color: rgb(14, 181, 70); -} - -.callout-example .callout-title { - color: rgb(50, 31, 219); -} - -.callout-quote .callout-title { - color: rgb(61, 61, 61); -} - -.callout-title { - font-size: 80%; -} - -.callout-title::before { - font-family: 'unicons'; - font-size: 20px; - vertical-align: middle; - padding: 0 .1em 0 .1em; -} - -.callout-info .callout-title::before { - content: '\e9ad'; -} - -.callout-warning .callout-title::before { - content: '\e9b1'; -} - -.callout-danger .callout-title::before { - content: '\eafb'; -} - -.callout-tip .callout-title::before { - content: '\e90b'; -} - -.callout-quote .callout-title::before { - content: '\e810'; -} - -.callout-example .callout-title::before { - content: '\e905'; -} - -.callout p { - margin:.5em; -} +@import url('https://fonts.googleapis.com/css2?family=Fira+Sans:ital,wght@0,400;0,700;1,400;1,700&family=Fira+Code:wght@400;700&display=swap'); +body{font-family:'Fira Sans',sans-serif;color:#3b454e;min-height:100vh;position:relative;padding:3em 0} +.navbar-brand{margin-left:1.5em} +.navbar-brand:hover{color:#fad000} +.list_title{font-size:12px;font-weight:bold; text-transform:uppercase;margin-bottom:1em;margin-left:1em;color:#2d2b57} +.list_heading{font-size:13px;font-weight:700;margin:.8em 0 0} +[aria-expanded="false"]::before{display:inline-block;width:1em;font-family:'FontAwesome';content:'\f105';font-size:80%} +[aria-expanded="true"]::before{display:inline-block;width:1em;font-family:'FontAwesome';content:'\f107';font-size:80%} +.list_files{list-style:none} +.list_files>li{margin-left:-.5em} +.sidebar,.toc{position:fixed;overflow:scroll} +.powered-by a{text-decoration:none} +.powered-by a:hover{color:#DB504A;font-weight:500} +.toc{right:0} +.list_item.ind-3{padding-left:1em} +.list_item.ind-4{padding-left:2em} +.list_item.ind-5{padding-left:3em} +.list_item.ind-6{padding-left:4em} +.sidebar_list,.toc_list{font-size:80%;list-style:none;max-height:80vh} +.sidebar_list a.active{color:#DB504A;font-weight:700;text-decoration:underline;} +.sidebar_list a:hover,.toc_list a:hover{color:#DB504A} +.sidebar_list a,.toc_list a{color:#2d2b57;text-decoration:none} +.toc_list a.active{color:#DB504A;font-weight:700} +.sidebar_list{margin:0;padding-left:.5em} +.btn{display:inherit} +.nav-link{display:inherit;padding:inherit} +.full_content{margin-top:2em;margin-bottom:2em} +.content_area{margin:0 auto} +.content_area a{color:#DB504A;text-decoration:underline} +.content_area a:hover{text-decoration:none} +.content_area a code, .content_area code a{color:#fad000;text-decoration:underline} +.content_area a:hover code{text-decoration:none} +.navbar{background-color:#2d2b57} +.navbar-brand{color:#fad000} +h1,h2,h3,h4,h5,h5,h6{font-weight:700;color:#3b454e} +h1{font-size:2em} +h2{font-size:1.6em} +h3{font-size:1.4em} +h4{font-size:1.2em} +h5{font-size:1em} +h6{font-size:.8em} +.content_area li,p{font-size:95%} +code{color:#e3dfff;background-color:#2d2b57;padding:.1em .2em;border-radius:.2em} +pre{border-radius:5px;position:relative;} +pre::before{content:attr(data-language);font-family:'Fira Code',Arial,Helvetica,sans-serif;font-weight:700;font-size:.7rem;position:absolute;float:right;top:.3rem;right:.5rem;color:#f7df1e!important} +pre:hover::before{right:5rem} +.highlight>code{display: block;} +.copy-block{display:none;position:absolute;float:right;right:.5em;top:.1em;color:#f7df1e;cursor:pointer} +.copy-block svg{width:1rem;height:1rem} +pre:hover>.copy-block{display:block} +pre:not(:hover)>.copy-block{display:none} +li p{margin:0} @media (max-width: 576px) { - .h-anchor { - scroll-margin-top: 60px; - } - #sidebarToggler { - text-align: center; - } - .navbar-toggler { - color: #fad000; - } - .navbar-toggler[aria-expanded="false"]::before { - font-family: 'Font Awesome 6 Free'; - content: '\f0c9'; - } - .navbar-toggler[aria-expanded="true"]::before { - font-family: 'Font Awesome 6 Free'; - content: '\f00d'; - } - #sidebarToggler .list_title { - color: #dee2e6; - margin-left: 0; - } - #sidebarToggler .sidebar_list { - padding: 0; - } - .sidebar_list a, .powered-by { - color: #fad000; - } - .sidebar { - display: none; - } - .list_heading { - margin: 0 auto; - color: #dee2e6; - } - .list_heading:hover { - color: #dee2e6; - } - .toc { - display: none; - } - .content_area { - width: 85%; - } - .powered-by { - display: none; - } +.h-anchor{scroll-margin-top:60px} +#sidebarToggler{text-align:center} +.navbar-toggler{color:#fad000} +.navbar-toggler[aria-expanded="false"]::before{font-family:'FontAwesome';content:'\f107'} +.navbar-toggler[aria-expanded="true"]::before{font-family:'FontAwesome';content:'\f105'} +#sidebarToggler .list_title{color:#dee2e6;margin-left:0} +#sidebarToggler .sidebar_list{padding:0} +.sidebar_list a,.powered-by{color:#fad000} +.sidebar{display:none} +.list_heading{margin:0 auto;color:#dee2e6} +.list_heading:hover{color:#dee2e6} +.toc{display:none} +.content_area{width:85%} +.powered-by{display:none} } @media (min-width: 992px) { - .h-anchor { - scroll-margin-top: 76px; - } - #sidebarToggler { - display: none !important; - } - .powered-by { - font-size: 70%; - margin-left: 1em; - position: fixed; - bottom: 1em; - } - .powered-by a { - color: #2d2b57; - } +#sidebarToggler{display:none!important} +.powered-by{font-size:70%;margin-left:1em;position:fixed;bottom:1em} +.powered-by a{color:#2d2b57} } @media (min-width: 768px) and (max-width: 992px) { - #sidebarToggler { - display: none !important; - } - .sidebar_list, .toc_list { - padding-left: 1em; - } - .content_area { - width: 65%; - } - .powered-by { - font-size: 60%; - margin-left: 1em; - position: fixed; - bottom: 1em; - } - .powered-by a { - color: #2d2b57; - } +#sidebarToggler{display:none!important} +.sidebar_list,.toc_list{padding-left:1em} +.content_area{width:65%} +.powered-by{font-size:60%;margin-left:1em;position:fixed;bottom:1em} +.powered-by a{color:#2d2b57} } @media (min-width: 576px) and (max-width: 768px) { - #sidebarToggler { - display: none !important; - } - .sidebar_list { - padding-left: 1em; - } - .content_area { - width: 80%; - margin: 0 2em 0 auto; - } - .toc { - display: none; - } - .powered-by { - font-size: 50%; - margin-left: 1em; - position: fixed; - bottom: 1em; - } - .powered-by a { - color: #2d2b57; - } -} +#sidebarToggler{display:none!important} +.sidebar_list{padding-left:1em} +.content_area{width:80%;margin:0 2em 0 auto} +.toc{display:none} +.powered-by{font-size:50%;margin-left:1em;position:fixed;bottom:1em} +.powered-by a{color:#2d2b57} +} + +h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400} +h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"#";font-size:.85em;display:block;padding-top:.1em} +h1:hover>a.anchor,h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible} +h1,h2,h3,h4,h5,h6{scroll-margin-top:76px} +.listingblock>.content{position:relative} +.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-family:'Fira Code' sans-serif;font-size:.8em;font-weight:700; top:.425rem;right:1.8rem;line-height:1;text-transform:lowercase;color:inherit;color:#f7df1e} +.listingblock:hover code[data-lang]::before{display:block} +.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5} +.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"} +.listingblock pre.highlightjs{padding:0} +.listingblock pre.highlightjs>code{padding:1em;border-radius:4px} +.listingblock pre.prettyprint{border-width:0} +.admonitionblock{border-radius:5px} +.admonitionblock>table{border-collapse:separate;border-color:#eee;border-left-width:.25rem;border-radius:5px;background:none;width:100%;position:relative} +.admonitionblock>table td.icon{position:absolute;top:0;left:0;padding:.2rem .5rem} +.admonitionblock>table td.icon i::after{content:attr(title);padding-left:.5rem;position:relative;bottom:2px;font-size:.8rem;text-transform:uppercase;font-family:'Ubuntu',sans-serif} +.admonitionblock>table td.icon img{max-width:none} +.admonitionblock>table td.icon .title{font-weight:bold;text-transform:uppercase} +.admonitionblock>table td.content{padding:2rem 1rem 0;color:rgba(0,0,0,.6);word-wrap:anywhere} +.admonitionblock>table td.content> :last-child> :last-child{margin-bottom:0} +.admonitionblock td.icon [class^="fa icon-"]{font-family:'unicons';font-size:20px} +.admonitionblock td.icon .icon-note::before{content:"\e9ad"} +.admonitionblock.note{background-color:rgba(51,153,255,.1)} +.admonitionblock.note>table{border-left-color:rgba(51,153,255)} +.admonitionblock.tip>table{border-left-color:rgb(14,181,70)} +.admonitionblock.warning>table{border-left-color:rgb(255,181,0)} +.admonitionblock.caution>table{border-left-color:rgb(125,56,188)} +.admonitionblock.important>table{border-left-color:rgb(228,0,70)} +.admonitionblock.note{background-color:rgba(51,153,255,.1)} +.admonitionblock.tip{background-color:rgb(14,181,70,.1)} +.admonitionblock.example{background-color:rgb(19,138,54,.1)} +.admonitionblock.warning{background-color:rgb(255,181,0,.1)} +.admonitionblock.caution{background-color:rgb(125,56,188,.1)} +.admonitionblock.important{background-color:rgb(228,0,70,.1)} +.admonitionblock td.icon .icon-note::before,.admonitionblock.note>table,.admonitionblock.note>table td.icon i::after{color:rgb(51,153,255)} +.admonitionblock.tip>table,.admonitionblock.tip>table td.icon i::after{color:rgb(14,181,70)} +.admonitionblock.warning>table,.admonitionblock.warning>table td.icon i::after{color:rgb(255,181,0)} +.admonitionblock.caution>table,.admonitionblock.caution>table td.icon i::after{color:rgb(125,56,188)} +.admonitionblock.important>table,.admonitionblock.important>table td.icon i::after{color:rgb(228,0,70)} +.admonitionblock td.icon .icon-tip::before{content:"\e9c2";color:rgb(14,181,70)} +.admonitionblock.example td.icon .icon-tip::before{content:"\e905"} +.admonitionblock td.icon .icon-warning::before{content:"\e9b1";color:rgb(255,181,0)} +.admonitionblock td.icon .icon-caution::before{content:"\e9b0";color:rgb(125,56,188)} +.admonitionblock td.icon .icon-important::before{content:"\e9a9";color:rgb(228,0,70)} +abbr{font-size:.9em} +abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none} +dfn{font-style:italic} +mark{background:#ff0;color:#000} +pre{white-space:pre-wrap} +q{quotes:"\201C""\201D""\2018""\2019"} +small{font-size:80%} +sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} +sup{top:-.5em} +sub{bottom:-.25em} +img{border:0} +svg:not(:root){overflow:hidden} +figure{margin:0} +audio,video{display:inline-block} +audio:not([controls]){display:none;height:0} +fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em} +legend{border:0;padding:0} +button,input,select,textarea{font-family:inherit;font-size:100%;margin:0} +button,input{line-height:normal} +button,select{text-transform:none} +button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer} +button[disabled],html input[disabled]{cursor:default} +input[type=checkbox],input[type=radio]{padding:0} +button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0} +textarea{overflow:auto;vertical-align:top} +table{border-collapse:collapse;border-spacing:0} +body{background:#fff;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased} +a:hover{cursor:pointer} +img,object,embed{max-width:100%;height:auto} +object,embed{height:100%} +img{-ms-interpolation-mode:bicubic} +.left{float:left!important} +.right{float:right!important} +.text-left{text-align:left!important} +.text-right{text-align:right!important} +.text-center{text-align:center!important} +.text-justify{text-align:justify!important} +.hide{display:none} +img,object,svg{display:inline-block;vertical-align:middle} +textarea{height:auto;min-height:50px} +select{width:100%} +.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em} +#toctitle,.sidebarblock>.content>.title{margin:0;padding:0} +a img{border:0} +p{text-rendering:optimizeLegibility} +p aside{font-size:.875em;line-height:1.35;font-style:italic} +#toctitle,.sidebarblock>.content>.title{font-family:'Open Sans','DejaVu Sans',sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em} +h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em} +hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em} +small{font-size:60%;line-height:inherit} +ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit} +ul.square{list-style-type:square} +ul.circle{list-style-type:circle} +ul.disc{list-style-type:disc} +ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0} +dl dt{margin-bottom:.3125em;font-weight:bold} +dl dd{margin-bottom:1.25em} +blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd} +blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)} + +@media screen and (min-width:768px) { +#toctitle,.sidebarblock>.content>.title{line-height:1.2} +} + +table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal} +table thead,table tfoot{background:#f7f8f7} +table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left} +table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)} +table tr.even,table tr.alt{background:#f8f8f7} +table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6} +#toctitle,.sidebarblock>.content>.title{line-height:1.2;word-spacing:-.05em} +h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400} +.center{margin-left:auto;margin-right:auto} +.stretch{width:100%} +.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table} +.clearfix::after,.float-group::after{clear:both} +:not(pre).nobreak{word-wrap:normal} +:not(pre).nowrap{white-space:nowrap} +:not(pre).pre-wrap{white-space:pre-wrap} +:not(pre):not([class^=L])>code{text-rendering:optimizeSpeed} +pre{text-rendering:optimizeSpeed} +pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit} +pre>code{display:block;font-size:.85em;font-family:'Fira Code',Arial,Helvetica,sans-serif;} +.hljs{padding:1em;} +pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal} +em em{font-style:normal} +strong strong{font-weight:400} +.keyseq{color:rgba(51,51,51,.8)} +kbd{font-family:'Droid Sans Mono','DejaVu Sans Mono',monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap} +.keyseq kbd:first-child{margin-left:0} +.keyseq kbd:last-child{margin-right:0} +.menuseq,.menuref{color:#000} +.menuseq b:not(.caret),.menuref{font-weight:inherit} +.menuseq{word-spacing:-.02em} +.menuseq b.caret{font-size:1.25em;line-height:.8} +.menuseq i.caret{font-weight:bold;text-align:center;width:.45em} +b.button::before,b.button::after{position:relative;top:-1px;font-weight:400} +b.button::before{content:"[";padding:0 3px 0 2px} +b.button::after{content:"]";padding:0 2px 0 3px} +#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em} +#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table} +#header::after,#content::after,#footnotes::after,#footer::after{clear:both} +#content{margin-top:1.25em} +#content::before{content:none} +#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0} +#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf} +#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px} +#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap} +#header .details span:first-child{margin-left:-.125em} +#header .details span.email a{color:rgba(0,0,0,.85)} +#header .details br{display:none} +#header .details br+span::before{content:"\00a0\2013\00a0"} +#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)} +#header .details br+span#revremark::before{content:"\00a0|\00a0"} +#header #revnumber{text-transform:capitalize} +#header #revnumber::after{content:"\00a0"} +#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem} +#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em} +#toc>ul{margin-left:.125em} +#toc ul.sectlevel0>li>a{font-style:italic} +#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0} +#toc ul{font-family:'Open Sans','DejaVu Sans',sans-serif;list-style-type:none} +#toc li{line-height:1.3334;margin-top:.3334em} +#toc a{text-decoration:none} +#toc a:active{text-decoration:underline} +#toctitle{color:#7a2518;font-size:1.2em} + +@media screen and (min-width:768px) { +#toctitle{font-size:1.375em} +body.toc2{padding-left:15em;padding-right:0} +#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto} +#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em} +#toc.toc2>ul{font-size:.9em;margin-bottom:0} +#toc.toc2 ul ul{margin-left:0;padding-left:1em} +#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em} +body.toc2.toc-right{padding-left:0;padding-right:15em} +body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0} +} + +@media screen and (min-width:1280px) { +body.toc2{padding-left:20em;padding-right:0} +#toc.toc2{width:20em} +#toc.toc2 #toctitle{font-size:1.375em} +#toc.toc2>ul{font-size:.95em} +#toc.toc2 ul ul{padding-left:1.25em} +body.toc2.toc-right{padding-left:0;padding-right:20em} +} + +#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px} +#content #toc>:first-child{margin-top:0} +#content #toc>:last-child{margin-bottom:0} +#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em} +#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44} +#content{margin-bottom:.625em} +.sect1{padding-bottom:.625em} + +@media screen and (min-width:768px) { +#content{margin-bottom:1.25em} +.sect1{padding-bottom:1.25em} +} + +.sect1:last-child{padding-bottom:0} +.sect1+.sect1{border-top:1px solid #e7e7e9} +details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em} +details{margin-left:1.25rem} +details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent} +details>summary::-webkit-details-marker{display:none} +details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)} +details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)} +details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem} +.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:'Noto Serif','DejaVu Serif',serif;font-size:1rem;font-style:italic} +table.tableblock.fit-content>caption.title{white-space:nowrap;width:0} +.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)} +.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px} +.exampleblock>.content>:first-child{margin-top:0} +.exampleblock>.content>:last-child{margin-bottom:0} +.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px} +.sidebarblock>:first-child{margin-top:0} +.sidebarblock>:last-child{margin-bottom:0} +.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center} +.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0} +.admonitionblock pre,.admonitionblock>.content>pre{border-radius:4px;overflow-x:auto;padding:0 0 0 1em;font-size:.9em} + +.literalblock pre{background:#f7f7f8} +.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)} + +.prettyprint{background:#f7f7f8} +pre.prettyprint .linenums{line-height:1.45;margin-left:2em} +pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0} +pre.prettyprint li code[data-lang]::before{opacity:1} +pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none} +table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none} +table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal} +table.linenotable td.code{padding-left:.75em} +table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} +pre.pygments span.linenos{display:inline-block;margin-right:.75em} +.quoteblock{margin:0 1em 1.25em 1.5em;display:table} +.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em} +.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify} +.quoteblock blockquote{margin:0;padding:0;border:0} +.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)} +.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0} +.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right} +.verseblock{margin:0 1em 1.25em} +.verseblock pre{font-family:'Open Sans','DejaVu Sans',sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility} +.verseblock pre strong{font-weight:400} +.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex} +.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic} +.quoteblock .attribution br,.verseblock .attribution br{display:none} +.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)} +.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none} +.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0} +.quoteblock.abstract{margin:0 1em 1.25em;display:block} +.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center} +.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf} +.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0} +.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem} +.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0} +p.tableblock:last-child{margin-bottom:0} +td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere} +td.tableblock>.content>:last-child{margin-bottom:-1.25em} +table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede} +table.grid-all>*>tr>*{border-width:1px} +table.grid-cols>*>tr>*{border-width:0 1px} +table.grid-rows>*>tr>*{border-width:1px 0} +table.frame-all{border-width:1px} +table.frame-ends{border-width:1px 0} +table.frame-sides{border-width:0 1px} +table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0} +table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0} +table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0} +table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0} +table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7} +th.halign-left,td.halign-left{text-align:left} +th.halign-right,td.halign-right{text-align:right} +th.halign-center,td.halign-center{text-align:center} +th.valign-top,td.valign-top{vertical-align:top} +th.valign-bottom,td.valign-bottom{vertical-align:bottom} +th.valign-middle,td.valign-middle{vertical-align:middle} +table thead th,table tfoot th{font-weight:bold} +tbody tr th{background:#f7f8f7} +tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold} +p.tableblock>code:only-child{background:none;padding:0} +p.tableblock{font-size:1em} +ol{margin-left:1.75em} +ul li ol{margin-left:1.5em} +dl dd{margin-left:1.125em} +dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0} +li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.25em} +ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none} +ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em} +ul.unstyled,ol.unstyled{margin-left:0} +li>p:empty:only-child::before{content:"";display:inline-block} +ul.checklist>li>p:first-child{margin-left:-1em} +ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em} +ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em} +ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em} +ul.inline>li{margin-left:1.25em} +.unstyled dl dt{font-weight:400;font-style:normal} +ol.arabic{list-style-type:decimal} +ol.decimal{list-style-type:decimal-leading-zero} +ol.loweralpha{list-style-type:lower-alpha} +ol.upperalpha{list-style-type:upper-alpha} +ol.lowerroman{list-style-type:lower-roman} +ol.upperroman{list-style-type:upper-roman} +ol.lowergreek{list-style-type:lower-greek} +.hdlist>table,.colist>table{border:0;background:none} +.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none} +td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em} +td.hdlist1{font-weight:bold;padding-bottom:1.25em} +td.hdlist2{word-wrap:anywhere} +.literalblock+.colist,.listingblock+.colist{margin-top:-.5em} +.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top} +.colist td:not([class]):first-child img{max-width:none} +.colist td:not([class]):last-child{padding:.25em 0} +.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd} +.imageblock.left{margin:.25em .625em 1.25em 0} +.imageblock.right{margin:.25em 0 1.25em .625em} +.imageblock>.title{margin-bottom:0} +.imageblock.thumb,.imageblock.th{border-width:6px} +.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em} +.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0} +.image.left{margin-right:.625em} +.image.right{margin-left:.625em} +a.image{text-decoration:none;display:inline-block} +a.image object{pointer-events:none} +sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super} +sup.footnote a,sup.footnoteref a{text-decoration:none} +sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline} +#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em} +#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0} +#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em} +#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em} +#footnotes .footnote:last-of-type{margin-bottom:0} +#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0} +div.unbreakable{page-break-inside:avoid} +.big{font-size:larger} +.small{font-size:smaller} +.underline{text-decoration:underline} +.overline{text-decoration:overline} +.line-through{text-decoration:line-through} +.aqua{color:#00bfbf} +.aqua-background{background:#00fafa} +.black{color:#000} +.black-background{background:#000} +.blue{color:#0000bf} +.blue-background{background:#0000fa} +.fuchsia{color:#bf00bf} +.fuchsia-background{background:#fa00fa} +.gray{color:#606060} +.gray-background{background:#7d7d7d} +.green{color:#006000} +.green-background{background:#007d00} +.lime{color:#00bf00} +.lime-background{background:#00fa00} +.maroon{color:#600000} +.maroon-background{background:#7d0000} +.navy{color:#000060} +.navy-background{background:#00007d} +.olive{color:#606000} +.olive-background{background:#7d7d00} +.purple{color:#600060} +.purple-background{background:#7d007d} +.red{color:#bf0000} +.red-background{background:#fa0000} +.silver{color:#909090} +.silver-background{background:#bcbcbc} +.teal{color:#006060} +.teal-background{background:#007d7d} +.white{color:#bfbfbf} +.white-background{background:#fafafa} +.yellow{color:#bfbf00} +.yellow-background{background:#fafa00} +span.icon>.fa{cursor:default} +a span.icon>.fa{cursor:inherit} +.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:'Open Sans','DejaVu Sans',sans-serif;font-style:normal;font-weight:bold} +.conum[data-value] *{color:#fff!important} +.conum[data-value]+b{display:none} +.conum[data-value]::after{content:attr(data-value)} +pre .conum[data-value]{position:relative;top:-.125em} +b.conum *{color:inherit!important} +.conum:not([data-value]):empty{display:none} +dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility} +h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em} +p strong,td.content strong,div.footnote strong{letter-spacing:-.005em} +.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em} +.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc} +.print-only{display:none!important} diff --git a/src/commands/serve.js b/src/commands/serve.js index 2020cb1..076e8d1 100644 --- a/src/commands/serve.js +++ b/src/commands/serve.js @@ -1,12 +1,10 @@ -const { findMdFiles, getFilesContent, convertMdToHtml } = require('../common/prepare-content'); -const { embedRemoteMarkdown } = require('../common/embed-remote-markdown'); +const { findDocFiles, getFilesContent, convertDocToHtml } = require('../common/prepare-content'); const serveContent = async (req, res, next) => { - const locatedMdFiles = await findMdFiles(); - const allPages = locatedMdFiles; - const mdFilesContent = await getFilesContent(locatedMdFiles); - const mdFilesWithRemoteContent = await embedRemoteMarkdown(mdFilesContent); - const htmlContent = convertMdToHtml(mdFilesWithRemoteContent); + const locatedDocFiles = await findDocFiles(); + const allPages = locatedDocFiles; + const docFilesContent = await getFilesContent(locatedDocFiles); + const htmlContent = convertDocToHtml(docFilesContent); res.locals.files_data = htmlContent; res.locals.all_pages = allPages; next(); diff --git a/src/common/embed-remote-markdown.js b/src/common/embed-remote-markdown.js deleted file mode 100644 index 15f6bc9..0000000 --- a/src/common/embed-remote-markdown.js +++ /dev/null @@ -1,47 +0,0 @@ -const axios = require('axios'); -const { fileInclusionSchema, embedRemoteMarkdownSchema } = require('../schemas/schemas'); - -const fileInclusion = async (url) => { - await fileInclusionSchema.validateAsync(url).catch(error => { - throw error; - }); - - return axios.get(url).then(response => { - return response.data; - }).catch(error => { - throw error; - }); -}; - -const embedRemoteMarkdown = async (mdFilesContent) => { - try { - await embedRemoteMarkdownSchema.validateAsync(mdFilesContent); - } catch (error) { - throw new Error('Cannot search for remote URLs. The provided content is invalid.'); - } - - const remoteContentExtractionRegex = /!!\+ (?https:\/\/(?:github.com|bitbucket.org)\/[\S\s]*?.md)/g; - return await Promise.all(mdFilesContent.map(async file => { - let resultingContent = file.content; - const isRemoteContent = file.content.match(remoteContentExtractionRegex); - if (isRemoteContent) { - const matches = file.content.matchAll(remoteContentExtractionRegex); - for (const match of matches) { - resultingContent = await fileInclusion(match.groups.url).then(content => { - return resultingContent.replace(match[0], content); - }).catch(() => { - throw new Error('Cannot retrieve the remote content. Something wrong with the URL.'); - }); - } - } - return { - name: file.name, - title: file.title, - content: resultingContent - }; - })); -}; - -module.exports = { - embedRemoteMarkdown -}; diff --git a/src/common/md-parser.js b/src/common/md-parser.js deleted file mode 100644 index 21e0b79..0000000 --- a/src/common/md-parser.js +++ /dev/null @@ -1,22 +0,0 @@ -const showdown = require('showdown'); -const { markyText } = require('../extensions/text-modifications'); -showdown.extension('markyText', markyText); - -const md = new showdown.Converter({ - emoji: true, - ghCodeBlocks: true, - ghCompatibleHeaderId: true, - metadata: true, - parseImgDimension: true, - simplifiedAutoLink: true, - strikethrough: true, - tables: true, - tasklists: true, - extensions: ['markyText', markyText] -}); - -showdown.setFlavor('github'); - -module.exports = { - md: md -}; diff --git a/src/common/parsers.js b/src/common/parsers.js new file mode 100644 index 0000000..2c8f354 --- /dev/null +++ b/src/common/parsers.js @@ -0,0 +1,17 @@ +const Asciidoctor = require('asciidoctor'); +const asciidoctor = Asciidoctor(); + +const asciidoctorDefaultConfig = { + attributes: { + icons: 'font', + idprefix: '', + idseparator: '-', + sectanchors: '' + }, + safe: 'safe' +}; + +module.exports = { + adoc: asciidoctor, + asciidoctorDefaultConfig +}; diff --git a/src/common/prepare-content.js b/src/common/prepare-content.js index c1390f3..ed4a185 100644 --- a/src/common/prepare-content.js +++ b/src/common/prepare-content.js @@ -2,11 +2,11 @@ const ejs = require('ejs'); const fs = require('fs'); const fse = require('fs-extra'); const path = require('path'); -const { md } = require('./md-parser'); +const { adoc, asciidoctorDefaultConfig } = require('./parsers'); const { - findMdFilesSchema, + findDocFilesSchema, filesContentSchema, - convertMdToHtmlSchema, + convertDocToHtmlSchema, saveHtmlContentSchemaFile, saveHtmlContentSchemaContent, compileTemplateSchemaTemplatesPath, @@ -15,30 +15,29 @@ const { buildStaticFilesSchema, copyStaticAssetsSchema } = require('../schemas/schemas'); -const { embedRemoteMarkdown } = require('./embed-remote-markdown'); const checkForChildrenFolders = require('../helpers/checkForChildrenFolders'); -const getMarkdownFiles = require('../helpers/getMarkdownFiles'); +const getDocFiles = require('../helpers/getDocFiles'); const { buildToc } = require('../toc'); const { errorPage } = require('../data/defaults'); /** - * Find all Markdown files within the specified directory + * Find all Asciidoctor files within the specified directory */ -const findMdFiles = async (docsDirectoryName = 'docs') => { - await findMdFilesSchema.validateAsync(docsDirectoryName).catch(() => { +const findDocFiles = async (docsDirectoryName = 'docs') => { + await findDocFilesSchema.validateAsync(docsDirectoryName).catch(() => { throw new Error('Invalid docs directory'); }); const docsDirectoryPath = path.join(process.cwd() + `/${docsDirectoryName}`); if (!fs.existsSync(docsDirectoryPath)) { - throw new Error('The specified directory does not exist'); + throw new Error(`Looks like the specified directory ${docsDirectoryName} does not exist`); } const resultingFilesTree = []; - // Find all the Markdown files in the documentation directory and keep track of the child hierarchy - const markdownFilesFilder = async (dir = docsDirectoryPath) => { + // Find all the Asciidoctor files in the documentation directory and keep track of the child hierarchy + const docFilesFilder = async (dir = docsDirectoryPath) => { // dirLevel is used to calculate hierarchy and children // 0 - docsDirectoryName, 1 - child dir for docsDirectoryName, etc // dirLevel is calculated via relative path @@ -52,72 +51,76 @@ const findMdFiles = async (docsDirectoryName = 'docs') => { const dirName = dirClass[0].toUpperCase() + dirClass.substring(1, dirClass.length); // Directory traversal logic - const markdownFilesInFolder = getMarkdownFiles(dir); - - if (markdownFilesInFolder.length > 0) { + const docFilesInFolder = getDocFiles(dir); + if (docFilesInFolder.length > 0) { resultingFilesTree.push({ dirLevel: dirLevel, basePath: docsDirectoryPath, dirPath: relativePath, dirClass: dirClass, dirName: dirName, - files: markdownFilesInFolder + files: docFilesInFolder }); } const anyFolderInside = checkForChildrenFolders(dir); if (anyFolderInside.length > 0) { for (const directory of anyFolderInside) { - await markdownFilesFilder(path.join(dir, directory)); + await docFilesFilder(path.join(dir, directory)); } } return resultingFilesTree; }; - const mdFiles = await markdownFilesFilder(); - return mdFiles; + const docFiles = await docFilesFilder(); + return docFiles; }; /** - * Get content of Markdown files + * Get content of Asciidoctor files */ const getFilesContent = async (fileDetails) => { + if (!fileDetails.length) { + throw new Error('Looks like you forgot to add files to your documentation directory'); + } try { await filesContentSchema.validateAsync(fileDetails); } catch (error) { - throw new Error('Can\'t get content from Markdown files'); + throw new Error('Can\'t get content from files'); } - const mdFileContent = []; + const docFileContent = []; for (const details of fileDetails) { const absolutePath = path.join(details.basePath, details.dirPath); details.files.forEach(file => { - const content = fs.readFileSync(path.join(absolutePath, `${file}.md`), { encoding: 'utf-8' }); - mdFileContent.push({ + const content = fs.readFileSync(path.join(absolutePath, file.file), { encoding: 'utf-8' }); + docFileContent.push({ name: file, - title: file, + title: file.fileName, content: content }); }); } - return mdFileContent; + return docFileContent; }; /** - * Convert Markdown files to HTML + * Convert Asciidoctor files to HTML */ -const convertMdToHtml = (mdTextArray) => { - const validation = convertMdToHtmlSchema.validate(mdTextArray); +const convertDocToHtml = (docTextArray) => { + const validation = convertDocToHtmlSchema.validate(docTextArray); if (validation.error) { throw new Error('Can\'t convert. Input data is invalid'); } - return mdTextArray.map(mdText => { - const html = md.makeHtml(mdText.content); + return docTextArray.map(docText => { + const fileName = docText.name.file; + const fileTitle = docText.title; + const html = adoc.convert(docText.content, asciidoctorDefaultConfig); const tableOfCOntents = buildToc(html); return { - name: mdText.name, - title: mdText.title, + name: fileName.substring(0, fileName.lastIndexOf('.')), + title: fileTitle.substring(0, fileTitle.lastIndexOf('.')), html, toc: tableOfCOntents }; @@ -137,17 +140,16 @@ const saveHtmlContent = async (filename, htmlContent, outputDirectory = 'public' }; /** - * Wrapper function to find Markdown files and convert content to HTML + * Wrapper function to find Asciidoctor files and convert content to HTML * Returns an object containing two arrays: * - an array of objects with hierarchy of all pages * - an array of objects with all pages and corresponding HTML content */ const buildContent = async (docsDirectoryName = 'docs') => { - const locatedMdFiles = await findMdFiles(docsDirectoryName); - const allPages = locatedMdFiles; - const mdFilesContent = await getFilesContent(locatedMdFiles); - const mdFilesWithRemoteContent = await embedRemoteMarkdown(mdFilesContent); - const htmlContent = convertMdToHtml(mdFilesWithRemoteContent); + const locatedDocFiles = await findDocFiles(docsDirectoryName); + const allPages = locatedDocFiles; + const docFilesContent = await getFilesContent(locatedDocFiles); + const htmlContent = convertDocToHtml(docFilesContent); return { allPages: allPages, htmlContent: htmlContent @@ -245,7 +247,7 @@ const buildStaticFiles = async (docsDirectoryName = 'docs', outputDirectory = 'p const readyHtml = compiledTemplate({ name: '/', title: 'Home', - content: md.makeHtml(fs.readFileSync(path.resolve('README.md'), 'utf-8')), + content: adoc.convert(fs.readFileSync(path.resolve('README.adoc'), 'utf-8'), asciidoctorDefaultConfig), pages: sidebarListOfPages }); await saveHtmlContent('index.html', readyHtml); @@ -265,8 +267,8 @@ const buildStaticFiles = async (docsDirectoryName = 'docs', outputDirectory = 'p }; module.exports = { - findMdFiles: findMdFiles, + findDocFiles: findDocFiles, getFilesContent: getFilesContent, - convertMdToHtml: convertMdToHtml, + convertDocToHtml: convertDocToHtml, buildStaticFiles: buildStaticFiles }; diff --git a/src/extensions/text-modifications.js b/src/extensions/text-modifications.js deleted file mode 100644 index 5c29ccb..0000000 --- a/src/extensions/text-modifications.js +++ /dev/null @@ -1,44 +0,0 @@ -const markyText = (text) => { - return [ - // CROSS-FILES LINKS md -> html - { - type: 'output', - regex: /([\s\S]*?)<\/a>/g, - replace: '$3' - }, - // ADMONITIONS - { - type: 'output', - regex: /

::: (\w*) "(.*)"\n?([\S\s]*?):::<\/p>/g, - replace: '

$2

$3

' - }, - // LANGUAGE LABEL IN CODE BLOCK - { - type: 'output', - regex: /
/g,
-      replace: '
'
-    },
-    // DEFAULT LANGUAGE LABEL IN CODE BLOCK
-    {
-      type: 'output',
-      regex: /
/g,
-      replace: '
'
-    },
-    // TABLE
-    {
-      type: 'output',
-      regex: '',
-      replace: '
' - }, - // HIGHLIGHTED TEXT - { - type: 'output', - regex: /==(.*)==/g, - replace: '$1' - } - ]; -}; - -module.exports = { - markyText -}; diff --git a/src/helpers/getMarkdownFiles.js b/src/helpers/getDocFiles.js similarity index 62% rename from src/helpers/getMarkdownFiles.js rename to src/helpers/getDocFiles.js index 20c0bf0..14e7795 100644 --- a/src/helpers/getMarkdownFiles.js +++ b/src/helpers/getDocFiles.js @@ -3,11 +3,11 @@ const path = require('path'); const { filenameSchema } = require('../schemas/schemas'); /** -* Find all Markdown files in directory +* Find all Asciidoctor files in directory */ -const getMarkdownFiles = (dir) => { +const getDocFiles = (dir) => { return fs.readdirSync(dir) - .filter(file => path.extname(file) === '.md') + .filter(file => path.extname(file) === '.adoc') .map(file => { const validation = filenameSchema.validate(file); @@ -15,8 +15,11 @@ const getMarkdownFiles = (dir) => { throw new Error('Filename is invalid. Valid characters are: letters (A-Z, a-z), numbers (0-9), dashes (-), underscores (_), dots (.)'); } - return file.substring(0, file.lastIndexOf('.')); + return { + file: file, + fileName: file.substring(0, file.lastIndexOf('.')) + }; }); }; -module.exports = getMarkdownFiles; +module.exports = getDocFiles; diff --git a/src/schemas/schemas.js b/src/schemas/schemas.js index fa0fa76..8f4b41f 100644 --- a/src/schemas/schemas.js +++ b/src/schemas/schemas.js @@ -5,12 +5,17 @@ const stringOrPath = joi.string(); const directoryNumberHierarchy = joi.number().min(0).max(4); const stringWithCapitalLetter = stringOrPath.pattern(/^[A-Z][\w]*/); const lowercasedString = stringOrPath.case('lower'); -const arrayOfStrings = joi.array().items(joi.string()); const fullHtmlDocument = stringOrPath.pattern(/[\s\S]*[\s\S]*<\/title>[\s\S]*<\/head>[\s\S]*<body[\s\S]*<\/body>[\s\S]*<\/html>/); const htmlDocumentBody = stringOrPath.pattern(/<\/?[^>]+>/); // just matches HTML tags, should be improved later +const arrayOfObjects = joi.array().min(1).items( + joi.object({ + file: joi.string(), + fileName: joi.string() + }) +); // Schemas -const findMdFilesSchema = stringOrPath.allow('/', stringOrPath).required(); +const findDocFilesSchema = stringOrPath.allow('/', stringOrPath).required(); const filesContentSchema = joi.array().min(1).items( joi.object({ @@ -19,22 +24,22 @@ const filesContentSchema = joi.array().min(1).items( dirPath: stringOrPath.allow('').required(), dirName: stringWithCapitalLetter.required(), dirClass: lowercasedString.required(), - files: arrayOfStrings.required() + files: arrayOfObjects.required() }).required() ).required(); const nameTitleContent = joi.array().min(1).items( joi.object({ - name: stringOrPath.required(), + name: joi.object().required(), title: stringOrPath.required(), content: stringOrPath.required() }).required() ).required(); // Validate raw URLs for Markdown files within GitHub and BitBucket -const fileInclusionSchema = stringOrPath.pattern(/https:\/\/(?:github.com|bitbucket.org)\/([\s\S]*?\/){2}raw\/([\s\S])*?\/([\s\S])*?.md/).required(); +const fileInclusionSchema = stringOrPath.pattern(/include::(https:\/\/raw.githubusercontent.com\/([\s\S]*?.adoc)\[\])/).required(); -const convertMdToHtmlSchema = nameTitleContent.required(); +const convertDocToHtmlSchema = nameTitleContent.required(); const filenameSchema = stringOrPath.pattern(/^([\w-.])*?$/).required(); const saveHtmlContentSchemaFile = stringOrPath.pattern(/^([\w-.])*?\.html$/).required(); @@ -56,12 +61,12 @@ const buildTocSchema = htmlDocumentBody.required(); const embedRemoteMarkdownSchema = nameTitleContent.required(); module.exports = { - findMdFilesSchema, + findDocFilesSchema, filenameSchema, filesContentSchema, fileInclusionSchema, embedRemoteMarkdownSchema, - convertMdToHtmlSchema, + convertDocToHtmlSchema, saveHtmlContentSchemaFile, saveHtmlContentSchemaContent, compileTemplateSchemaTemplatesPath, diff --git a/src/toc.js b/src/toc.js index b784cb3..bdbf782 100644 --- a/src/toc.js +++ b/src/toc.js @@ -7,7 +7,7 @@ const buildToc = (htmlContent) => { throw new Error('Can\'t build table of contents, the provided content is invalid'); } - return [...htmlContent.matchAll(/<h(?<id>[2-6]) id="(?<link>.*)">(?<name>.*)<\/h[2-6]>/g)].map(item => { + return [...htmlContent.matchAll(/<h(?<id>[2-6]) id="(?<link>.*)"><a[\s\S]*?><\/a>(?<name>.*)<\/h[2-6]>/g)].map(item => { return { id: item.groups.id, link: item.groups.link, diff --git a/test/unit/buildStaticFiles.test.js b/test/unit/buildStaticFiles.test.js index 2cd75b9..4626013 100644 --- a/test/unit/buildStaticFiles.test.js +++ b/test/unit/buildStaticFiles.test.js @@ -15,15 +15,15 @@ describe('module buildStaticFiles', () => { test('Ensure the output directory contains generated static files (root README)', async () => { mock({ 'docs': { - 'unit-test.md': '## Unit test 1\nUnit test 1 file content', - 'unit-test-2.md': '## Unit test 2\nUnit test 2 file content' + 'unit-test.adoc': '== Unit test 1\nUnit test 1 file content', + 'unit-test-2.adoc': '== Unit test 2\nUnit test 2 file content' }, 'docs/unit-sub-dir': { - 'unit-test-3.md': '## Unit test 3\nUnit test 3 file content' + 'unit-test-3.adoc': '== Unit test 3\nUnit test 3 file content' }, 'views': mock.load(path.resolve(process.cwd(), 'views')), 'src/assets': mock.load(path.resolve(process.cwd(), 'src/assets')), - 'README.md': mock.load(path.resolve(process.cwd(), 'README.md')), + 'README.adoc': mock.load(path.resolve(process.cwd(), 'README.adoc')), 'public': {} }); @@ -41,12 +41,12 @@ describe('module buildStaticFiles', () => { test('Ensure the output directory contains generated static files (README in the default docs directory)', async () => { mock({ 'docs': { - 'unit-test.md': '## Unit test 1\nUnit test 1 file content', - 'unit-test-2.md': '## Unit test 2\nUnit test 2 file content', - 'README.md': mock.load(path.resolve(process.cwd(), 'README.md')) + 'unit-test.adoc': '== Unit test 1\nUnit test 1 file content', + 'unit-test-2.adoc': '== Unit test 2\nUnit test 2 file content', + 'README.adoc': mock.load(path.resolve(process.cwd(), 'README.adoc')) }, 'docs/unit-sub-dir': { - 'unit-test-3.md': '## Unit test 3\nUnit test 3 file content' + 'unit-test-3.adoc': '== Unit test 3\nUnit test 3 file content' }, 'views': mock.load(path.resolve(process.cwd(), 'views')), 'src/assets': mock.load(path.resolve(process.cwd(), 'src/assets')), @@ -71,7 +71,7 @@ describe('module buildStaticFiles', () => { test('Ensure assets in the default docs directory are copied to the output directory', async () => { mock({ 'docs': { - 'unit-test.md': '## Unit test 1\nUnit test 1 file content', + 'unit-test.adoc': '== Unit test 1\nUnit test 1 file content', }, 'docs/assets': { 'dummy.json': '{ "userId": 1, "title": "delectus aut autem", "completed": false }', @@ -79,7 +79,7 @@ describe('module buildStaticFiles', () => { }, 'views': mock.load(path.resolve(process.cwd(), 'views')), 'src/assets': mock.load(path.resolve(process.cwd(), 'src/assets')), - 'README.md': mock.load(path.resolve(process.cwd(), 'README.md')), + 'README.adoc': mock.load(path.resolve(process.cwd(), 'README.adoc')), 'public': {} }); diff --git a/test/unit/convertDocToHtml.test.js b/test/unit/convertDocToHtml.test.js new file mode 100644 index 0000000..f2ceda8 --- /dev/null +++ b/test/unit/convertDocToHtml.test.js @@ -0,0 +1,40 @@ +const { convertDocToHtml } = require('../../src/common/prepare-content'); + +describe('module convertDocToHtml', () => { + test('Ensure the module throws an error when passing input of incorrect type', () => { + let inputOfIncorrectType = ['string', 77, { "name": "John" }]; + for (let value of inputOfIncorrectType) { + expect(() => convertDocToHtml(value)).toThrowError('Can\'t convert. Input data is invalid'); + } + }); + + test('Convert content without remote inclusion', () => { + let content = [{ + name: { file: 'features.adoc', fileName: 'features' }, + title: 'features', + content: '== Title\nHello world from the fake-root-one file\nhttps://github.com/bandantonio[link to GitHub]' + },{ + name: { file: 'test-file.adoc', fileName: 'test-file' }, + title: 'test-file', + content: '== Title\nHello world from the fake-root-two file\nhttps://github.com/bandantonio/antmarky[link to Antmarky]' + }]; + + let result = convertDocToHtml(content); + expect(result).toHaveLength(2); + result.forEach(obj => expect(Object.keys(obj)).toEqual(['name', 'title', 'html', 'toc'])); + }); + test.todo('convert content with remote inclusion'); + test.todo('create mock function to serve remote content'); + + test('Convert Asciidoctor content', () => { + let content = [{ + name: { file: 'features.adoc', fileName: 'features' }, + title: 'features', + content: '== Hello\n_Hello, Asciidoctor_' + }]; + + let result = convertDocToHtml(content); + expect(result).toHaveLength(1); + result.forEach(obj => expect(Object.keys(obj)).toEqual(['name', 'title', 'html', 'toc'])); + }); +}); \ No newline at end of file diff --git a/test/unit/convertMdToHtml.test.js b/test/unit/convertMdToHtml.test.js deleted file mode 100644 index e1a580b..0000000 --- a/test/unit/convertMdToHtml.test.js +++ /dev/null @@ -1,28 +0,0 @@ -const { convertMdToHtml } = require('../../src/common/prepare-content'); - -describe('module convertMdToHtml', () => { - test('Ensure the module throws an error when passing input of incorrect type', () => { - let inputOfIncorrectType = ['string', 77, { "name": "John" }]; - for (let value of inputOfIncorrectType) { - expect(() => convertMdToHtml(value)).toThrowError('Can\'t convert. Input data is invalid'); - } - }); - - test('Convert content without remote inclusion', () => { - let content = [{ - name: 'features', - title: 'features', - content: '# Title\nHello world from the fake-root-one file\n[link to GitHub](https://github.com/bandantonio)' - },{ - name: 'markdown', - title: 'markdown', - content: '# Title\nHello world from the fake-root-two file\n[link to Antmarky](https://github.com/bandantonio/antmarky)' - }]; - - let result = convertMdToHtml(content); - expect(result).toHaveLength(2); - result.forEach(obj => expect(Object.keys(obj)).toEqual(['name', 'title', 'html', 'toc'])); - }); - test.todo('convert content with remote inclusion'); - test.todo('create mock function to serve remote content'); -}); \ No newline at end of file diff --git a/test/unit/embedRemoteMarkdown.test.js b/test/unit/embedRemoteAsciidoctor.test.js similarity index 54% rename from test/unit/embedRemoteMarkdown.test.js rename to test/unit/embedRemoteAsciidoctor.test.js index b8c2893..dadd07f 100644 --- a/test/unit/embedRemoteMarkdown.test.js +++ b/test/unit/embedRemoteAsciidoctor.test.js @@ -1,68 +1,65 @@ -// jest.mock('../../src/common/embed-remote-markdown'); -const { embedRemoteMarkdown } = require('../../src/common/embed-remote-markdown'); - -describe('module embedRemoteMarkdown', () => { +describe.skip('module embedRemoteAsciidoctor', () => { test('Throw an error when passing input of incorrect type', async () => { let inputOfIncorrectType = ['string', 77, { "name": "John" }]; for (let value of inputOfIncorrectType) { - await expect(embedRemoteMarkdown(value)).rejects.toThrow(Error, 'Cannot search for remote URLs. The provided content is invalid.'); + await expect(embedRemoteAsciidoctor(value)).rejects.toThrow(Error, 'Cannot search for remote URLs. The provided content is invalid.'); } }); test('Throw an error when passing an empty input', async () => { - await expect(embedRemoteMarkdown()).rejects.toThrow(Error, 'Cannot search for remote URLs. The provided content is invalid.'); + await expect(embedRemoteAsciidoctor()).rejects.toThrow(Error, 'Cannot search for remote URLs. The provided content is invalid.'); }); test('Throw an error when trying to fetch an invalid remote content URL', async () => { - let markdownWithBrokenRemoteURL = [{ - name: 'markdown with broken url', - title: 'markdown with broken url', + let asciidoctorWithBrokenRemoteURL = [{ + name: { file: 'asciidoctor with broken url.adoc', fileName: 'asciidoctor with broken url' }, + title: 'asciidoctor with broken url', content: - '## Remote markdown files\n' + - 'You can include remote Markdown files in **raw** format from **GitHub** and **BitBucket** public repositories using `!!+` directive:\n' + - '!!+ github.com/link/to/your/raw/markdown/file.md\n' + - '!!+ bitbucket.org/link/to/your/raw/markdown/file.md\n' + - '!!+ https://github.com/<username>/<repo>/raw/<branch>/filename.md\n' + '== Remote asciidoctor files\n' + + 'You can include remote asciidoctor files in *raw* format from *GitHub* and *BitBucket* public repositories using `!!+` directive:\n' + + '!!+ github.com/link/to/your/raw/asciidoctor/file.adoc\n' + + '!!+ bitbucket.org/link/to/your/raw/asciidoctor/file.adoc\n' + + '!!+ https://github.com/<username>/<repo>/raw/<branch>/filename.adoc\n' }] - await expect(embedRemoteMarkdown(markdownWithBrokenRemoteURL)).rejects.toThrow(Error, 'Cannot retrieve the remote content. Something wrong with the URL.'); + await expect(embedRemoteAsciidoctor(asciidoctorWithBrokenRemoteURL)).rejects.toThrow(Error, 'Cannot retrieve the remote content. Something wrong with the URL.'); }); test('Throw an error when a URL is rejected by validation', async () => { - let markdownWithInvalidRemoteURLs = [{ - name: 'markdown with broken url', - title: 'markdown with broken url', + let asciidoctorWithInvalidRemoteURLs = [{ + name: { file: 'asciidoctor with broken url.adoc', fileName: 'asciidoctor with broken url' }, + title: 'asciidoctor with broken url', content: - '## Remote markdown files\n' + - 'You can include remote Markdown files in **raw** format from **GitHub** and **BitBucket** public repositories using `!!+` directive:\n' + - '!!+ github.com/link/to/your/raw/markdown/file.md\n' + - '!!+ bitbucket.org/link/to/your/raw/markdown/file.md\n' + - '!!+ https://github.com/bandantonio/antmarky/edit/main/README.md\n' + - '!!+ https://github.com/bandantonio/antmarky/blob/main/README.md\n' + - '!!+ https://bitbucket.org/stephendeutsch/confluence-user-macros/src/master/README.md\n' + '== Remote asciidoctor files\n' + + 'You can include remote asciidoctor files in *raw* format from *GitHub* and *BitBucket* public repositories using `!!+` directive:\n' + + '!!+ github.com/link/to/your/raw/asciidoctor/file.adoc\n' + + '!!+ bitbucket.org/link/to/your/raw/asciidoctor/file.adoc\n' + + '!!+ https://github.com/bandantonio/antmarky/edit/main/README.adoc\n' + + '!!+ https://github.com/bandantonio/antmarky/blob/main/README.adoc\n' + + '!!+ https://bitbucket.org/stephendeutsch/confluence-user-macros/src/master/README.adoc\n' }] - await expect(embedRemoteMarkdown(markdownWithInvalidRemoteURLs)).rejects.toThrow(Error, 'Cannot retrieve the remote content. Something wrong with the URL.'); + await expect(embedRemoteAsciidoctor(asciidoctorWithInvalidRemoteURLs)).rejects.toThrow(Error, 'Cannot retrieve the remote content. Something wrong with the URL.'); }); test('Embed remote content from GitHub into the doc', async () => { - let markdownWithSuitableGitHubURL = [{ - name: 'remote-github', + let asciidoctorWithSuitableGitHubURL = [{ + name: { file: 'remote-github.adoc', fileName: 'remote-github' }, title: 'remote-github', content: - '## Remote content from GitHub\n' + - 'Antmarky is a static-site generator for Markdown based on Node.js/EJS.\n' + + '== Remote content from GitHub\n' + + 'Antmarky is a static-site generator for asciidoctor based on Node.js/EJS.\n' + '\n' + - '!!+ https://github.com/bandantonio/bandantonio.github.io/raw/main/README.md\n' + + '!!+ https://github.com/bandantonio/bandantonio.github.io/raw/main/README.adoc\n' + '\n' + 'Zero configuration\n' + '\n' + - 'The main idea behind creating Antmarky was to have a generator with zero configuration that can serve your Markdown files in the documentation directory.' + 'The main idea behind creating Antmarky was to have a generator with zero configuration that can serve your asciidoctor files in the documentation directory.' }]; let expectedGitHubContent = [{ - name: 'remote-github', + name: { file: 'remote-github.adoc', fileName: 'remote-github' }, title: 'remote-github', content: - '## Remote content from GitHub\n' + - 'Antmarky is a static-site generator for Markdown based on Node.js/EJS.\n' + + '== Remote content from GitHub\n' + + 'Antmarky is a static-site generator for asciidoctor based on Node.js/EJS.\n' + '\n' + '# mister-gold.pro\n' + '\n' + @@ -74,7 +71,7 @@ describe('module embedRemoteMarkdown', () => { '\n' + 'My personal website built with [Hexo](https://hexo.io), [EJS](https://ejs.co), and [GitHub Pages](https://pages.github.com).\n' + '\n' + - '## Lighthouse Score\n' + + '== Lighthouse Score\n' + '\n' + '[![Lighthouse](https://img.shields.io/badge/-Performance:97-white?style=flat&logo=Lighthouse&logoColor=red)](https://mister-gold.pro)\n' + '[![Lighthouse](https://img.shields.io/badge/-Accessibility:100-white?style=flat&logo=Lighthouse&logoColor=red)](https://mister-gold.pro)\n' + @@ -84,35 +81,35 @@ describe('module embedRemoteMarkdown', () => { '\n' + 'Zero configuration\n' + '\n' + - 'The main idea behind creating Antmarky was to have a generator with zero configuration that can serve your Markdown files in the documentation directory.' + 'The main idea behind creating Antmarky was to have a generator with zero configuration that can serve your asciidoctor files in the documentation directory.' }]; - let resultingRemoteContent = await embedRemoteMarkdown(markdownWithSuitableGitHubURL); + let resultingRemoteContent = await embedRemoteAsciidoctor(asciidoctorWithSuitableGitHubURL); expect(resultingRemoteContent).toEqual(expectedGitHubContent); }); test('Embed remote content from BitBucket into the doc', async () => { - let markdownWithSuitableBitBucketURL = [{ - name: 'remote-bitbucket', + let asciidoctorWithSuitableBitBucketURL = [{ + name: { file: 'remote-bitbucket.adoc', fileName: 'remote-bitbucket' }, title: 'remote-bitbucket', content: - '## Remote content from Bitbucket\n' + + '== Remote content from Bitbucket\n' + 'Some basic text\n' + - '## Remote markdown files\n' + - 'You can include remote Markdown files in **raw** format from **GitHub** and **BitBucket** public repositories using `!!+` directive:\n' + - '!!+ github.com/link/to/your/raw/markdown/file.md\n' + - '!!+ bitbucket.org/link/to/your/raw/markdown/file.md\n' + - '!!+ https://bitbucket.org/stephendeutsch/confluence-user-macros/raw/master/README.md' + '== Remote asciidoctor files\n' + + 'You can include remote asciidoctor files in *raw* format from *GitHub* and *BitBucket* public repositories using `!!+` directive:\n' + + '!!+ github.com/link/to/your/raw/asciidoctor/file.adoc\n' + + '!!+ bitbucket.org/link/to/your/raw/asciidoctor/file.adoc\n' + + '!!+ https://bitbucket.org/stephendeutsch/confluence-user-macros/raw/master/README.adoc' }]; let expectedBitBucketContent = [{ - name: 'remote-bitbucket', + name: { file: 'remote-bitbucket.adoc', fileName: 'remote-bitbucket' }, title: 'remote-bitbucket', content: - '## Remote content from Bitbucket\n' + + '== Remote content from Bitbucket\n' + 'Some basic text\n' + - '## Remote markdown files\n' + - 'You can include remote Markdown files in **raw** format from **GitHub** and **BitBucket** public repositories using `!!+` directive:\n' + - '!!+ github.com/link/to/your/raw/markdown/file.md\n' + - '!!+ bitbucket.org/link/to/your/raw/markdown/file.md\n' + + '== Remote asciidoctor files\n' + + 'You can include remote asciidoctor files in *raw* format from *GitHub* and *BitBucket* public repositories using `!!+` directive:\n' + + '!!+ github.com/link/to/your/raw/asciidoctor/file.adoc\n' + + '!!+ bitbucket.org/link/to/your/raw/asciidoctor/file.adoc\n' + '# Confluence User Macros #\n' + '\n' + 'These are a couple of user macros that I have created for Confluence in order to add new features or make administration easier.\n' + @@ -123,22 +120,22 @@ describe('module embedRemoteMarkdown', () => { '\n' + 'I am making these available for free under the [Apache license](http://www.apache.org/licenses/LICENSE-2.0.html), but if you find any of them useful, consider [making a donation via Paypal.](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=8Z6CHCT4XCLPA&lc=US&item_name=Stephen%20Deutsch¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted)' }]; - let resultingRemoteContent = await embedRemoteMarkdown(markdownWithSuitableBitBucketURL); + let resultingRemoteContent = await embedRemoteAsciidoctor(asciidoctorWithSuitableBitBucketURL); expect(resultingRemoteContent).toEqual(expectedBitBucketContent); }); test('Pass content "as is" if no remote content found (URLs skipped)', async () => { - let markdownWithGeneralURLs = [{ - name: 'text-with-random-valid-urls', + let asciidoctorWithGeneralURLs = [{ + name: { file: 'text-with-random-valid-urls.adoc', fileName: 'text-with-random-valid-urls' }, title: 'text-with-random-valid-urls', content: - '## Faveas et conveniet oblitus ima properatis libet\n' + + '== Faveas et conveniet oblitus ima properatis libet\n' + '\n' + - 'Lorem markdownum gerens? Hac tyranni crudelis pater, atria nec fiducia *tibi*:\n' + + 'Lorem asciidoctorum gerens? Hac tyranni crudelis pater, atria nec fiducia *tibi*:\n' + 'hanc inplicuit potest. Non [At tanges](http://www.tamen.net/hectoris) florentis,\n' + 'iusque, iam vetus Semeleia duxere sed alveus petit vel: de.\n' + '\n' + - 'https://github.com/username/repo/raw/branch/filname.md\n' + + 'https://github.com/username/repo/raw/branch/filname.adoc\n' + '\n' + 'Laevaque Rhoetei\n' + 'hostem, confesso fixis, iterumque torto cruore et pennis nostris aliquis quam\n' + @@ -157,9 +154,9 @@ describe('module embedRemoteMarkdown', () => { 'mille restat sororum, plus Inachides parenti belua, lato floresque magis.' }]; - let expectedGeneralContent = markdownWithGeneralURLs; + let expectedGeneralContent = asciidoctorWithGeneralURLs; - let resultingRemoteContent = await embedRemoteMarkdown(markdownWithGeneralURLs); + let resultingRemoteContent = await embedRemoteAsciidoctor(asciidoctorWithGeneralURLs); expect(resultingRemoteContent).toEqual(expectedGeneralContent); }); }); \ No newline at end of file diff --git a/test/unit/findDocFiles.test.js b/test/unit/findDocFiles.test.js new file mode 100644 index 0000000..94bc423 --- /dev/null +++ b/test/unit/findDocFiles.test.js @@ -0,0 +1,101 @@ +const mock = require('mock-fs'); +const { findDocFiles } = require('../../src/common/prepare-content'); + +describe('module findDocFiles', () => { + + test('Ensure the module outputs an array (default `docs` directory)', async () => { + const files = await findDocFiles(); + expect(files).toBeInstanceOf(Array); + }) + + test.todo('Ensure the module outputs an array (custom `docs` directory'); + + test('Ensure the module outputs an array (root directory)', async () => { + const files = await findDocFiles('/'); + expect(files).toBeInstanceOf(Array); + }); + + test('Ensure the module outputs an array of expected format (default `docs` directory)', async () => { + const files = await findDocFiles(); + files.map(obj => expect(Object.keys(obj)).toEqual(['dirLevel', 'basePath', 'dirPath', 'dirClass', 'dirName', 'files'])); + }); + + test.todo('Ensure the module outputs an array of expected format (custom `docs` directory)'); + + test('Ensure the module outputs an array of expected format (root directory)', async () => { + const files = await findDocFiles('/'); + files.map(obj => expect(Object.keys(obj)).toEqual(['dirLevel', 'basePath', 'dirPath', 'dirClass', 'dirName', 'files'])) + }); + + test('Throw an error when docs directory name is invalid', async () => { + await expect(findDocFiles(123)).rejects.toThrow(Error, 'Invalid docs directory'); + }); + + test(`Throw an error when the specified docs directory doesn't exist`, async () => { + await expect(findDocFiles('nonexistentdirectory')).rejects.toThrow(Error, 'The specified directory does not exist'); + }); + + describe('module findDocFiles - working with files', () => { + + test('Output files from the root docs directory', async () => { + mock({ + 'fake-root-one.adoc': `= Title\nHello world from the fake-root-one file`, + 'fake-root-two.adoc': `= Title\nHello world from the fake-root-two file`, + }); + + let result = await findDocFiles('/'); + expect(result.map(obj => obj.files)).toContainEqual([ + { file: 'fake-root-one.adoc', fileName: 'fake-root-one' }, + { file: 'fake-root-two.adoc', fileName: 'fake-root-two' } + ]) + + mock.restore(); + }); + + test(`Output files from the default docs directory (with child directories)`, async () => { + mock({ + 'docs': { + 'Fake-docs-dir-one.adoc': `= Title\nHello world from the fake-docs-dir-one file`, + 'Fake-docs-dir-two.adoc': `= Title\nHello world from the fake-docs-dir-two file`, + 'child-directory': { + 'Fake-docs-child-dir-one.adoc': `= Title\nHello world from the fake-child-dir-one file`, + 'Fake-docs-child-dir-two.adoc': `= Title\nHello world from the fake-child-dir-two file`, + } + } + }); + + let result = await findDocFiles(); + + expect(result).toBeInstanceOf(Array); + expect(result).toHaveLength(2); + expect(result[0].dirName).toEqual('Home'); + expect(result[1].dirName).toEqual('Child-directory'); + + expect(result[0].files).toEqual(expect.arrayContaining([ + { file: 'Fake-docs-dir-one.adoc', fileName: 'Fake-docs-dir-one' }, + { file: 'Fake-docs-dir-two.adoc', fileName: 'Fake-docs-dir-two' } + ])); + expect(result[1].files).toEqual(expect.arrayContaining([ + { file: 'Fake-docs-child-dir-one.adoc', fileName: 'Fake-docs-child-dir-one' }, + { file: 'Fake-docs-child-dir-two.adoc', fileName: 'Fake-docs-child-dir-two' } + ])); + + mock.restore(); + }); + + test(`Should throw an error when passing invalid filename`, async () => { + mock({ + 'docs': { + 'Fake-docs-dir-one.adoc': `= Title\nHello world from the fake-docs-dir-one file`, + 'child-directory': { + 'Very¢£«±Ÿ÷_bad&*()\/<> file-!@#$%^ name.adoc': `= Title\nHello world from the fake-child-dir-one file` + } + } + }); + + await expect(findDocFiles()).rejects.toThrow(Error, 'Filename is invalid. Valid characters are: letters (A-Z, a-z), numbers (0-9), dashes (-), underscores (_), dots (.)'); + + mock.restore(); + }); + }); +}); diff --git a/test/unit/findMdFiles.test.js b/test/unit/findMdFiles.test.js deleted file mode 100644 index c4fd23a..0000000 --- a/test/unit/findMdFiles.test.js +++ /dev/null @@ -1,92 +0,0 @@ -const mock = require('mock-fs'); -const { findMdFiles } = require('../../src/common/prepare-content'); - -describe('module findMdFiles', () => { - - test('Ensure the module outputs an array (default `docs` directory)', async () => { - const files = await findMdFiles(); - expect(files).toBeInstanceOf(Array); - }) - - test.todo('Ensure the module outputs an array (custom `docs` directory'); - - test('Ensure the module outputs an array (root directory)', async () => { - const files = await findMdFiles('/'); - expect(files).toBeInstanceOf(Array); - }); - - test('Ensure the module outputs an array of expected format (default `docs` directory)', async () => { - const files = await findMdFiles(); - files.map(obj => expect(Object.keys(obj)).toEqual(['dirLevel', 'basePath', 'dirPath', 'dirClass', 'dirName', 'files'])); - }); - - test.todo('Ensure the module outputs an array of expected format (custom `docs` directory)'); - - test('Ensure the module outputs an array of expected format (root directory)', async () => { - const files = await findMdFiles('/'); - files.map(obj => expect(Object.keys(obj)).toEqual(['dirLevel', 'basePath', 'dirPath', 'dirClass', 'dirName', 'files'])) - }); - - test('Throw an error when docs directory name is invalid', async () => { - await expect(findMdFiles(123)).rejects.toThrow(Error, 'Invalid docs directory'); - }); - - test(`Throw an error when the specified docs directory doesn't exist`, async () => { - await expect(findMdFiles('nonexistentdirectory')).rejects.toThrow(Error, 'The specified directory does not exist'); - }); - - describe('module findMdFiles - working with files', () => { - - test('Output files from the root docs directory', async () => { - mock({ - 'fake-root-one.md': `# Title\nHello world from the fake-root-one file`, - 'fake-root-two.md': `# Title\nHello world from the fake-root-two file`, - }); - - let result = await findMdFiles('/'); - expect(result.map(obj => obj.files)).toContainEqual(['fake-root-one', 'fake-root-two']) - - mock.restore(); - }); - - test(`Output files from the default docs directory (with child directories)`, async () => { - mock({ - 'docs': { - 'Fake-docs-dir-one.md': `# Title\nHello world from the fake-docs-dir-one file`, - 'Fake-docs-dir-two.md': `# Title\nHello world from the fake-docs-dir-two file`, - 'child-directory': { - 'Fake-docs-child-dir-one.md': `# Title\nHello world from the fake-child-dir-one file`, - 'Fake-docs-child-dir-two.md': `# Title\nHello world from the fake-child-dir-two file`, - } - } - }); - - let result = await findMdFiles(); - - expect(result).toBeInstanceOf(Array); - expect(result).toHaveLength(2); - expect(result[0].dirName).toEqual('Home'); - expect(result[1].dirName).toEqual('Child-directory'); - - expect(result[0].files).toEqual(expect.arrayContaining(['Fake-docs-dir-one', 'Fake-docs-dir-two'])); - expect(result[1].files).toEqual(expect.arrayContaining(['Fake-docs-child-dir-one', 'Fake-docs-child-dir-two'])); - - mock.restore(); - }); - - test(`Should throw an error when passing invalid filename`, async () => { - mock({ - 'docs': { - 'Fake-docs-dir-one.md': `# Title\nHello world from the fake-docs-dir-one file`, - 'child-directory': { - 'Very¢£«±Ÿ÷_bad&*()\/<> file-!@#$%^ name.md': `# Title\nHello world from the fake-child-dir-one file` - } - } - }); - - await expect(findMdFiles()).rejects.toThrow(Error, 'Filename is invalid. Valid characters are: letters (A-Z, a-z), numbers (0-9), dashes (-), underscores (_), dots (.)'); - - mock.restore(); - }); - }); -}); diff --git a/test/unit/getFilesContent.test.js b/test/unit/getFilesContent.test.js index e4fc389..50007e2 100644 --- a/test/unit/getFilesContent.test.js +++ b/test/unit/getFilesContent.test.js @@ -5,7 +5,7 @@ describe('module getFilesContent', () => { test('Throw an error when passing input of incorrect type', async () => { let inputOfIncorrectType = ['string', 77, { "name": "John" }]; for (let value of inputOfIncorrectType) { - await expect(getFilesContent(value)).rejects.toThrow(Error, `Can't get content from Markdown files`); + await expect(getFilesContent(value)).rejects.toThrow(Error, `Can't get content from files`); } }); @@ -13,11 +13,21 @@ describe('module getFilesContent', () => { // Merge this test later with the one below mock({ 'docs': { - 'unit-tests.md': `# Title\nHello world from the fake-root-one file\n[link to GitHub](https://github.com/bandantonio)`, - 'markdown.md': `# Title\nHello world from the fake-root-two file\n[link to Antmarky](https://github.com/bandantonio/antmarky)`, + 'unit-tests.adoc': `= Title\nHello world from the fake-root-one file\nhttps://github.com/bandantonio[link to GitHub]`, + 'asciidoctor.adoc': `= Title\nHello world from the fake-root-two file\nhttps://github.com/bandantonio/antmarky[link to Antmarky]`, } }); - let content = [{ dirLevel: 0, basePath: `${process.cwd()}/docs`, dirPath: '', dirClass: 'home', dirName: 'Home', files: [ 'unit-tests', 'markdown' ] }] + let content = [{ + dirLevel: 0, + basePath: `${process.cwd()}/docs`, + dirPath: '', + dirClass: 'home', + dirName: 'Home', + files: [ + { file: 'unit-tests.adoc', fileName: 'unit-tests' }, + { file: 'asciidoctor.adoc', fileName: 'asciidoctor' } + ] + }] let result = await getFilesContent(content); @@ -25,14 +35,14 @@ describe('module getFilesContent', () => { result.forEach(obj => expect(Object.keys(obj)).toEqual(['name', 'title', 'content'])); expect(result[0]).toEqual({ - name: 'unit-tests', + name: { file: 'unit-tests.adoc', fileName: 'unit-tests' }, title: 'unit-tests', - content: '# Title\n' + 'Hello world from the fake-root-one file\n' + '[link to GitHub](https://github.com/bandantonio)' + content: '= Title\n' + 'Hello world from the fake-root-one file\n' + 'https://github.com/bandantonio[link to GitHub]' }); expect(result[1]).toEqual({ - name: 'markdown', - title: 'markdown', - content: '# Title\n' + 'Hello world from the fake-root-two file\n' + '[link to Antmarky](https://github.com/bandantonio/antmarky)' + name: { file: 'asciidoctor.adoc', fileName: 'asciidoctor' }, + title: 'asciidoctor', + content: '= Title\n' + 'Hello world from the fake-root-two file\n' + 'https://github.com/bandantonio/antmarky[link to Antmarky]' }); mock.restore(); diff --git a/test/unit/toc.test.js b/test/unit/toc.test.js index 1b62c08..790e257 100644 --- a/test/unit/toc.test.js +++ b/test/unit/toc.test.js @@ -27,39 +27,41 @@ describe('module buildToc', () => { test('Should properly find all the headings from the html content', () => { let htmlPiece = - '<h1 id="features">Features</h1>\n' + - '<h2 id="remote-markdown-files">Remote markdown files</h2>\n' + - '<p>You can include remote Markdown files in <strong>raw</strong> format from <strong>GitHub</strong> and <strong>BitBucket</strong> public repositories using <code>!!+</code> directive</p>\n' + - '<pre data-language="js"><code class="js language-js">let greeting = \'Hello World!\';\n' + - 'console.log(greeting);\n' + - '</code></pre>\n' + - '<h3 id="syntax-highlighting">Syntax highlighting</h3>\n' + - '<h1 id="markdown-support">Markdown support</h1>\n' + - '<p>Antmarky uses <code>GitHub</code> flavor for the Markdown parser. The parser supports the following syntax:</p>\n' + - '<h2 id="heading-ids">Heading ids</h2>\n' + - '<h1 id="hello-world">Hello World</h1>\n' + - '<h1 id="features">Features</h1>\n' + - '<h2 id="features-two"></h2>\n' + - '<h2>Remote markdown files</h2>\n' + - '<p>You can include remote Markdown files in <strong>raw</strong> format from <strong>GitHub</strong> and <strong>BitBucket</strong> public repositories using <code>!!+</code> directive</p>\n' + - '<pre data-language="js"><code class="js language-js">let greeting = \'Hello World!\';\n' + - 'console.log(greeting);\n' + - '</code></pre>\n' + - '<h7 id="syntax-highlighting-bad"></h7>\n' + - '<h4 id="syntax-highlighting-ok">Syntax</h4>\n' + - '<h6 id="markdown-support">Markdown support</h6>\n' + - '<p>Antmarky uses <code>GitHub</code> flavor for the Markdown parser. The parser supports the following syntax:</p>\n' + - '<h5 id="heading-ids">Heading ids</h5>\n' + - '<h1 id="hello-world">Hello World</h1>'; + '<h2 id="features"><a class="anchor" href="#features"></a>Features</h2>\n' + + '<div class="sectionbody">\n' + + '<div class="sect2">\n' + + '<h3 id="admonitions"><a class="anchor" href="#admonitions"></a>Admonitions</h3>\n' + + '<div class="paragraph"><p>Supported admonitions: <code>NOTE</code>, <code>TIP</code>, <code>IMPORTANT</code>, <code>CAUTION</code>,\n' + + '<code>WARNING</code>.</p></div>\n' + + '<div class="admonitionblock note"><table><tr>\n' + + '<td class="icon">\n' + + '<i class="fa icon-note" title="Note"></i></td>\n' + + '<td class="content">\n' + + '<div class="listingblock">\n' + + '<div class="content">\n' + + '<pre class="highlight"><code class="language-md" data-lang="md">NOTE: Note admonition body</code></pre>\n' + + '</div></div></td></tr></table></div></div>\n' + + '<div class="sect2">\n' + + '<h3 id="fontawesome"><a class="anchor" href="#fontawesome"></a>FontAwesome</h3>\n' + + '<div class="paragraph">\n' + + '<p>Antmarky supports <a href="https://fontawesome.com/v4/cheatsheet/" target="_blank" rel="noopener">FontAwesome v4.7</a> for compatibility reasons.</p>\n' + + '</div>\n' + + '<div class="paragraph">\n' + + '<p>To use icons in the document, use the following syntax:</p>\n' + + '</div>\n' + + '<div class="listingblock">\n' + + '<div class="content">\n' + + '<pre class="highlight"><code class="language-plaintext" data-lang="plaintext">icon:bicycle[] is good for your icon:heart[]</code></pre>\n' + + '</div></div>\n' + + '<div class="paragraph">\n' + + '<p>This will be rendered as: <span class="icon"><i class="fa fa-bicycle"></i></span> is good for your <span\n' + + 'class="icon"><i class="fa fa-heart"></i></span></p>\n' + + '</div></div></div></div>'; let expectedResult = [ - { id: '2', link: 'remote-markdown-files', title: 'Remote markdown files' }, - { id: '3', link: 'syntax-highlighting', title: 'Syntax highlighting' }, - { id: '2', link: 'heading-ids', title: 'Heading ids' }, - { id: '2', link: 'features-two', title: '' }, - { id: '4', link: 'syntax-highlighting-ok', title: 'Syntax' }, - { id: '6', link: 'markdown-support', title: 'Markdown support' }, - { id: '5', link: 'heading-ids', title: 'Heading ids' }, + { id: '2', link: 'features', title: 'Features' }, + { id: '3', link: 'admonitions', title: 'Admonitions' }, + { id: '3', link: 'fontawesome', title: 'FontAwesome' } ]; expect(buildToc(htmlPiece)).toEqual(expectedResult); diff --git a/views/404.ejs b/views/404.ejs index d1b3ce2..b8955b0 100644 --- a/views/404.ejs +++ b/views/404.ejs @@ -15,7 +15,7 @@ <%= text %> </div> </div> - <div class="content_area col"> + <div class="content_area col-8"> <h1><%- title %></h1> <br /> <p>Return to <a href="./">Home page</a></p> diff --git a/views/partials/footer.ejs b/views/partials/footer.ejs index 5e882d7..20e1c93 100644 --- a/views/partials/footer.ejs +++ b/views/partials/footer.ejs @@ -1,5 +1,5 @@ <div class="powered-by"> <span> - <a href="https://github.com/bandantonio/antmarky">Powered by <i class="fas fa-check-square"></i> Antmarky</a> + <a href="https://github.com/bandantonio/antmarky">Powered by <i class="fa fa-check-square"></i> Antmarky</a> </span> </div> \ No newline at end of file diff --git a/views/partials/head.ejs b/views/partials/head.ejs index 0b1ec50..191baeb 100644 --- a/views/partials/head.ejs +++ b/views/partials/head.ejs @@ -2,9 +2,7 @@ <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Antmarky | <%= title %> - - + + - - \ No newline at end of file diff --git a/views/partials/nav.ejs b/views/partials/nav.ejs index b0a89f8..a940def 100644 --- a/views/partials/nav.ejs +++ b/views/partials/nav.ejs @@ -1,6 +1,6 @@