Reusing Contents
User Guide → Reusing ContentsMarkBind is highly-optimized for content reuse. It offers several mechanisms to provide readers with many variations of the content while minimizing duplication at source file level. As a result, instead of creating a one-size-fits-all site, MarkBind can create a site in which readers can chart their own path of reading.
Nunjucks variables are ideal for reusing small bits of code in multiple places; you can define a variable to represent the code bit in question and reuse it anywhere in the site by referring to the variable instead of duplicating the code bit.
MarkBind does not aim to alter the already robust variable features of Nunjucks, but provides several extensions to it.
Global variables are to be defined in the _markbind/variables.md
file. Each variable must have an name
and the value can be any MarkBind-compliant code fragment. The name
should not contain -
and .
. For example, search-option
and search.options
are not allowed.
The variables declared here are available from anywhere in the code base.
Example Here's how you can define two variables year
and options
:
<variable name="year">2018</variable>
<variable name="options">
* yes
* no
* maybe
</variable>
To include a variable value in your code, give the variable id enclosed in Nunjucks' double curly braces syntax.
Example The year was {{ year }}.
The year was 2018.
Global variables (_markbind/variables.md
) will take precedence over any variables set via Nunjucks' tags (e.g. {% set %}
).
MarkBind also provides a number of built-in variables.
Variable | Notes | Example | Output |
---|---|---|---|
baseUrl | Represents the root directory of the site on the server, as configured in your site configuration file. Used for specifying intra-site links. | If baseUrl is specified as userGuide/ :<img src="{{baseUrl}}/images/logo.png" /> | <img src="userGuide/images/logo.png" /> |
timestamp | The time stamp that indicates when the page was generated. The default values of "timeZone" and "locale" are "UTC" and "en-GB" respectively. | The following example showcases the use of the "Asia/Singapore" time zone.Page generated at: {{timestamp}} | Page generated at: Sat, 7 Sept 2024, 14:39:19 GMT+8 |
MarkBind | The MarkBind version in use, linked to the MarkBind website. | Page generated by: {{MarkBind}} | Page generated by: MarkBind 5.5.3 |
You can also source variables from external files using MarkBind's {% ext varName = "filepathToFile" %}
Nunjucks extension.
This is useful if you have external datasets you want to display in your site!
To do so, assign a root variable name (varName
) to the file path from the . You may then access the file's variables using dot varName.xx
or array varName[i]
syntax, depending on the file's contents.
Example
CODE:
{% ext studentScoreboard = "userGuide/syntax/extra/scoreboard.json" %}
Student Number | Score | Rank
:----- | :-------: | ----
{% for student in studentScoreboard.students -%}
{{ student.number }} | {{ student.score }} | {{ student.rank }}
{% endfor %}
<small>Last updated at {{ studentScoreboard.lastUpdated }}</small>
JSON file used in example
OUTPUT:
Student Number | Score | Rank |
---|---|---|
A1234567X | 87 / 100 | 1 |
A1234123U | 60 / 100 | 3 |
A9876543L | 76 / 100 | 2 |
Last updated at 21 November, 2020
Example
CODE:
{% ext studentScoreboard = "userGuide/syntax/extra/scoreboard.csv" %}
Student Number | Score | Rank
:----- | :-------: | ----
{% for student in studentScoreboard -%}
{{ student.number }} | {{ student.score }} | {{ student.rank }}
{% endfor %}
CSV file used in example
If you do not want to have a header row, you can specify it by appending a noHeader
option at the end of the variable declaration. In this example, it should be {% ext studentScoreboard = "userGuide/syntax/extra/scoreboard.csv", noHeader %}
. Elements have to be accessed using the []
operator (i.e. using student[0]
to access student number instead of student.number
).
OUTPUT:
Student Number | Score | Rank |
---|---|---|
A1234567X | 87 / 100 | 1 |
A1234123U | 60 / 100 | 3 |
A9876543L | 76 / 100 | 2 |
Only .json
and .csv
files are supported for now.
Place global variables in `_markbind/variables.md`
<variable name="year">2018</variable>
The year was {{ year }}
MarkBind has a powerful <include>
mechanism which allows you to create documents by combining other content fragments.
You can use <include>
tag to include another markdown or HTML document into the current document.
Example Including text from a tip2.md
in another file.
Tip 1. ...
<include src="tips/tip2.md" />
Tip 3. ...
You can <include>
a fragment of a file by specifying the #fragment-id
at the end of the src
attribute value, provided the fragment is wrapped in a <div>
/<span>
tag with the matching id
.
Choose <div>
over <span>
when wrapping block-level elements, to prevent invalid HTML markup which causes hydration issues.
Example Including a fragment from a file:
Some text
<include src="docs/tips.md#tip-1" />
Some other text
docs/tips.md
:
...
<div id="tip-1" />
Tip 1. ...
...
</div>
Tip 2. ...
When setting the id
of a fragment, be careful not to clash with heading anchor IDs auto-generated by MarkBind. For example, if you have a heading ## Some Useful Tips
, MarkBind will auto-generate an ID some-useful-tips
for that heading.
Importing external resources that contains script
or styles
can inadvertently take global effects on your MarkBind website. Due to hoisting during processing, imported scripts and stylesheets affect the entire page. This could potentially alter its appearance and behavior beyond the intended scope.
For example, if a CSS file imported via such means styles headings to be red, this change will be reflected page-wide.
To safeguard against unintended consequences, consider directly incorporating the code or customizing styles to target specific elements or classes not used universally. This approach grants more precise control over your website's presentation and reduces the risk of unexpected changes.
Add {{ baseUrl }}
to make your URLs absolute links if they may be reused in different contexts
Make an internal relative link an absolute link by adding {{ baseUrl }}
in front of the path. This allows the link to always point to the same target. Keep this in mind when putting content with links that is reused (eg: via <include>
). This is because when your content is re-used, a relative link may no longer point to where you want it to.
The <include>
mechanism can be used inside any MarkBind source file (even inside the frontmatter section) but it will not work inside some special files such as the _markbind/variables.md
.
Attributes:
src
: specify the source file path.inline
(optional): make the included result an inline element. (wrapped in <span>
tag). e.g.,The title is <include src="../docs/summary.md#title" inline /> while ...
optional
(optional): include the file/fragment only if it exists i.e., there will be no error message if the file/fragment does not exist. e.g.,<include src="UserStories.md" optional />
trim
(optional): remove leading and trailing whitespace and newlines from the document before including.<include src="UserStories.md#epic" trim />
omitFrontmatter
(optional): omit the frontmatter of the file/fragment from being included (if any).<include src="UserStories.md#epic" omitFrontmatter />
<include>
Inside an Included FileAlthough the src
attribute of an <include>
is given relative to the current directory, it is converted to an absolute value before the is included from another file.
Example Suppose you have a MarkBind project with the following file structure.
The book.md
:
# My Book
<include src="chapter1.md" />
<include src="chapter2.md" />
The review.md
:
# My Review
<include src="../bookFiles/book.md" />
...
The content of the chapter1.md
and chapter2.md
will be included in the review.md
(via <include src="../bookFiles/book.md" />
) although chapter1.md
and chapter2.md
are not in reviewFiles
directory. i.e., <include src="chapter1.md" />
will be interpreted as <include src="c:/mySite/bookFiles/chapter1.md" />
In other words, <include>
interprets the reused code relative to the original location of the file, not the location in which it is reused.
<include>
It is possible to include variables in an <include>
.
Example Specifying title
and author
variables in an <include>
tag:
<include src="article.md">
<variable name="title">My Title</variable>
<variable name="author">John Doe</variable>
</include>
In article.md
:
# {{ title }}<br>
Author: {{ author }}
These variables work the same way as variables in _markbind/variables.md
, except that they only apply to the included file. They allow the included file to be reused as a template, for different source files using different variable values.
You can also specify include variables within the <include>
tag itself by adding a var-
prefix.
Example Specifying title
and author
variables inline:
<include src="article.md" var-title="My Title" var-author="John Doe" />
If the same variable is defined in a chain of <include>
s (e.g. a.md
includes b.md
includes c.md
...), variables defined in the top-most <include>
will take precedence. Global variables (_markbind/variables.md
) will take precedence over any <include>
variables.
This is to allow the outer context to adapt the reused content without changing its actual content.
MarkBind supports the exclusion of files from page generation. For example, you can exclude files containing that are only meant to be used in <include>
.
Note: This example below is assuming that you have included the following glob pattern in the site.json
file:
{
"pages": [
{
"glob": "*.md",
"layout": "normal",
"searchable": "yes"
}
],
}
Else, if each page is included individually, there is no need to exclude the fragments as they will not be included in the page generation.
Example Suppose you have a fragment file content-fragment.md
and you want to include it in some pages of the site course
without rendering content-fragment.md
as a page.
In reading.md
(note how it reuses content from the content-fragment.md
):
# Week 1 Reading:
<include src="content-fragment.md" />
In site.json
we then exclude the fragment from the page generation with pagesExclude:
...
"pagesExclude": [
"**/*-fragment.md"
],
...
You may use any custom name you wish for your fragments but be sure to update the pagesExclude
list with the appropriate glob pattern.
If you find yourself duplicating a fragment in multiple places of your code base, you can use a boilerplate
file to avoid such duplication. Note that you cannot use a normal <include>
in this case because the code included using a normal <include>
stays relative to the original location while boilerplate code needs to be interpreted relative to the location it is being used.
Example Suppose you have a MarkBind project with the following file structure.
The book.md
:
# My Book
<include src="chapter1/chapter.md" />
<include src="chapter2/chapter.md" />
The chapter1/chapter.md
:
## Text
<include src="text.md" />
## Exercises
<include src="exercises.md" />
The chapter2/chapter.md
:
## Text
<include src="text.md" />
## Exercises
<include src="exercises.md" />
As you can see, both chapter.md
files are exactly the same. If we were to use only one of the chapter.md
files and <include>
it twice in the book.md
, we'll end up with the same chapter content duplicated twice, which is not what we want. In other words, chapter.md
contains boilerplate code that needs to be interpreted relative to where it is applied, once relative to chapter1
directory and once relative to chapter2
directory.
To use a code fragment as a boilerplate file,
_markbind/boilerplates
directory.<include>
the file as if a copy of it exists in any directory you want it to applied, but add the boilerplate
attribute to the <include>
tag.Example Here's how you can use a boilerplate file to avoid duplicating the chapter.md
:
The book.md
:
# My Book
<include src="chapter1/chapter.md" boilerplate />
<include src="chapter2/chapter.md" boilerplate />
The _markbind/boilerplates/chapter.md
:
## Text
<include src="text.md" />
## Exercises
<include src="exercises.md" />
Consider the line <include src="chapter1/chapter.md" boilerplate />
. Note how you can use src="chapter1/chapter.md"
even though there is no such file. MarkBind will use the chapter.md
file from /_markbind/boilerplates/
but interpret it as if the file exists in the chapter1
directory (i.e., interpret the chapter.md
code relative to the chapter1
directory).
Similarly, <include src="chapter2/chapter.md" boilerplate />
interprets the chapter.md
relative to the chapter2
directory.
If you have many boilerplate files, you can organize them into directories inside the _markbind
directory. When using such boilerplate files, you need to replace boilerplate
attribute with boilerplate="<path to file relative to _markbind/boilerplates>"
.
Example Suppose the chapter.md
is places in a book
directory:
It needs to be used as follows:
<include src="chapter1/chapter.md" boilerplate="book/chapter.md" />
<include src="chapter2/chapter.md" boilerplate="book/chapter.md" />
<include src="foo.md#bar" boilerplate inline trim>
<variable name="x">5</variable>
</include>
MarkBind supports reusing across sites. It allows you to include the pages you want from a sub-site in another main-site without having to change anything in the source files of the sub-site as long as the sub-site source files are inside the directory of the main-site.
Example Suppose you have a site textbook
and you want to include some pages from it in another site course
. Given below is how you can locate the sub-site textbook
inside the root directory of the main-site course
so that files from textbook
can be reused in the course
site.
In reading.md
(note how it reuses content from the sub-site textbook
):
# Week 1 Reading:
<include src="textbook/overview.md" />
If you are using Git for version control, you can set up the sub-site repository as a Git sub-module of the main site repository.
MarkBind can create sites that give more control to the reader. Given below are some mechanisms authors can use to create variations of content that gives more control to the reader in charting their own path through the content.
When the readers can remove an item from a page, they can create their own version of the page by removing items they don't want to see. This is especially useful when printing a page.
To make an element closeable, use v-closeable
.
<div v-closeable>
Optional video:
@[youtube](v40b3ExbM0c)
</div>
This is how the content will appear. Note how you can hover over the content to access the ❌ button that can collapse the content.
Optional video:
You can use a Tabs component to give alternative versions of content, for example, giving a code snippet in different programming languages.
You can use following components to give readers an option to access additional content at their discretion.
You can take advantage of MarkBind's feature for content reuse to organize content in alternative ways to cater for different readers, without having to duplicate content. For example, you can have different pages that organizes the same information alphabetically, chronologically, by difficulty, group information by topic, etc.
To hide minimized panels in the print view, add the following code to a CSS file used in your site.
@media print {
.card-container > .morph {
display: none;
}
}
MarkBind also comes with some built-in optimizations for printing by default:
To permanently hide a fragment from the reader:
<span class="d-none">
content to hide ...
</span>
<panel header="..." add-class="d-none">
content to hide ...
<panel>
To hide a fragment in one specific page, 'mark' the elements using a class
:
<span class="extra">
content to hide ...
</span>
Then, in a page-specific CSS file,
.extra {
display: none; /* 'block' or 'inline-block' if you want it to show */
}
By overriding the title
declared in the frontmatter of the page using site.json
, it is possible to allow MarkBind to serve the same page with different titles.
This may especially be useful for users who are serving a page from a submodule.
Tags are a good way to create multiple variations of a page within the same source file, such as to filter content for creating multiple different versions of the same page. See User Guide: Tweaking the Page Structure → Tags section for more information.