Emissary’s templates determine much more than the way a particular site looks. Templates can define their own data schemas, workflow rules, and other site behaviors. Each one is a self-contained unit that can be loaded from your server’s filesystem or downloaded from a networked Git repository. This lets system administrators set the ground-rules for how their servers are to be used, while still giving website owners control over their individual domains.
The Emissary distribution includes a number of default templates to get started, although system administrators can use the Setup Console to remove these and add a set of custom templates instead.
Creating and Distributing Templates
Each Template is just a collection of files in a directory that Emissary knows to scan. This can be a directory in the local filesystem, or a remote Git repository that is registered in the server configuration. Emissary will recognize a directory as a template if it contains a template.json
(or template.hjson
) definition file (as opposed to a theme.json
file or a widget.json
file).
Template File Format
Property | Description |
---|---|
templateId | Unique name used to identify this template |
model | Name of one of the renderer models to use when rendering. Almost always “stream” |
extends | Array of Template IDs that this Template extends (see below) |
templateRole | The role that this template takes in the system. Used to determine where new streams can be placed in the hierarchy |
socialRole | ActivityPub object type to use when publishing streams to ActivityPub (e.g. Note, Article, Image, etc) |
containedBy | Array of zero or more template roles that can contain this template. |
label | Human-friendly label, displayed in template lists |
description | Human-friendly description, displayed in template lists |
icon | Icon name, displayed in template lists |
widgetLocations | Array of region names where widgets can be embedded. TOP, LEFT, BOTTOM, RIGHT are recommended. |
bundles | Describes resource bundles that are packaged with this Template |
schema | Describes the data schema of all custom data that this Template uses |
states | Defines the workflow states that this Template uses (see below) |
accessRoles | Defines the user roles that this Template uses (see below) |
actions | Defines the actions that can be performed on streams that use this Template (see below) |
Example JSON
Here is an example template.json
file that includes some of these options:
{
"templateId": "photo",
"socialRole":"Image",
"model":"stream",
"containedBy": ["photo-album"],
"label": "Photograph",
"icon": "picture",
"schema": {
"type": "object",
"properties": {
"document": {"type":"object", "properties": {
"label": {"type": "string"},
"imageUrl": {"type": "string"}
}}
}
},
"states": {
"default": {
"label": "Default State",
"description": "Photos only have one state"
}
},
"accessRoles": {
"editor": {
"label": "Editor",
"description": "Can make changes to this item."
},
"readonly": {
"label": "Read Only Access",
"description": "Can view this photo, but cannot make changes"
}
},
"actions": {
"view": {
"step": "view-html",
},
"edit": {
"roles": ["editor"],
"steps":[{
"step":"as-modal",
"steps": [{
"step":"edit",
"form": {
"type":"layout-vertical",
"label":"Edit Folder",
"children": [
{"type":"text", "label":"Label", "path":"document.label"},
{"type":"textarea", "label":"Summary", "path":"document.summary"},
{"type":"select", "label":"Format", "path":"data.format"},
{"type":"select", "label":"Show Images", "path":"data.showImages"}
]
}
},
{"step":"save", "comment":"Updated Folder"}
]}]
},
"delete": {
"roles": ["author"],
"steps": [
{"step":"delete", "title": "Delete this Photograph?"},
{"step": "forward-to", "url":"/{{.ParentID}}"}
]
}
}
}
Actions and Steps
Every Template defines a series of actions that can be performed on the Streams that use that Template. These actions might be as simple as “Create”, “View”, “Update”, and “Delete” – or they might define a sophisticated publishing and approval workflow that spans a number of individual users and roles.
There are three actions defined in the example template.json
file above: “view”, “edit”, and “delete”. These actions can be as simple as setting some initial data, or loading an HTML template to display to the user, or they can define a complex behavior like the edit action.
Each action is composed from a library of interchangeable Steps that provide pre-built functionality to your template design. Steps each have their own rules and parameters. Some are very simple and only do one thing. Others can contain a large number of options – including other steps – to facilitate complex branching and workflow rules. All steps are pre-compiled when the template is loaded in order to maximize server performance.
HTML Content
In the action list above, the “view” action displays an actual web page. This HTML content is packaged into the template as a Standard Go Template. Like Themes and Widgets, These content templates are stored as separate files in the template directory, that must all be named with the *.html
suffix.
State Machines
Every stream in Emissary is also a mini state machine. Each template can define the various states that its streams support. For example, the built-in article templates include “published” and “unpublished” states, while the built-in comment templates use “visible”, “hidden”, and “awaiting-moderation”. Here’s an example snippet from the built-in Article template:
{
"states":{
"unpublished": {
"label":"Unpublished",
"description":"Visible only to Authors and Owners"
},
"published": {
"label":"Published",
"description":"Visible to all people with permissions"
}
} ...
}
States determine which Actions can be taken on a Stream at any given time. This can be further limited to specific users so that, for instance, an Administrator might be allowed to “edit” a Stream when it is marked as “published”, but the original commenter could not.
Extending / Inheriting Templates
Many templates share are so closely related, or share a lot of features in common. In this case, you can put common features in a base template that is “extended” by others.
As an example, the default package includes an “article” template that defines most of the workflow for creating and publishing articles. Two other templates – “article-editorjs” and “article-markdown” – extend this base template with specific functionality required for each of these different data types.
Here’s how extensions work: If a template defines an action itself, then that is the action that is used. Otherwise, Emissary searches through the list of extensions in the order in which they are defined to find that action name. To extend other templates, add an “extends” key into your template.json
file with an array of template names as its value.
For example: if a template declares this: "extends": ["A", "B", "C"]
then Emissary will first search template “A” for the required actions, then “B”, then “C”.
This inheritance structure can also cascade upwards, so that template “D” could extend “E”, which itself extends “F”.
This extension mechanism also makes it simple for Templates to inherit from multiple bases. In this case, if there multiple extensions, then the first template listed (and all of it’s ancestors in order) is searched first, then the second (with all of its ancestors), and so on.