A guide to writing page content for Publ.
Overall format
Publ entries are files saved as .md
or .html
in your content directory. An
entry consists of three parts: Headers, above-the-fold, and below-the-fold (also
known as a “cut”).
Here is what an entry might look like:
Headers
Headers follow the RFC 2822 email message format. They are, more or less, a series of lines like:
followed by a blank line. Long values can also be split on multiple lines by using a hanging indent:
You can define whatever headers you want for your templates, and can also have multiple headers of the same name. For example:
Header names are case-insensitive (so, for example, my-header
, My-header
,
and My-HEADER
all mean the same thing).
When using these headers from a template, they appear as part of their entry object, and can be accessed using the get
mechanism.
Publ-defined headers
Publ defines a number of headers for its own use. They are as follows:
Title
: The display title of the entryDefault value: None
Sort-Title
: The sorting title of the entryThis affects the title for the purpose of sorting, but is otherwise unused. If none is given it defaults to the entry’s display title.
Entry-ID
: The numerical ID of the entryThis must be unique across all entries and will be automatically assigned if missing. It must also be just a number.
Entry IDs also provide a convenient linking mechanism; this entry has ID of 322 so a link to /322 or 322 works fine regardless of where the entry gets moved to in the future.
Date
: The publication date and timeThis can be in any format that Arrow understands. If no timezone is specified it will use the timezone indicated in
config.py
.Default value: the modification time of the entry file (which will be added to the file for later).
If you set this to a non-date value (e.g.
now
) then it will be replaced with the file modification time when the file is scanned, as if the header were omitted.Category
: Which category to put this entry inDefault value: the entry file’s directory
Status
: The publish status of the entryAllowed values:
DRAFT
: This entry is not visible at allHIDDEN
: This entry is visible when accessed directly, but will not be included in entry listings or in previous/next linksUNLISTED
: A synonym forHIDDEN
PUBLISHED
: This entry is visible at all timesSCHEDULED
: Until the publication date, this acts asHIDDEN
; afterwards, it acts asPUBLISHED
GONE
: The entry has been deleted and will not be coming back; attempts to access this entry will result in an HTTP 410 GONE error.DELETED
: A synonym forGONE
Default value:
SCHEDULED
Slug-Text
: The human-readable part of the URLIn some circles this is known as “SEO text.”
Default value: the entry title
Redirect-To
: A URL to redirect this entry toThis is useful if you want to remove an entry and redirect to another entry, or if you want an entry to be a placeholder for some external content (e.g. when the entry was syndicated from an external source).
Path-Alias
: An alternate path to this entryThis is useful for providing easy-to-remember short names for an entry, and for redirecting old, non-Publ URLs to this entry. For example, if you’re migrating from a legacy site and you have a URL like
http://example.com/blog/0012345.php
you can set a header like:Any number of these may be added to any given URL.
For example, this entry has a
Path-Alias
of/entry-format
, and the template format page can be similarly reached at/template-format
.Note: A path-alias will never override another entry at its canonical URL; however, it can potentially override any other kind of URL, including URLs for category views and non-canonical entry URLs.
You can also optionally specify a category template to use, by writing it after the aliased path:
which will cause the legacy URL to remap to showing this view within the category using the
archive
template. If you would like to just go to the category without any specific template, useindex
.The path portion is URL-encoded; for example:
Typically, only whitespace and percent signs must be URL-encoded.
Path-Mount
: An alternate path to this entryThis is similar to
Path-Alias
, except that the browser will not be redirected to the canonical location; for example, if you have an entry like:then both
/blog/12345-test
and/faq
will render as the entry directly.Also, unlike
Path-Alias
, if a template is specified, this will be treated as an entry template (i.e. there will be anentry
object and noview
object).Path-Canonical
: Specify the canonical path to this entryDefault value:
/{category}/{entry id}-{slug text}
(for example,/articles/general/1924-this-is-a-test
)For example, if there’s an entry with:
then any access to
/blog/12345-test
,/blog/12345
,/12345
,/faq.html
, etc. will be redirected to/faq
. However,Path-Mount
paths (/faque
in the above example) will still be a non-redirecting alias.As with
Path-Mount
, if a template is specified then this will be treated as an entry template.If you specify more than one
Path-Canonical
header, all but one of them will be treated asPath-Mount
instead; it is undefined as to which one becomes the canonical path.UUID
: A globally-unique identifier for the entryWhile Publ doesn’t use this internally, having this sort of generated ID is useful for Atom feeds and the like. As such, a UUID will be automatically generated if not present.
It is highly recommended (but not technically required) that this be unique to every entry, including between different websites.
Entry-Type
: An arbitrary string which you can use to define an entry typeThis allows you to differentiate entry types however you want; with this you can, for example, set up something similar to what WordPress and Tumblr call “page”-type content, or use this to manage entries within a navigation sidebar or a linkroll or the like.
Note that this is intended for affecting the layout/structure of the site, and each entry only has a single type. If you set more than one, only one of them will be used (and which one is undefined). For making content that can be filtered on multiple criteria, use tags instead.
Entry-Template
: Use the specified template instead ofentry
when rendering this entryLast-Modified
: The date to use for the last-modified time for this entry.Like with
Date
, if you set this to a non-date value (e.g.now
) then it will be replaced with the file modification time when the file is scanned.Default value: the entry’s
Date
Tag
: Add the specified tag to the entry. To add more than one tag, use separateTag:
headers.Summary
: A plain-text summary/description of the entry. Markdown is supported.Default value: The first paragraph of text in the entry body.
Auth
: A list of permissions for who can and cannot see the entry.This is a list of identities or groups, separated by spaces. Identities/groups which start with a
!
means that they cannot access the entry. For example, this line:means that members of
friends
can access the entry, but noterik@example.com
(even if they are a member offriends
).This list can grow arbitrarily long, and the rightmost rule wins.
There is also a special access group,
*
, which just refers to anyone who is logged in; for example:will be visible to anyone who is logged in, and
will only be visible to anyone who is not logged in. These rules can also stack; for example:
will be visible to anyone who’s logged in except for members of the
enemies
group, butmailto:bob@example.com
will be allowed even if they are inenemies
. This is one way that you can make an entry which is open to everyone except people who have been blocked, for example.Note that identities won’t necessarily be an email address; they are only listed as such here for illustrative purposes. For example, a Mastodon user will appear as e.g.
https://plush.city/@fluffy
. See the user configuration file and admin guide for more information.Note: Only a single
Auth:
header is supported. If more than one is present, only one will be used, and it is undefined as to which one that is. If you want nicer formatting on long per-entry access lists, you can format it using hanging indents:Attach
: Another entry to “attach” to this one, useful for defining arbitrary content sections or the like. This can be by file path or by entry ID.This is to be used with the
entry.attachments
andentry.attached
template functions, as well as the related view parameters
Entry content
After the headers, you can have entry content; if the file has a .htm
or .html
extension it will just render directly as HTML (with internal href
and src
links rewritten to local files and entries), but with a .md
extension it will
render as Markdown.
Publ supports a number of extensions to Markdown, specifically via Misaka (which in turn uses Hoedown). The extensions are configurable; by default it is configured to resemble GitHub-flavored markdown with the addition of Kramdown-style math (which in turn works with MathJax or KaTeX).
Markdown entries can use code fences, which support syntax highlighting via Pygments; see its rather large list of syntaxes. There are Publ-specific extensions to this.
There are also some Publ-specific extensions for things like cuts, image renditions, and galleries.
Custom tags
.....
: Indicates the cut from above-the-fold to below-the-fold content (must be on a line by itself)
Images
Publ extends the standard Markdown image tag (![](imageFile)
) syntax with some added features for
generating display-resolution-independent renditions and Lightbox galleries. The syntax is based on the standard Markdown for an image, which is:
(where alt text
and "title text"
are optional), but it adds a few features:
Multiple images can be specified in the image list, separated by
|
characters; for example:Images can be configured by adding
{arguments}
to the filename portion or to the alt text; for example:
You can also specify the width and height as the first one or two unnamed arguments to an argument list; for example, these two invocations are equivalent:
For the shorthand notation, if you want to specify only height you can use None
for the width, e.g.:
For a full list of the configurations available, please see the manual entry on image renditions.
If the image path is absolute (i.e. starts with a /
) it will search for the image within the content directory. Otherwise it will search in the following order:
- Relative to the entry file
- Relative to the entry category in the content directory
So, for example, if content directory is content/entries
and your entry is in
content/entries/photos/my vacation/vacation.md
but indicates a category of photos
,
and you have your static directory set to content/static
,
an image called DSC_12345.jpg
will be looked up in the following order:
content/entries/photos/my vacation/DSC_12345.jpg
content/entries/photos/DSC_12345.jpg
Relative paths will also be interpreted, including ../
parent directory paths.
This approach allows for greater flexibility in how you manage your images; the simple case is that the files simply live in the same directory as the entry file, and in more complex cases things work in a hopefully-intuitive manner.
Of course, external absolute URLs (e.g. http://example.com/image.jpg
or //example.com/protocol-relative.gif
) are still allowed, although they are
more limited in what you can do with them (for example, scaling will be done client-side and cropping
options will not work). Also, keep in mind that URLs that are not under your control may change without notice.
This functionality is also available for links to images; for example:
In this variant you can also specify the image attributes:
Note that these links to images do not inherit the default image arguments from the page template (this is by design). And, of course, only a single image is supported.
Image sets
To support image sets, the following options can be added to the alt text
section to wrap the image(s) in a <div>
:
div_class
: Sets theclass
attribute on the containing<div>
div_style
: A string or list of strings which are added to the containing<div>
’s<style>
element
For example, this Markdown fragment:
will produce the equivalent of the following HTML:
HTML tags
HTML <img>
tags will use their width
and height
attributes to determine the image rendition size. For example:
is equivalent to:
You can also use most of the Markdown image rendition flags, for example:
This <img>
functionality is available on both HTML and Markdown entries.
Linking to things
Both Markdown and HTML entries support a number of enhancements to how link targets are handled; this allows the transparent use of local file paths in your entries.
In HTML tags, this applies to all href
and src
attributes (e.g. <a href="example.md">link</a>
and <audio src="example.mp3" controls>
).
In Markdown entries, this also applies to images (e.g. ![](example.jpg)
) and hyperlinks (e.g. [example link](example.md)
) in addition to applying to embedded HTML content.
In HTML tags, you can also force this to apply to other attributes by prefixing them with a $, which is useful for supporting various JavaScript libraries and the like. For example:
Local files
Entries can link to files that are stored within the content directory, using the same relative path rules as images. For example, if you have
a file term paper.pdf
in the same directory as my entry.md
, then from the entry you can link to it with:
This also works in HTML links; for example:
Any file type is supported; however, keep in mind that an HTML or Markdown file will be interpreted as an entry. If you would like to serve one up as just their plain content, you can give it headers like:
and create a templates/_plain.html
file that is simply:
Static assets
Starting a link target with @
acts as a shorthand for linking to a file in the static assets directory. For example,
will link to the file files/file.txt
within the static files for the site. This is more portable than linking
to the static files directly, e.g. /static/files/file.txt
.
This also applies to images (e.g. ![](@foo.jpg)
will display the image /static/foo.jpg
), although it will be
treated the same way as an external image; if you want the image to be scaled to save bandwidth, it is better to
put it into your content tree instead.
When writing HTML, the following is equivalent to the above two examples:
Entries
You can also link to an entry by its entry ID or by an absolute or relative file path to the source file. This also
supports anchors (#
). Some examples:
[link to entry 322](322)
→ link to entry 322[link to `Template format.md`](Template format.md)
→ link toTemplate format.md
[link to `/faq.md`](/faq.md)
→ link to/faq.md
[link to `../api/view.md#order`](../api/view.md#order)
→ link to../api/view.md#order
And the HTML equivalents to the above:
As above, you can also use these in any HTML element with the src
, href
, or arbitrary attributes annotated with $
; for example:
Fenced code blocks
As is typical for Markdown, Publ supports fenced code blocks, with a syntax like:
Syntax extensions
Publ allows you to add a title to a fenced code block, by putting it on the first line and prefixed with !
:
This will render as:
Note: If you need the first line of code to start with a literal !
character, put a blank line before it, or prefix it with a \
. For example,
renders as:
You can also provide template override arguments to the code block by putting parameters after the language declaration; for example:
These render as:
HTML output
The overall code block structure is:
If a language is specified, the <pre>
also gets class="highlight"
and a data-language
attribute that is set to the language.
If code highlighting is enabled, the code is also run through Pygments.
If line number links are enabled, the <pre>
gets an empty attribute of data-line-numbers
.
Each line is wrapped in a <span class="line">
and a <span class="line-content">
, to allow for additional CSS formatting. If both code_highlight
and code_number_links
are enabled, there will also be a <a class="line-number"></a>
inserted before the line-content
span, and the line
span will have an ID that this links to. This allows for code-line permalinks:
This markup is intended to be used with CSS counters to actually add the line numbering, which allows copy-and-paste to still function correctly. For a minimal example:
For a more thorough example of how to format fancy code, look at the code block CSS file for this website.