Diving deep
source: Theme Development
Every new mkdocs project (which will ofcourse be a website) in our workflow will involve two directories:
vector@ArchLinuxVS ~ $ tree -L 1 /home/vector/development/mkdocs
/home/vector/development/mkdocs
├── mkdocs-material
└── site
...where mkdocs-material
is the result of git clone https://github.com/squidfunk/mkdocs-material
and site is the site we made using mkdocs new site
.
cd mkdocs-material
python -m venv venv
source venv/bin/activate
pip install mkdocs-material
pip install -e ".[recommended]"
pip install nodeenv
nodeenv -p -n lts
npm install
Now I edited mkdocs/mkdocs-material/tools/build/_/index.ts
so that any modication made to templates in src directory will lead to a recompiling and output produced in mkdocs/mkdocs-material/material/templates
.
import * as chokidar from "chokidar"
import * as fs from "fs/promises"
import * as path from "path"
import {
EMPTY,
Observable,
filter,
from,
fromEvent,
identity,
catchError,
defer,
map,
mergeWith,
of,
switchMap,
tap
} from "rxjs"
import glob from "tiny-glob"
/* ----------------------------------------------------------------------------
* Helper types
* ------------------------------------------------------------------------- */
/**
* Resolve options
*/
interface ResolveOptions {
cwd: string /* Working directory */
watch?: boolean /* Watch mode */
dot?: boolean /* Hidden files or directories */
}
/**
* Watch options
*/
interface WatchOptions {
cwd: string /* Working directory */
}
/* ----------------------------------------------------------------------------
* Data
* ------------------------------------------------------------------------- */
/**
* Base directory for compiled files
*/
export const base = "material"
/**
* Cache to omit redundant writes
*/
export const cache = new Map<string, string>()
/* ----------------------------------------------------------------------------
* Helper Ffunctions
* ------------------------------------------------------------------------- */
/**
* Return the current time
*
* @returns Time
*/
function now() {
const date = new Date()
return [
`${date.getHours()}`.padStart(2, "0"),
`${date.getMinutes()}`.padStart(2, "0"),
`${date.getSeconds()}`.padStart(2, "0")
]
.join(":")
}
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Resolve a pattern
*
* @param pattern - Pattern
* @param options - Options
*
* @returns File observable
*/
export function resolve(
pattern: string, options?: ResolveOptions
): Observable<string> {
return from(glob(pattern, { dot: true, ...options }))
.pipe(
catchError(() => EMPTY),
switchMap(files => from(files).pipe(
/* Start file watcher */
options?.watch
? mergeWith(watch(files, options))
: identity
)),
/* Build overrides */
!process.argv.includes("--all")
? filter(file => !file.startsWith(`.overrides${path.sep}`))
: identity,
)
}
/**
* Watch all given files
*
* @param files - Files
* @param options - Options
*
* @returns File observable
*/
// export function watch(
// files: string[], options: WatchOptions
// ): Observable<string> {
// return fromEvent(
// chokidar.watch(files, options),
// "change", file => file // see https://t.ly/dli_k
// ) as Observable<string>
// }
export function watch(
patterns: string[], options: WatchOptions
): Observable<string> {
// If the patterns look like file paths (no wildcards),
// watch the containing directories instead
const watchTargets = patterns.map(p => {
if (!p.includes('*') && !p.includes('?')) {
// Get the directory of the file
return path.dirname(p);
}
return p;
});
return fromEvent(
chokidar.watch(watchTargets, {
...options,
ignoreInitial: true // Don't emit events for initial scan
}),
"all", // Watch all events, not just "change"
(eventName, file) => file // Return the file path
).pipe(
filter(file => patterns.some(pattern => {
// If the pattern is a direct file path, match exactly
if (!pattern.includes('*') && !pattern.includes('?')) {
return file === pattern;
}
// Otherwise use a simple glob matcher
return file.endsWith(path.extname(pattern));
}))
) as Observable<string>;
}
/* ------------------------------------------------------------------------- */
/**
* Recursively create the given directory
*
* @param directory - Directory
*
* @returns Directory observable
*/
export function mkdir(directory: string): Observable<string> {
return defer(() => fs.mkdir(directory, { recursive: true }))
.pipe(
map(() => directory)
)
}
/**
* Read a file
*
* @param file - File
*
* @returns File data observable
*/
export function read(file: string): Observable<string> {
return defer(() => fs.readFile(file, "utf8"))
}
/**
* Write a file, but only if the contents changed
*
* @param file - File
* @param data - File data
*
* @returns File observable
*/
export function write(file: string, data: string): Observable<string> {
let contents = cache.get(file)
if (contents === data) {
return of(file)
} else {
cache.set(file, data)
return defer(() => fs.writeFile(file, data))
.pipe(
map(() => file),
process.argv.includes("--verbose")
? tap(file => console.log(`${now()} + ${file}`))
: identity
)
}
}
Now do:
The template wills will be generated in mkdocs-material/material/templates
folder.
templates $ ls
404.html base.html blog-post.html __init__.py mkdocs_theme.yml __pycache__
assets blog.html fragments main.html partials redirect.html
Take note of the overrides
directory in src
.
The contents of this directory shows a sampe site implementation using mkdocs material.
```vector@ArchLinuxVS ~/development/mkdocs/mkdocs-material/src $ tree overrides overrides ├── assets │ ├── javascripts │ │ ├── components │ │ │ ├── _ │ │ │ │ └── index.ts │ │ │ ├── iconsearch │ │ │ │ ├── _ │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── query │ │ │ │ │ └── index.ts │ │ │ │ └── result │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ └── sponsorship │ │ │ └── index.ts │ │ ├── custom.ts │ │ ├── integrations │ │ │ ├── analytics │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ └── templates │ │ ├── iconsearch │ │ │ └── index.tsx │ │ ├── index.ts │ │ └── sponsorship │ │ └── index.tsx │ └── stylesheets │ ├── custom │ │ ├── layout │ │ │ ├── _banner.scss │ │ │ ├── _hero.scss │ │ │ ├── _iconsearch.scss │ │ │ └── _sponsorship.scss │ │ └── _typeset.scss │ └── custom.scss ├── home.html ├── hooks │ ├── shortcodes.py │ ├── translations.html │ └── translations.py └── main.html
cd site ln -s /home/vector/development/mkdocs/mkdocs-material/material/templates /home/vector/development/mkdocs/sreema/overridesIts probably not best practice to use the templates folder like this. But I'm at the point where I value control and I wannt see for myself if I can make really customized sites with mkdocs.
Now let's develop the site.