Template System
Attention 💥
Support since v0.2.14.
The template system allows you to create custom embed applications that aren’t supported by the built-in preprocessor. This powerful feature enables you to extend mdbook-embedify with your own interactive components and dynamic content.
Real world example 💡
You can take a looke of my notes book.
It supports caption and preview with much more complex behavior.
Dynamic vs Static Content
Custom templates act like dynamic reusable components. If you want to just copy static content, you should use the include app instead.
Creating a Custom App
Template Folder Setup
Templates must be placed in the assets/templates folder (relative to your book.toml
file).
Configure the template folder (optional):
[preprocessor.embedify]
custom-templates-folder = "assets/templates"
📍 Path Requirements:
The template folder path must be relative to the book root directory (where
book.toml
is located).
Template File Naming
The template filename becomes the app name:
Template File | App Name | Usage |
---|---|---|
canvas.html | canvas | {% embed canvas %} |
my-widget.html | my-widget | {% embed my-widget %} |
youtube.html | youtube | {% embed youtube %} (overrides built-in) |
⚠️ Override Behavior:
If your custom app name matches a built-in app, the custom template will override the built-in functionality.
Example: Creating a Canvas App
Let’s create a canvas app - a simple drawable canvas component.
Step 1: Create the template file (assets/templates/canvas.html
)
Basic HTML structure with styling:
<div class="canvas-container">
<canvas height="400"></canvas>
</div>
<style>
.canvas-container {
width: 100%;
background: white;
border-radius: 1rem;
border: 1px solid #ccc;
background-size: 20px 20px;
background-image: linear-gradient(to right, #eee 1px, transparent 1px),
linear-gradient(to bottom, #eee 1px, transparent 1px);
}
</style>
Add interactive JavaScript:
<script>
document.addEventListener("DOMContentLoaded", () => {
const container = document.querySelector(".canvas-container");
const canvas = container.querySelector("canvas");
const ctx = canvas.getContext("2d");
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) canvas.width = entry.contentRect.width;
});
resizeObserver.observe(container);
let drawing = false;
const lastPos = { x: 0, y: 0 };
// Draw a line from last position to current position
function draw(x, y) {
ctx.beginPath();
ctx.moveTo(lastPos.x, lastPos.y);
ctx.lineTo(x, y);
ctx.stroke();
lastPos.x = x;
lastPos.y = y;
}
// Mouse events
canvas.addEventListener("mousedown", (e) => {
drawing = true;
lastPos.x = e.offsetX;
lastPos.y = e.offsetY;
});
canvas.addEventListener("mousemove", (e) => {
if (!drawing) return;
draw(e.offsetX, e.offsetY);
});
canvas.addEventListener("mouseup", () => (drawing = false));
canvas.addEventListener("mouseout", () => (drawing = false));
// Touch events
canvas.addEventListener("touchstart", (e) => {
if (e.touches.length !== 1) return;
e.preventDefault();
drawing = true;
const rect = canvas.getBoundingClientRect();
lastPos.x = e.touches[0].clientX - rect.left;
lastPos.y = e.touches[0].clientY - rect.top;
});
canvas.addEventListener("touchmove", (e) => {
if (!drawing || e.touches.length !== 1) return;
e.preventDefault();
const rect = canvas.getBoundingClientRect();
const x = e.touches[0].clientX - rect.left;
const y = e.touches[0].clientY - rect.top;
draw(x, y);
});
canvas.addEventListener("touchend", () => (drawing = false));
canvas.addEventListener("touchcancel", () => (drawing = false));
});
</script>
CSS & JavaScript Integration 💡
You can include CSS and JavaScript directly in template files using
<style>
and<script>
blocks.
Step 2: Add dynamic height support
Make the canvas height configurable using placeholder syntax:
<canvas height="{% height=400 %}"></canvas>
This allows users to customize the height: {% embed canvas height=600 %}
Placeholder Syntax
The template system supports two types of dynamic content:
Syntax Types
Type | Syntax | Purpose | Example |
---|---|---|---|
Placeholder | {% key=default %} | Variable substitution | {% height=400 %} |
Processor | {% processor(key=default) %} | Content transformation | {% markdown(content) %} |
Placeholder Examples
Syntax | Behavior | Use Case |
---|---|---|
{% height %} | Required - User must provide value | Mandatory configuration |
{% height=400 %} | Optional - Uses default if not provided | Optional configuration |
{% markdown(message) %} | Processed - Content transformed by processor | Dynamic content rendering |
Available Processors
Processor | Purpose | Input | Output |
---|---|---|---|
markdown | Renders markdown to HTML | Markdown text | HTML content |
Example usage:
<div class="content">{% markdown(description="Description in **markdown**") %}</div>
Complete Canvas Template
Here’s the complete template file for our canvas app:
<div class="canvas-container">
<canvas height="{% height=400 %}"></canvas>
</div>
<style>
.canvas-container {
width: 100%;
background: white;
border-radius: 1rem;
border: 1px solid #ccc;
background-size: 20px 20px;
background-image: linear-gradient(to right, #eee 1px, transparent 1px),
linear-gradient(to bottom, #eee 1px, transparent 1px);
}
</style>
<script>
document.addEventListener("DOMContentLoaded", () => {
const container = document.querySelector(".canvas-container");
const canvas = container.querySelector("canvas");
const ctx = canvas.getContext("2d");
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) canvas.width = entry.contentRect.width;
});
resizeObserver.observe(container);
let drawing = false;
const lastPos = { x: 0, y: 0 };
// Draw a line from last position to current position
function draw(x, y) {
ctx.beginPath();
ctx.moveTo(lastPos.x, lastPos.y);
ctx.lineTo(x, y);
ctx.stroke();
lastPos.x = x;
lastPos.y = y;
}
// Mouse events
canvas.addEventListener("mousedown", (e) => {
drawing = true;
lastPos.x = e.offsetX;
lastPos.y = e.offsetY;
});
canvas.addEventListener("mousemove", (e) => {
if (!drawing) return;
draw(e.offsetX, e.offsetY);
});
canvas.addEventListener("mouseup", () => (drawing = false));
canvas.addEventListener("mouseout", () => (drawing = false));
// Touch events
canvas.addEventListener("touchstart", (e) => {
if (e.touches.length !== 1) return;
e.preventDefault();
drawing = true;
const rect = canvas.getBoundingClientRect();
lastPos.x = e.touches[0].clientX - rect.left;
lastPos.y = e.touches[0].clientY - rect.top;
});
canvas.addEventListener("touchmove", (e) => {
if (!drawing || e.touches.length !== 1) return;
e.preventDefault();
const rect = canvas.getBoundingClientRect();
const x = e.touches[0].clientX - rect.left;
const y = e.touches[0].clientY - rect.top;
draw(x, y);
});
canvas.addEventListener("touchend", () => (drawing = false));
canvas.addEventListener("touchcancel", () => (drawing = false));
});
</script>
Using Your Custom App
Basic Usage
After creating the template file, use your app in your book:
{% embed canvas height=400 %}
With Default Values
Since height has a default value of 400, you can omit it:
{% embed canvas %}
Interactive Example
Test the canvas app by drawing on it:
Contributing Templates
Want to share your custom templates with the community?
For Personal Use
- Keep templates in your
assets/templates/
folder - Customize as needed for your specific use case
For Community Contribution
- Fork the mdbook-embedify repository
- Add your template to src/assets/templates
- Submit a pull request with documentation
- Include examples and usage instructions