MkDocs¶
Like GitBook, MkDocs is a fast and simple static site generator with template, plugin and extension support. Documentation source files are written also in Markdown, and configured with a single YAML configuration file. MkDocs brings modern and customizable style, lots of possible extensions with powerful markdown interpretation.
This site use the material theme but others are possible, too. Material theme has responsive design and fluid layout for all kinds of screens and devices, designed to serve your project documentation in a user-friendly way in 34 languages with optimal readability. Some basic customization like primary and accent color, fonts... could be configured.
Also a collection of useful extensions are included here, too. So this is not only a description of the basics but presenting you a fully usable and optimal setup of it.
Docker¶
You can use a ready and complete docker image to create mkdocs pages. This gives you an easy solution without thinking about all the installation details with the extensions and plugins.
CLI call¶
After installing docker you can directly run it:
# run with mkdocs in the current directory
docker run -v $(pwd):/data alinex/mkdocs
# run with documentation in specific directory
docker run -v /my-project:/data alinex/mkdocs
The given directory has to contain the mkdocs.yml
configuration file.
Create in GitLab CI¶
Use the following CI script to run the document creation within GitLab CI and deploy to GitLab pages:
- create documentation
- deploy to pages
image: alinex/mkdocs
pages:
stage: deploy
script:
- "/run.sh"
- rm -rf public
- mv site public
artifacts:
paths:
- public
Run as NPM Script¶
To allow easy call within NodeJS projects add the following configuration:
"scripts": {
...
"docs": "docker run -v $(pwd):/data alinex/mkdocs && xdg-open file:///$(pwd)/site/index.html"
}
Afterwards you can call it with npm run docs
and it will create them and open it in your default browser.
Check Locally¶
To check your result locally you simply open the file site/index.html
in the browser.
Install¶
Info
As an easy alternative to install mkdocs, use it in a ready to run docker container with all of the functionality of this page build together, including PDF creation.
You only need to read this chapter if you don't use docker.
Python 3 should already be installed in new releases, so nothing to do.
On Debian the following steps should be enough to get it locally running:
sudo apt install build-essential python3-dev python3-pip python3-setuptools python3-wheel python3-cffi libcairo2 libpango-1.0-0 libpangocairo-1.0-0 libgdk-pixbuf2.0-0 libffi-dev shared-mime-info
Take care that you use python 3!
$ ll -al $(which python)
lrwxrwxrwx 1 root root 18 Mär 8 10:03 /usr/bin/python -> /usr/bin/python3.6*
If this points to python 2.7 you should change that on problems first.
Now you can install the python packages:
python -m pip install --upgrade pip
python -m pip install mkdocs
python -m pip install mkdocs-material
python -m pip install pymdown-extensions
python -m pip install markdown-blockdiag
python -m pip install markdown-include
python -m pip install mkdocs-with-pdf
python -m pip install django-weasyprint
python -m pip install mkdocs-awesome-pages-plugin
python -m pip install mkdocs-minify-plugin
python -m pip install mkdocs-git-revision-date-localized-plugin
python -m pip install mkdocs-include-markdown-plugin
You should be able to directly call mkdocs
on the console, now.
To make it accessible in path, add the following to ~/.bashrc
:
PATH=$PATH:~/.local/bin
And for the epub conversion you need to have calibre installed as package or using:
curl -sL https://download.calibre-ebook.com/linux-installer.sh | sudo -E bash -
Bug
The epub output is not really useable at the moment.
Problems¶
mkdocs could not be installed
If the above won't install mkdocs
try to install some tools first:
sudo apt-get install python-setuptools
python -m pip install wheel
After that retry to install mkdocs
and it's extensions.
Problem with cairocffi
Maybe your cairocffi version is not matching and you get some errors like Requirement.parse('cairocffi>=0.9.0'), {'weasyprint'})
, then you can check your version like:
$ python -m pip show cairocffi
Name: cairocffi
Version: 0.9.0
...
To install a specific version use:
python -m pip uninstall cairocffi
python -m pip install cairocffi==1.0.1
Update¶
To later update your installation only call the following:
python -m pip install --upgrade pip
python -m pip install --upgrade mkdocs
python -m pip install --upgrade mkdocs-material
python -m pip install --upgrade pymdown-extensions
python -m pip install --upgrade markdown-blockdiag
python -m pip install --upgrade markdown-include
python -m pip install --upgrade mkdocs-with-pdf
python -m pip install --upgrade django-weasyprint
python -m pip install --upgrade mkdocs-awesome-pages-plugin
python -m pip install --upgrade mkdocs-minify-plugin
python -m pip install --upgrade mkdocs-git-revision-date-localized-plugin
python -m pip install --upgrade mkdocs-mermaid2-plugin
python -m pip install --upgrade mkdocs-include-markdown-plugin
Preview Server¶
While you are working on the documentation and create new stuff it is often necessary to immediately see how it looks like. This is possible if you start an development server of mkdocs
using:
mkdocs serve # from within the project home
This will start an development server which automatically reloads on changes.
Build Documentation¶
To create the documentation in the site
sub folder use:
mkdocs build
Configuration¶
The setup is completely done in a mkdocs.yml
file within your project's root directory. All in all you can and have to specify a lot, but this section will guide you.
First some descriptive information for the site:
site_name: Alinex Development Guide
site_description: A book to learn modern web technologies.
site_author: Alexander Schilling
copyright: Copyright © 2016 - 2022 <a href="https://alinex.de">Alexander Schilling</a>
While the site_name
is used as heading the site_description
and site_author
goes into the meta data. And the copyright
line will be displayed in the footer with optional HTML links as seen above.
Navigation¶
The navigation may be
- auto detected
- defined using the nav section
- defined by
.pages
files
A navigation section in the mkdocs.yml
will look like:
nav:
- Home:
- README.md
- alinex.md
- Languages:
- Overview: lang/README.md
- Markdown: lang/markdown.m
- Handlebars: lang/handlebars.md
- ... | lang/*.md
Chapters can not contain a direct page. A title can be given for each page. If not the title setting at the top of each page is used or the first heading.
And you can also add all not individually added pages anywhere with ...
as entry or using a glob pattern like ... | lang/*.md
, ... | flat | lang/*.md
or regexp patterns ... | regex=page-[0-9]+.md
.
Through the Awesome Pages Plugin Another alternative is to use .pages
files in each documentation directory which specifies this part:
- use a
nav
section with the markdown files and subdirectories (from this folder) - add
...
there to add the undeclared ones - add
sort: asc
to sort automatically added entries - add
collapse: true
to not make a folder entry while there is only one element - add
hide: true
to exclude this directory - add
title: Section Title
to give this section a specific title
Theme¶
Now the theme definition, here we use the material theme as a basis:
use_directory_urls: false
theme:
name: material
icon:
logo: material/book-open-variant
favicon: assets/favicon.ico
language: en
palette:
scheme: slate
primary: grey
accent: dark orange
font:
text: Lato
code: Roboto Mono
features:
- navigation.instant
- content.code.annotate
The first line with use_directory_urls: false
makes the site also browsable locally.
The logo
can be a name from the material icons (displayed on the top left beside the page heading). The favicon
has to be set to an image within the docs
folder. If feature/tabs
is set the first level of navigation is put at tabs on the top.
repo_name: "alinex/alinex.gitlab.io"
repo_url: "https://gitlab.com/alinex/alinex.gitlab.io"
edit_uri: "" (1)
- This line prevents the edit icon, remove this line or set a correct url to allow editing.
Like shown in the image the repository will be displayed on the right and if no edit_uri: "...."
is given or set an icon to edit the page source is added, too. To prevent this in the example config edit_uri
is set to an empty string.
extra:
social:
- icon: material/gitlab
link: https://gitlab.com/alinex
- icon: material/github
link: https://github.com/alinex
- icon: material/home
link: https://alinex.de
The social links use the FontAwesome names as type with a link. They will be displayed at the bottom right corner of the page.
extra_css:
- assets/extra.css
With the extra_css
section you may add more stylesheets to the generated HTML which are used to:
- optimize the theme
- to be used with attributes
Also you should at least add the folowing two javascript files:
extra_javascript:
- assets/extra.js
The two files will look like:
@import url("https://fonts.googleapis.com/css2?family=Oswald&display=swap");
/* use image for color theme */
.md-footer,
.md-header,
.md-tabs,
body {
/* background-image: url('blue-tunnel.jpg');*/
background-attachment: fixed;
background-image: url("default.jpg");
background-size: cover;
}
.md-container,
.md-search__inner {
background-color: rgba(0, 0, 0, 0.9);
}
.md-footer-meta,
.md-footer-nav {
background: transparent;
}
/* General style */
.md-typeset h1,
.md-tabs,
.md-header-nav__topic,
.md-sidebar {
font-family: "Oswald", sans-serif;
}
.md-tabs a {
font-size: 0.9rem;
}
.md-sidebar label {
font-size: 0.85rem;
}
.md-sidebar a {
font-size: 0.75rem;
margin-top: 0.4em;
}
.md-typeset h1 {
color: white;
}
.md-typeset__table th {
border-bottom: 2px solid darkorange;
font-weight: bold;
}
.md-typeset__table tr:nth-child(even) {
background: rgb(61, 61, 76);
}
.md-typeset table:not([class]) tr:nth-child(even):hover {
background-color: rgba(61, 61, 76, 0.035);
}
/* change link colors for grey theme */
.md-header-nav__button:hover {
color: darkorange;
opacity: 1;
transition: color 0.5s;
}
[data-md-color-primary="grey"] .md-typeset a {
color: #00ade2;
}
[data-md-color-primary="grey"] .md-typeset a:hover {
color: darkorange;
}
.md-nav__item .md-nav__link--active {
color: white;
}
.md-sidebar label,
.md-nav__item--nested > .md-nav__link,
.md-sidebar a {
color: gray;
}
.md-nav__link:hover {
color: darkorange;
}
.md-nav__link[data-md-state="blur"] {
color: rgb(180, 180, 180);
}
.md-nav__item .md-nav__link--active {
color: white;
}
.md-footer-nav__link {
font-weight: bold;
}
.md-footer-nav__link:hover {
color: darkorange;
opacity: 1;
transition: color 0.5s;
}
a.headerlink {
color: gray;
}
hr {
border-color: white;
}
/* display external links with icon */
div.md-content a[href^="http://"]:not([href*="alinex.gitlab.io"]):after,
div.md-content a[href^="https://"]:not([href*="alinex.gitlab.io"]):after,
div.md-content a[href^="//"]:not([href*="alinex.gitlab.io"])
{
content: "↗";
display: inline-block;
font-size: 70%;
font-style: normal;
font-weight: normal;
text-decoration: none;
vertical-align: top;
}
/* attribute classes to be used via {: .class} */
.left {
/* left align with text float on the right */
float: left;
padding-right: 20px;
}
.right {
/* right align with text float on the left */
float: right;
padding-left: 20px;
}
.icon {
/* change image size */
width: 25%;
}
.border {
/* add drop shadow */
-moz-box-shadow: 0 0 18px 5px rgba(250, 249, 249, 0.5);
-webkit-box-shadow: 0 0 18px 5px rgba(250, 249, 249, 0.5);
border: 1px solid #7f8081;
box-shadow: 0 0 18px 5px rgba(250, 249, 249, 0.5);
}
/* special use tags ==...== */
mark,
.md-typeset mark {
background-color: rgb(255, 253, 130);
color: black;
font-weight: bold;
}
/* blockdiag */
img[src*="%3Ctitle%3Eblockdiag%3C"] {
background-color: lightgray;
}
/* image zoom */
img {
height: auto;
width: auto;
}
.zoom,
.zoom2 {
cursor: zoom-in;
transition: transform ease-in-out 0.5s;
}
.image-zoom {
background: #000;
box-shadow: 0 4px 8px 0 rgba(238, 238, 238, 0.5),
0 6px 20px 0 rgba(238, 238, 238, 0.5);
cursor: zoom-out;
position: absolute;+
transform: scale(1);
z-index: 100;
}
.image-zoom2 {
background: #000;
cursor: zoom-out;
box-shadow: 0 4px 8px 0 rgba(238, 238, 238, 0.5),
0 6px 20px 0 rgba(238, 238, 238, 0.5);
position: absolute;
transform: scale(1.5);
z-index: 100;
}
/* progress bar */
.progress-label {
font-weight: 700;
line-height: 1.2rem;
margin: 0;
overflow: hidden;
position: absolute;
text-align: center;
text-shadow: 0 0 6px #000000;
width: 100%;
white-space: nowrap;
}
.progress-bar {
background-color: #2979ff;
float: left;
height: 1.2rem;
}
.progress {
background-color: #cccccc8a;
display: block;
height: 1.2rem;
margin: 0.5rem 0;
position: relative;
width: 100%;
}
.progress.thin {
height: 0.4rem;
margin-top: 0.9rem;
}
.progress.thin .progress-label {
margin-top: -0.4rem;
}
.progress.thin .progress-bar {
height: 0.4rem;
}
.progress-100plus .progress-bar {
background-color: #00e676;
}
.progress-80plus .progress-bar {
background-color: #fbc02d;
}
.progress-60plus .progress-bar {
background-color: #ff9100;
}
.progress-40plus .progress-bar {
background-color: #ff5252;
}
.progress-20plus .progress-bar {
background-color: #ff1744;
}
.progress-0plus .progress-bar {
background-color: #f50057;
}
/* Synchronized Tabs */
const tabs = document.querySelectorAll('.tabbed-set > input')
for (const tab of tabs) {
tab.addEventListener('click', () => {
const current = document.querySelector(`label[for=${tab.id}]`)
const pos = current.getBoundingClientRect().top
const labelContent = current.innerHTML
const labels = document.querySelectorAll('.tabbed-set > label, .tabbed-alternate > .tabbed-labels > label')
for (const label of labels) {
if (label.innerHTML === labelContent) {
document.querySelector(`input[id=${label.getAttribute('for')}]`).checked = true
}
}
// Preserve scroll position
const delta = (current.getBoundingClientRect().top) - pos
window.scrollBy(0, delta)
})
}
/* make tables sortable */
document$.subscribe(function() {
var tables = document.querySelectorAll('article table:not([class])')
tables.forEach(function(table) {
new Tablesort(table)
})
})
/* MATHJax */
window.MathJax = {
tex: {
inlineMath: [['\\(', '\\)']],
displayMath: [['\\[', '\\]']],
processEscapes: true,
processEnvironments: true
},
options: {
ignoreHtmlClass: '.*',
processHtmlClass: 'arithmatex'
}
};
/* ZOOM */
document.querySelectorAll('.zoom').forEach(item => {
item.addEventListener('click', function () {
this.classList.toggle('image-zoom');
})
});
document.querySelectorAll('.zoom2').forEach(item => {
item.addEventListener('click', function () {
this.classList.toggle('image-zoom2');
})
});
This also contains a part needed for Math (see below).
Plugins and Extensions¶
And at last some plugins and extensions for more markdown possibilities like described below:
plugins:
- search
- awesome-pages
- include-markdown
- minify:
minify_html: true
htmlmin_opts:
remove_comments: true
markdown_extensions:
- extra
- toc:
permalink: true
- pymdownx.caret
- pymdownx.tilde
- pymdownx.mark
- admonition
- pymdownx.details
- pymdownx.highlight
- pymdownx.inlinehilite
- pymdownx.snippets
- pymdownx.superfences
- pymdownx.tabbed:
alternate_style: true
- pymdownx.betterem:
smart_enable: all
- pymdownx.emoji:
emoji_index: !!python/name:materialx.emoji.twemoji
emoji_generator: !!python/name:materialx.emoji.to_svg
- pymdownx.keys
- pymdownx.smartsymbols
- pymdownx.tasklist:
custom_checkbox: true
- markdown_blockdiag:
format: svg
- markdown_include.include
- pymdownx.arithmatex:
generic: true
- pymdownx.progressbar
If you use the pymdownx.keys
extension with the ++
syntax you can find all possible names. But if something is missing you can add it in the setup:
markdown_extensions:
- pymdownx.keys:
key_map:
{
"circumflex": "^",
"dollar": "$",
"percent": "%",
"parenthesis-left": "(",
"parenthesis-right": ")",
}
If VS Code with the Prettier plugin is used, set the tab width to 4 spaces for correct Markdown formatting in MkDocs.
{
"MD007": { "indent": 4 },
"MD013": false,
"MD030": false,
"MD036": false,
"MD041": false,
"MD046": false
}
See the complete setup of this book.
Add Tablesort¶
You need the following additions to make all tables sortable by clicking on the headers:
extra_javascript:
- https://cdnjs.cloudflare.com/ajax/libs/tablesort/5.2.1/tablesort.min.js
/* make tables sortable */
document$.subscribe(function() {
var tables = document.querySelectorAll("article table:not([class])")
tables.forEach(function(table) {
new Tablesort(table)
})
})
Allow Math¶
To enable math formulas, the following has to be added in mkdocs.yml
:
extra_javascript:
- https://polyfill.io/v3/polyfill.min.js?features=es6
- https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
plugins:
- pymdownx.arithmatex:
generic: true
The extra.js
file has to contain the mathjay section:
/* Synchronized Tabs */
const tabs = document.querySelectorAll('.tabbed-set > input')
for (const tab of tabs) {
tab.addEventListener('click', () => {
const current = document.querySelector(`label[for=${tab.id}]`)
const pos = current.getBoundingClientRect().top
const labelContent = current.innerHTML
const labels = document.querySelectorAll('.tabbed-set > label, .tabbed-alternate > .tabbed-labels > label')
for (const label of labels) {
if (label.innerHTML === labelContent) {
document.querySelector(`input[id=${label.getAttribute('for')}]`).checked = true
}
}
// Preserve scroll position
const delta = (current.getBoundingClientRect().top) - pos
window.scrollBy(0, delta)
})
}
/* make tables sortable */
document$.subscribe(function() {
var tables = document.querySelectorAll('article table:not([class])')
tables.forEach(function(table) {
new Tablesort(table)
})
})
/* MATHJax */
window.MathJax = {
tex: {
inlineMath: [['\\(', '\\)']],
displayMath: [['\\[', '\\]']],
processEscapes: true,
processEnvironments: true
},
options: {
ignoreHtmlClass: '.*',
processHtmlClass: 'arithmatex'
}
};
/* ZOOM */
document.querySelectorAll('.zoom').forEach(item => {
item.addEventListener('click', function () {
this.classList.toggle('image-zoom');
})
});
document.querySelectorAll('.zoom2').forEach(item => {
item.addEventListener('click', function () {
this.classList.toggle('image-zoom2');
})
});
Last Update Date¶
It is possible to automatically add the last update date below the page content. Therefore add the plugin in mkdocs.yml
:
pluǵins:
- git-revision-date-localized
If you use a build environment you have to setup it to don't only fetch the last commit:
- GitLab runners: set
GIT_DEPTH
to 0 - howto - GitHub actions: set
fetch_depth
to 0 - howto - Bitbucket pipelines:
set clone: depth: full
- howto
PDF and EPub¶
To create a PDF the following additions in mkdocs.yml
has to be made:
plugins:
- with-pdf:
cover_subtitle: Framework running powerful deep tests for standalone use or to enhance monitoring
cover_logo: https://assets.gitlab-static.net/uploads/-/system/project/avatar/12586261/images__1_.png
output_path: alinex-checkup.pdf
This will build the PDF, you can set more settings, see mkdocs-with-pdf.
An epub can be created using calibre, but the output is very ugly, so I won't do this at the moment.
ebook-convert site/$NAME.pdf site/$NAME.epub
In the setup below the documentation will be stored under
site/alinex-book.pdf
.
Writing Documentation¶
Pages are written in markdown Format and stored as *.md
files within the doc
folder. The Markdown implementation is nearly the same as used on GitHub but with some additions. See mkdocs markdown for a detailed writing guild with examples.